// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
/**
@title Voting Escrow
@author Curve Finance
@license MIT
@notice Votes have a weight depending on time, so that users are
committed to the future of (whatever they are voting for)
@dev Vote weight decays linearly over time. Lock time cannot be
more than `MAXTIME` (3 years).
# Voting escrow to have time-weighted votes
# Votes have a weight depending on time, so that users are committed
# to the future of (whatever they are voting for).
# The weight in this implementation is linear, and lock cannot be more than maxtime:
# w ^
# 1 + /
# | /
# | /
# | /
# |/
# 0 +--------+------> time
# maxtime (3 years?)
*/
struct Point {
int128 bias;
int128 slope; // # -dweight / dt
uint ts;
uint blk; // block
}
struct LockedBalance {
int128 amount;
uint end;
}
contract sVotingEscrow is Ownable, ReentrancyGuard {
enum DepositType {
DEPOSIT_FOR_TYPE,
CREATE_LOCK_TYPE,
INCREASE_LOCK_AMOUNT,
INCREASE_UNLOCK_TIME
}
event Deposit(address indexed provider, uint value, uint indexed locktime, DepositType deposit_type, uint ts);
event Withdraw(address indexed provider, uint value, uint ts);
uint internal constant WEEK = 1 weeks;
uint public constant MAXTIME = 3 * 365 * 86400;
int128 internal constant iMAXTIME = 3 * 365 * 86400;
uint internal constant MULTIPLIER = 1 ether;
mapping(address => LockedBalance) public locked;
uint public epoch;
mapping(uint => Point) public point_history; // epoch -> unsigned point
mapping(address => Point[1000000000]) public user_point_history; // user -> Point[user_epoch]
mapping(address => uint) public user_point_epoch;
mapping(uint => int128) public slope_changes; // time -> signed slope change
string public constant name = "sveSTG";
string public constant symbol = "sveSTG";
string public constant version = "1.0.0";
uint8 public constant decimals = 18;
/// @notice Contract constructor
constructor() {
point_history[0].blk = block.number;
point_history[0].ts = block.timestamp;
}
/// @notice Get the most recently recorded rate of voting power decrease for `_addr`
/// @param addr Address of the user wallet
/// @return Value of the slope
function get_last_user_slope(address addr) external view returns (int128) {
uint uepoch = user_point_epoch[addr];
return user_point_history[addr][uepoch].slope;
}
/// @notice Get the timestamp for checkpoint `_idx` for `_addr`
/// @param _addr User wallet address
/// @param _idx User epoch number
/// @return Epoch time of the checkpoint
function user_point_history__ts(address _addr, uint _idx) external view returns (uint) {
return user_point_history[_addr][_idx].ts;
}
/// @notice Get timestamp when `_addr`'s lock finishes
/// @param _addr User wallet address
/// @return Epoch time of the lock end
function locked__end(address _addr) external view returns (uint) {
return locked[_addr].end;
}
/// @notice Record global and per-user data to checkpoint
/// @param _addr User's wallet address. No user checkpoint if 0x0
/// @param old_locked Pevious locked amount / end lock time for the user
/// @param new_locked New locked amount / end lock time for the user
function _checkpoint(address _addr, LockedBalance memory old_locked, LockedBalance memory new_locked) internal {
Point memory u_old;
Point memory u_new;
int128 old_dslope = 0;
int128 new_dslope = 0;
uint _epoch = epoch;
if (_addr != address(0x0)) {
// Calculate slopes and biases
// Kept at zero when they have to
if (old_locked.end > block.timestamp && old_locked.amount > 0) {
u_old.slope = old_locked.amount / iMAXTIME;
u_old.bias = u_old.slope * int128(int(old_locked.end - block.timestamp));
}
if (new_locked.end > block.timestamp && new_locked.amount > 0) {
u_new.slope = new_locked.amount / iMAXTIME;
u_new.bias = u_new.slope * int128(int(new_locked.end - block.timestamp));
}
// Read values of scheduled changes in the slope
// old_locked.end can be in the past and in the future
// new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros
old_dslope = slope_changes[old_locked.end];
if (new_locked.end != 0) {
if (new_locked.end == old_locked.end) {
new_dslope = old_dslope;
} else {
new_dslope = slope_changes[new_locked.end];
}
}
}
Point memory last_point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number});
if (_epoch > 0) {
last_point = point_history[_epoch];
}
uint last_checkpoint = last_point.ts;
// initial_last_point is used for extrapolation to calculate block number
// (approximately, for *At methods) and save them
// as we cannot figure that out exactly from inside the contract
uint initial_last_point_ts = last_point.ts;
uint initial_last_point_blk = last_point.blk;
uint block_slope = 0; // dblock/dt
if (block.timestamp > last_point.ts) {
block_slope = (MULTIPLIER * (block.number - last_point.blk)) / (block.timestamp - last_point.ts);
}
// If last point is already recorded in this block, slope=0
// But that's ok b/c we know the block in such case
// Go over weeks to fill history and calculate what the current point is
uint t_i = (last_checkpoint / WEEK) * WEEK;
for (uint i = 0; i < 255; ++i) {
// Hopefully it won't happen that this won't get used in 5 years!
// If it does, users will be able to withdraw but vote weight will be broken
t_i += WEEK;
int128 d_slope = 0;
if (t_i > block.timestamp) {
t_i = block.timestamp;
} else {
d_slope = slope_changes[t_i];
}
last_point.bias -= last_point.slope * int128(int(t_i - last_checkpoint));
last_point.slope += d_slope;
if (last_point.bias < 0) {
// This can happen
last_point.bias = 0;
}
if (last_point.slope < 0) {
// This cannot happen - just in case
last_point.slope = 0;
}
last_checkpoint = t_i;
last_point.ts = t_i;
last_point.blk = initial_last_point_blk + (block_slope * (t_i - initial_last_point_ts)) / MULTIPLIER;
_epoch += 1;
if (t_i == block.timestamp) {
last_point.blk = block.number;
break;
} else {
point_history[_epoch] = last_point;
}
}
epoch = _epoch;
// Now point_history is filled until t=now
if (_addr != address(0x0)) {
// If last point was in this block, the slope change has been applied already
// But in such case we have 0 slope(s)
last_point.slope += (u_new.slope - u_old.slope);
last_point.bias += (u_new.bias - u_old.bias);
if (last_point.slope < 0) {
last_point.slope = 0;
}
if (last_point.bias < 0) {
last_point.bias = 0;
}
}
// Record the changed point into history
point_history[_epoch] = last_point;
if (_addr != address(0x0)) {
// Schedule the slope changes (slope is going down)
// We subtract new_user_slope from [new_locked.end]
// and add old_user_slope to [old_locked.end]
if (old_locked.end > block.timestamp) {
// old_dslope was <something> - u_old.slope, so we cancel that
old_dslope += u_old.slope;
if (new_locked.end == old_locked.end) {
old_dslope -= u_new.slope; // It was a new deposit, not extension
}
slope_changes[old_locked.end] = old_dslope;
}
if (new_locked.end > block.timestamp) {
if (new_locked.end > old_locked.end) {
new_dslope -= u_new.slope; // old slope disappeared at this point
slope_changes[new_locked.end] = new_dslope;
}
// else: we recorded it already in old_dslope
}
// Now handle user history
address addr = _addr;
uint user_epoch = user_point_epoch[addr] + 1;
user_point_epoch[addr] = user_epoch;
u_new.ts = block.timestamp;
u_new.blk = block.number;
user_point_history[addr][user_epoch] = u_new;
}
}
/// @notice Deposit and lock tokens for a user
/// @param _addr User's wallet address
/// @param _value Amount to deposit
/// @param unlock_time New time when to unlock the tokens, or 0 if unchanged
/// @param locked_balance Previous locked amount / timestamp
/// @param deposit_type The type of deposit
function _deposit_for(address _addr, uint _value, uint unlock_time, LockedBalance memory locked_balance, DepositType deposit_type) internal {
LockedBalance memory _locked = locked_balance;
LockedBalance memory old_locked;
(old_locked.amount, old_locked.end) = (_locked.amount, _locked.end);
// Adding to existing lock, or if a lock is expired - creating a new one
_locked.amount += int128(int(_value));
if (unlock_time != 0) {
_locked.end = unlock_time;
}
locked[_addr] = _locked;
// Possibilities:
// Both old_locked.end could be current or expired (>/< block.timestamp)
// value == 0 (extend lock) or value > 0 (add to lock or extend lock)
// _locked.end > block.timestamp (always)
_checkpoint(_addr, old_locked, _locked);
emit Deposit(_addr, _value, _locked.end, deposit_type, block.timestamp);
}
/// @notice Record global data to checkpoint
function checkpoint() external {
_checkpoint(address(0x0), LockedBalance(0, 0), LockedBalance(0, 0));
}
/// @notice Deposit `_value` tokens for `_addr` and lock until `_unlock_time`
/// @param _addr Create lock for address
/// @param _value Amount to deposit
/// @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks
function create_lock_for(address _addr, uint _value, uint _unlock_time) external nonReentrant onlyOwner {
require(_value > 0); // dev: need non-zero value
LockedBalance memory _locked = locked[_addr];
require(_locked.amount == 0, "Withdraw old tokens first");
uint unlock_time = (_unlock_time / WEEK) * WEEK; // Locktime is rounded down to weeks
require(unlock_time > block.timestamp, "Can only lock until time in the future");
require(unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 3 years max");
_deposit_for(_addr, _value, unlock_time, _locked, DepositType.CREATE_LOCK_TYPE);
}
/// @notice Deposit `_value` additional tokens for `_addr` without modifying the unlock time
/// @param _addr Increase amount for address
/// @param _value Amount of tokens to deposit and add to the lock
function increase_amount_for(address _addr, uint _value) external nonReentrant onlyOwner {
LockedBalance memory _locked = locked[_addr];
require(_value > 0); // dev: need non-zero value
require(_locked.amount > 0, "No existing lock found");
require(_locked.end > block.timestamp, "Cannot add to expired lock. Withdraw");
_deposit_for(_addr, _value, 0, _locked, DepositType.INCREASE_LOCK_AMOUNT);
}
/// @notice Extend the unlock time for `_addr` to `_unlock_time`
/// @param _addr Increase unlock time for address
/// @param _unlock_time New epoch time for unlocking
function increase_unlock_time_for(address _addr, uint _unlock_time) external nonReentrant onlyOwner {
LockedBalance memory _locked = locked[_addr];
uint unlock_time = (_unlock_time / WEEK) * WEEK; // Locktime is rounded down to weeks
require(_locked.end > block.timestamp, "Lock expired");
require(_locked.amount > 0, "Nothing is locked");
require(unlock_time > _locked.end, "Can only increase lock duration");
require(unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 3 years max");
_deposit_for(_addr, 0, unlock_time, _locked, DepositType.INCREASE_UNLOCK_TIME);
}
/// @notice Withdraw all tokens for `_addr`
/// @dev Only possible if the lock has expired
/// @param _addr Withdraw for address
function withdraw_for(address _addr) external nonReentrant onlyOwner {
LockedBalance memory _locked = locked[_addr];
uint value = uint(int(_locked.amount));
locked[_addr] = LockedBalance(0, 0);
// old_locked can have either expired <= timestamp or zero end
// _locked has only 0 end
// Both can have >= 0 amount
_checkpoint(_addr, _locked, LockedBalance(0, 0));
emit Withdraw(_addr, value, block.timestamp);
}
// The following ERC20/minime-compatible methods are not real balanceOf and supply!
// They measure the weights for the purpose of voting, so they don't represent
// real coins.
/// @notice Binary search to estimate timestamp for block number
/// @param _block Block to find
/// @param max_epoch Don't go beyond this epoch
/// @return Approximate timestamp for block
function _find_block_epoch(uint _block, uint max_epoch) internal view returns (uint) {
// Binary search
uint _min = 0;
uint _max = max_epoch;
for (uint i = 0; i < 128; ++i) {
// Will be always enough for 128-bit numbers
if (_min >= _max) {
break;
}
uint _mid = (_min + _max + 1) / 2;
if (point_history[_mid].blk <= _block) {
_min = _mid;
} else {
_max = _mid - 1;
}
}
return _min;
}
/// @notice Get the current voting power for `_addr`
/// @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
/// @param addr User wallet address
/// @param _t Epoch time to return voting power at
/// @return User voting power
function _balanceOf(address addr, uint _t) internal view returns (uint) {
uint _epoch = user_point_epoch[addr];
if (_epoch == 0) {
return 0;
} else {
Point memory last_point = user_point_history[addr][_epoch];
last_point.bias -= last_point.slope * int128(int(_t) - int(last_point.ts));
if (last_point.bias < 0) {
last_point.bias = 0;
}
return uint(int(last_point.bias));
}
}
function balanceOfAtT(address addr, uint _t) external view returns (uint) {
return _balanceOf(addr, _t);
}
function balanceOf(address addr) external view returns (uint) {
return _balanceOf(addr, block.timestamp);
}
/// @notice Measure voting power of `addr` at block height `_block`
/// @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime
/// @param addr User's wallet address
/// @param _block Block to calculate the voting power at
/// @return Voting power
function balanceOfAt(address addr, uint _block) external view returns (uint) {
// Copying and pasting totalSupply code because Vyper cannot pass by
// reference yet
require(_block <= block.number);
// Binary search
uint _min = 0;
uint _max = user_point_epoch[addr];
for (uint i = 0; i < 128; ++i) {
// Will be always enough for 128-bit numbers
if (_min >= _max) {
break;
}
uint _mid = (_min + _max + 1) / 2;
if (user_point_history[addr][_mid].blk <= _block) {
_min = _mid;
} else {
_max = _mid - 1;
}
}
Point memory upoint = user_point_history[addr][_min];
uint max_epoch = epoch;
uint _epoch = _find_block_epoch(_block, max_epoch);
Point memory point_0 = point_history[_epoch];
uint d_block = 0;
uint d_t = 0;
if (_epoch < max_epoch) {
Point memory point_1 = point_history[_epoch + 1];
d_block = point_1.blk - point_0.blk;
d_t = point_1.ts - point_0.ts;
} else {
d_block = block.number - point_0.blk;
d_t = block.timestamp - point_0.ts;
}
uint block_time = point_0.ts;
if (d_block != 0) {
block_time += (d_t * (_block - point_0.blk)) / d_block;
}
upoint.bias -= upoint.slope * int128(int(block_time - upoint.ts));
if (upoint.bias >= 0) {
return uint(uint128(upoint.bias));
} else {
return 0;
}
}
/// @notice Calculate total voting power at some point in the past
/// @param point The point (bias/slope) to start search from
/// @param t Time to calculate the total voting power at
/// @return Total voting power at that time
function _supply_at(Point memory point, uint t) internal view returns (uint) {
Point memory last_point = point;
uint t_i = (last_point.ts / WEEK) * WEEK;
for (uint i = 0; i < 255; ++i) {
t_i += WEEK;
int128 d_slope = 0;
if (t_i > t) {
t_i = t;
} else {
d_slope = slope_changes[t_i];
}
last_point.bias -= last_point.slope * int128(int(t_i - last_point.ts));
if (t_i == t) {
break;
}
last_point.slope += d_slope;
last_point.ts = t_i;
}
if (last_point.bias < 0) {
last_point.bias = 0;
}
return uint(uint128(last_point.bias));
}
/// @notice Calculate total voting power
/// @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
/// @return Total voting power
function _totalSupply(uint t) internal view returns (uint) {
uint _epoch = epoch;
Point memory last_point = point_history[_epoch];
return _supply_at(last_point, t);
}
function totalSupplyAtT(uint t) external view returns (uint) {
return _totalSupply(t);
}
function totalSupply() external view returns (uint) {
return _totalSupply(block.timestamp);
}
/// @notice Calculate total voting power at some point in the past
/// @param _block Block to calculate the total voting power at
/// @return Total voting power at `_block`
function totalSupplyAt(uint _block) external view returns (uint) {
require(_block <= block.number);
uint _epoch = epoch;
uint target_epoch = _find_block_epoch(_block, _epoch);
Point memory point = point_history[target_epoch];
uint dt = 0;
if (target_epoch < _epoch) {
Point memory point_next = point_history[target_epoch + 1];
if (point.blk != point_next.blk) {
dt = ((_block - point.blk) * (point_next.ts - point.ts)) / (point_next.blk - point.blk);
}
} else {
if (point.blk != block.number) {
dt = ((_block - point.blk) * (block.timestamp - point.ts)) / (block.number - point.blk);
}
}
// Now dt contains info on how far are we beyond point
return _supply_at(point, point.ts + dt);
}
}
{
"compilationTarget": {
"contracts/sVotingEscrow.sol": "sVotingEscrow"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 9999
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"locktime","type":"uint256"},{"indexed":false,"internalType":"enum sVotingEscrow.DepositType","name":"deposit_type","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"ts","type":"uint256"}],"name":"Deposit","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":"provider","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"ts","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"MAXTIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"_block","type":"uint256"}],"name":"balanceOfAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint256","name":"_t","type":"uint256"}],"name":"balanceOfAtT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"checkpoint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"_unlock_time","type":"uint256"}],"name":"create_lock_for","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"epoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"get_last_user_slope","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"increase_amount_for","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"},{"internalType":"uint256","name":"_unlock_time","type":"uint256"}],"name":"increase_unlock_time_for","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"locked","outputs":[{"internalType":"int128","name":"amount","type":"int128"},{"internalType":"uint256","name":"end","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"}],"name":"locked__end","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"point_history","outputs":[{"internalType":"int128","name":"bias","type":"int128"},{"internalType":"int128","name":"slope","type":"int128"},{"internalType":"uint256","name":"ts","type":"uint256"},{"internalType":"uint256","name":"blk","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"slope_changes","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_block","type":"uint256"}],"name":"totalSupplyAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"t","type":"uint256"}],"name":"totalSupplyAtT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"user_point_epoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"user_point_history","outputs":[{"internalType":"int128","name":"bias","type":"int128"},{"internalType":"int128","name":"slope","type":"int128"},{"internalType":"uint256","name":"ts","type":"uint256"},{"internalType":"uint256","name":"blk","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"},{"internalType":"uint256","name":"_idx","type":"uint256"}],"name":"user_point_history__ts","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"}],"name":"withdraw_for","outputs":[],"stateMutability":"nonpayable","type":"function"}]