pragma solidity 0.8.4;
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright (C) 2024 Yamato Protocol (DeFiGeek Community Japan)
*/
//solhint-disable max-line-length
//solhint-disable no-inline-assembly
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IYMT {
/**
* @dev mint token for recipient. Assuming onlyGovernance
*/
function mint(address to_, uint256 value_) external returns (bool);
function decimals() external view returns (uint8);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
function transfer(
address recipient,
uint256 amount
) external returns (bool);
function approve(address spender_, uint256 value_) external;
function rate() external view returns (uint256);
function futureEpochTimeWrite() external returns (uint256);
function startTime() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
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 make 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.0;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/
library SafeCast {
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toUint224(uint256 value) internal pure returns (uint224) {
require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
return uint224(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
return uint128(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
return uint96(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
return uint64(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
return uint32(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*/
function toUint8(uint256 value) internal pure returns (uint8) {
require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v3.1._
*/
function toInt128(int256 value) internal pure returns (int128) {
require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
return int128(value);
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v3.1._
*/
function toInt64(int256 value) internal pure returns (int64) {
require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
return int64(value);
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v3.1._
*/
function toInt32(int256 value) internal pure returns (int32) {
require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
return int32(value);
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v3.1._
*/
function toInt16(int256 value) internal pure returns (int16) {
require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
return int16(value);
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*
* _Available since v3.1._
*/
function toInt8(int256 value) internal pure returns (int8) {
require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
return int8(value);
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
return int256(value);
}
}
pragma solidity 0.8.4;
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright (C) 2024 Yamato Protocol (DeFiGeek Community Japan)
*/
//solhint-disable max-line-length
//solhint-disable no-inline-assembly
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "./Interfaces/IYMT.sol";
/**
* @title veYMT Token
* @notice Locked YMT. An ERC-20 but w/o transfer()
*/
contract veYMT is ReentrancyGuard {
// 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 (4 years?)
using SafeCast for uint256;
using SafeCast for int256;
struct Point {
int128 bias;
int128 slope; // - dweight / dt
uint256 ts; //timestamp
uint256 blk; // block
}
// We cannot really do block numbers per se b/c slope is per time, not per block
// and per block could be fairly bad b/c Ethereum changes blocktimes.
// What we can do is to extrapolate ***At functions
struct LockedBalance {
int128 amount;
uint256 end;
}
uint128 private constant DEPOSIT_FOR_TYPE = 0;
uint128 private constant CREATE_LOCK_TYPE = 1;
uint128 private constant INCREASE_LOCK_AMOUNT = 2;
uint128 private constant INCREASE_UNLOCK_TIME = 3;
event Deposit(
address indexed provider,
uint256 value,
uint256 indexed locktime,
uint128 _type,
uint256 ts
);
event Withdraw(address indexed provider, uint256 value, uint256 ts);
event Supply(uint256 prevSupply, uint256 supply);
uint256 public constant WEEK = 7 * 86400; // all future times are rounded by week
uint256 public constant MAXTIME = 4 * 365 * 86400; // 4 years
uint256 public constant MULTIPLIER = 1e18;
address public token;
uint256 public supply;
mapping(address => LockedBalance) public locked;
//everytime user deposit/withdraw/change_locktime, these values will be updated;
uint256 public epoch;
mapping(uint256 => Point) public pointHistory; // epoch -> unsigned point.
mapping(address => mapping(uint256 => Point)) public userPointHistory; // user -> Point[user_epoch]
mapping(address => uint256) public userPointEpoch;
mapping(uint256 => int128) public slopeChanges; // time -> signed slope change
// depositor -> user -> allowed
mapping(address => mapping(address => bool)) public depositForAllowed;
// user -> all allowed
mapping(address => bool) public depositForAllAllowed;
// Aragon's view methods for compatibility
bool public transfersEnabled;
string public name;
string public symbol;
uint8 public decimals;
/**
* @notice Contract constructor
* @param tokenAddr_ `YMT` token address
*/
constructor(address tokenAddr_) {
token = tokenAddr_;
pointHistory[0].blk = block.number;
pointHistory[0].ts = block.timestamp;
transfersEnabled = true;
decimals = IYMT(tokenAddr_).decimals();
name = "Voting-escrowed Yamato";
symbol = "veYMT";
}
/**
* @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 getLastUserSlope(address addr_) external view returns (int128) {
uint256 _uEpoch = userPointEpoch[addr_];
return userPointHistory[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 userPointHistoryTs(
address addr_,
uint256 idx_
) external view returns (uint256) {
return userPointHistory[addr_][idx_].ts;
}
/**
* @notice Get timestamp when `addr_`'s lock finishes
* @param addr_ User wallet
* @return Epoch time of the lock end
*/
function lockedEnd(address addr_) external view returns (uint256) {
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 oldLocked_ Pevious locked amount / end lock time for the user
* @param newLocked_ New locked amount / end lock time for the user
*/
function _checkpoint(
address addr_,
LockedBalance memory oldLocked_,
LockedBalance memory newLocked_
) internal {
Point memory _uOld;
Point memory _uNew;
int128 _oldDSlope;
int128 _newDSlope;
uint256 _epoch = epoch;
if (addr_ != address(0)) {
// Calculate slopes and biases
// Kept at zero when they have to
if (oldLocked_.end > block.timestamp && oldLocked_.amount > 0) {
unchecked {
_uOld.slope = int128(oldLocked_.amount / int256(MAXTIME));
}
_uOld.bias =
_uOld.slope *
castUint256ToInt128(oldLocked_.end - block.timestamp);
}
if (newLocked_.end > block.timestamp && newLocked_.amount > 0) {
unchecked {
_uNew.slope = int128(
uint128(newLocked_.amount) / uint128(MAXTIME)
);
}
_uNew.bias =
_uNew.slope *
castUint256ToInt128(newLocked_.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
_oldDSlope = slopeChanges[oldLocked_.end];
if (newLocked_.end != 0) {
if (newLocked_.end == oldLocked_.end) {
_newDSlope = _oldDSlope;
} else {
_newDSlope = slopeChanges[newLocked_.end];
}
}
}
Point memory _lastPoint = Point({
bias: 0,
slope: 0,
ts: block.timestamp,
blk: block.number
});
if (_epoch > 0) {
_lastPoint = Point({
bias: pointHistory[_epoch].bias,
slope: pointHistory[_epoch].slope,
ts: pointHistory[_epoch].ts,
blk: pointHistory[_epoch].blk
});
}
uint256 _lastCheckpoint = _lastPoint.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
Point memory _initialLastPoint = Point({
bias: _lastPoint.bias,
slope: _lastPoint.slope,
ts: _lastPoint.ts,
blk: _lastPoint.blk
});
uint256 _blockSlope;
if (block.timestamp > _lastPoint.ts) {
_blockSlope =
(MULTIPLIER * (block.number - _lastPoint.blk)) /
(block.timestamp - _lastPoint.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
uint256 _ti;
unchecked {
_ti = (_lastCheckpoint / WEEK) * WEEK;
}
for (uint256 i; i < 255; ) {
// 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
_ti += WEEK;
int128 _dSlope;
if (_ti > block.timestamp) {
_ti = block.timestamp;
} else {
_dSlope = slopeChanges[_ti];
}
_lastPoint.bias -=
_lastPoint.slope *
castInt256ToInt128(_ti.toInt256() - _lastCheckpoint.toInt256());
_lastPoint.slope += _dSlope;
if (_lastPoint.bias < 0) {
// This can happen
_lastPoint.bias = 0;
}
if (_lastPoint.slope < 0) {
// This cannot happen - just in case
_lastPoint.slope = 0;
}
_lastCheckpoint = _ti;
_lastPoint.ts = _ti;
_lastPoint.blk =
_initialLastPoint.blk +
(_blockSlope * (_ti - _initialLastPoint.ts)) /
MULTIPLIER;
++_epoch;
if (_ti == block.timestamp) {
_lastPoint.blk = block.number;
break;
} else {
pointHistory[_epoch] = Point({
bias: _lastPoint.bias,
slope: _lastPoint.slope,
ts: _lastPoint.ts,
blk: _lastPoint.blk
});
}
unchecked {
++i;
}
}
epoch = _epoch;
// Now point_history is filled until t=now
if (addr_ != address(0)) {
// If last point was in this block, the slope change has been applied already
// But in such case we have 0 slope(s)
_lastPoint.slope += (_uNew.slope - _uOld.slope);
_lastPoint.bias += (_uNew.bias - _uOld.bias);
if (_lastPoint.slope < 0) {
_lastPoint.slope = 0;
}
if (_lastPoint.bias < 0) {
_lastPoint.bias = 0;
}
}
// Record the changed point into history
pointHistory[_epoch] = _lastPoint;
address _addr = addr_; // To avoid being "Stack Too Deep"
if (_addr != address(0)) {
// 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 (oldLocked_.end > block.timestamp) {
// old_dslope was <something> - u_old.slope, so we cancel that
_oldDSlope += _uOld.slope;
if (newLocked_.end == oldLocked_.end) {
_oldDSlope -= _uNew.slope; // It was a new deposit, not extension
}
slopeChanges[oldLocked_.end] = _oldDSlope;
}
if (newLocked_.end > block.timestamp) {
if (newLocked_.end > oldLocked_.end) {
_newDSlope -= _uNew.slope; // old slope disappeared at this point
slopeChanges[newLocked_.end] = _newDSlope;
}
// else: we recorded it already in old_dslope
}
// Now handle user history
uint256 _userEpoch;
unchecked {
_userEpoch = userPointEpoch[_addr] + 1;
}
userPointEpoch[_addr] = _userEpoch;
_uNew.ts = block.timestamp;
_uNew.blk = block.number;
userPointHistory[_addr][_userEpoch] = _uNew;
}
}
/**
* @notice Deposit and lock tokens for a user
* @param addr_ User's wallet address
* @param value_ Amount to deposit
* @param unlockTime_ New time when to unlock the tokens, or 0 if unchanged
* @param lockedBalance_ Previous locked amount / timestamp
*/
function _depositFor(
address addr_,
uint256 value_,
uint256 unlockTime_,
LockedBalance memory lockedBalance_,
uint128 type_
) internal {
LockedBalance memory _locked = LockedBalance(
lockedBalance_.amount,
lockedBalance_.end
);
LockedBalance memory _oldLocked = LockedBalance(
lockedBalance_.amount,
lockedBalance_.end
);
uint256 _supplyBefore = supply;
supply = _supplyBefore + value_;
// Adding to existing lock, or if a lock is expired - creating a new one
_locked.amount += castUint256ToInt128(value_);
if (unlockTime_ != 0) {
_locked.end = unlockTime_;
}
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_, _oldLocked, _locked);
if (value_ != 0) {
require(
IYMT(token).transferFrom(addr_, address(this), value_),
"Transfer failed"
);
}
emit Deposit(addr_, value_, _locked.end, type_, block.timestamp);
emit Supply(_supplyBefore, _supplyBefore + value_);
}
/**
* @notice Record global data to checkpoint
*/
function checkpoint() external {
LockedBalance memory _old;
LockedBalance memory _new;
_checkpoint(address(0), _old, _new);
}
/**
* @notice Deposit `_value` tokens for `_addr` and add to the lock
* @dev Anyone (even a smart contract) can deposit for someone else, but
cannot extend their locktime and deposit for a brand new user
* @param addr_ User's wallet address
* @param value_ Amount to add to user's lock
*/
function depositFor(address addr_, uint256 value_) external nonReentrant {
require(
depositForAllAllowed[addr_] || depositForAllowed[msg.sender][addr_],
"Not allowed to deposit for this address"
);
LockedBalance memory _locked = locked[addr_];
require(value_ > 0, "Need non-zero value");
require(_locked.amount > 0, "No existing lock found");
require(
_locked.end > block.timestamp,
"Cannot add to expired lock. Withdraw"
);
_depositFor(addr_, value_, 0, locked[addr_], DEPOSIT_FOR_TYPE);
}
/**
* @notice Toggles the permission for a specific depositor to call depositFor on behalf of the message sender.
* @param depositor_ The address of the depositor whose permission is being toggled.
*/
function toggleDepositForApproval(address depositor_) external {
depositForAllowed[depositor_][msg.sender] = !depositForAllowed[
depositor_
][msg.sender];
}
/**
* @notice Toggles the permission for all addresses to call depositFor on behalf of the message sender.
*/
function toggleDepositForAllApproval() external {
depositForAllAllowed[msg.sender] = !depositForAllAllowed[msg.sender];
}
/**
* @notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time`
* @param value_ Amount to deposit
* @param unlockTime_ Epoch time when tokens unlock, rounded down to whole weeks
*/
function createLock(
uint256 value_,
uint256 unlockTime_
) external nonReentrant {
uint256 _unlockTimeRounded = (unlockTime_ / WEEK) * WEEK;
LockedBalance memory _locked = locked[msg.sender];
require(value_ > 0, "Need non-zero value");
require(_locked.amount == 0, "Withdraw old tokens first");
require(
_unlockTimeRounded > block.timestamp,
"Can only lock until time in the future"
);
require(
_unlockTimeRounded <= block.timestamp + MAXTIME,
"Voting lock can be 4 years max"
);
_depositFor(
msg.sender,
value_,
_unlockTimeRounded,
_locked,
CREATE_LOCK_TYPE
);
}
/**
* @notice Deposit `_value` additional tokens for `msg.sender`
without modifying the unlock time
* @param value_ Amount of tokens to deposit and add to the lock
*/
function increaseAmount(uint256 value_) external nonReentrant {
LockedBalance memory _locked = locked[msg.sender];
require(value_ > 0, "Need non-zero value");
require(_locked.amount > 0, "No existing lock found");
require(
_locked.end > block.timestamp,
"Cannot add to expired lock. Withdraw"
);
_depositFor(msg.sender, value_, 0, _locked, INCREASE_LOCK_AMOUNT);
}
/**
* @notice Extend the unlock time for `msg.sender` to `_unlock_time`
* @param unlockTime_ New epoch time for unlocking
*/
function increaseUnlockTime(uint256 unlockTime_) external nonReentrant {
LockedBalance memory _locked = locked[msg.sender];
uint256 _unlockTimeRounded;
unchecked {
_unlockTimeRounded = (unlockTime_ / WEEK) * WEEK;
}
require(_locked.end > block.timestamp, "Lock expired");
require(_locked.amount > 0, "Nothing is locked");
require(
_unlockTimeRounded > _locked.end,
"Can only increase lock duration"
);
require(
_unlockTimeRounded <= block.timestamp + MAXTIME,
"Voting lock can be 4 years max"
);
_depositFor(
msg.sender,
0,
_unlockTimeRounded,
_locked,
INCREASE_UNLOCK_TIME
);
}
/**
* @notice Withdraw all tokens for `msg.sender`
* @dev Only possible if the lock has expired
*/
function withdraw() external nonReentrant {
LockedBalance memory _locked = LockedBalance(
locked[msg.sender].amount,
locked[msg.sender].end
);
require(block.timestamp >= _locked.end, "The lock didn't expire");
uint256 _value = uint256(int256(_locked.amount));
LockedBalance memory _oldLocked = LockedBalance(
locked[msg.sender].amount,
locked[msg.sender].end
);
_locked.end = 0;
_locked.amount = 0;
locked[msg.sender] = LockedBalance(_locked.amount, _locked.end);
uint256 _supplyBefore = supply;
supply = _supplyBefore - _value;
// old_locked can have either expired <= timestamp or zero end
// _locked has only 0 end
// Both can have >= 0 amount
_checkpoint(msg.sender, _oldLocked, _locked);
require(IYMT(token).transfer(msg.sender, _value), "Transfer failed");
emit Withdraw(msg.sender, _value, block.timestamp);
emit Supply(_supplyBefore, _supplyBefore - _value);
}
// 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 epoch for block number
* @param block_ Block to find
* @param maxEpoch_ Don't go beyond this epoch
* @return Approximate epoch for block
*/
function findBlockEpoch(
uint256 block_,
uint256 maxEpoch_
) internal view returns (uint256) {
// Binary search
uint256 _min;
uint256 _max = maxEpoch_;
unchecked {
for (uint256 i; i < 128; ++i) {
// Will be always enough for 128-bit numbers
if (_min >= _max) {
break;
}
uint256 _mid = (_min + _max + 1) / 2;
if (pointHistory[_mid].blk <= block_) {
_min = _mid;
} else {
_max = _mid - 1;
}
}
}
return _min;
}
/**
* @notice Get the current voting power for `msg.sender`
* @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_,
uint256 t_
) external view returns (uint256) {
uint256 epoch_ = userPointEpoch[addr_];
if (epoch_ == 0) {
return 0;
} else {
Point memory lastPoint = userPointHistory[addr_][epoch_];
lastPoint.bias -=
lastPoint.slope *
int128(int256(t_) - int256(lastPoint.ts));
if (lastPoint.bias < 0) {
lastPoint.bias = 0;
}
return uint256(int256(lastPoint.bias));
}
}
/**
* @notice Get the current voting power for `msg.sender`
* @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
* @param addr_ User wallet address
* @return User voting power
*/
function balanceOf(address addr_) external view returns (uint256) {
uint256 epoch_ = userPointEpoch[addr_];
if (epoch_ == 0) {
return 0;
} else {
Point memory lastPoint = userPointHistory[addr_][epoch_];
lastPoint.bias -=
lastPoint.slope *
int128(int256(block.timestamp) - int256(lastPoint.ts));
if (lastPoint.bias < 0) {
lastPoint.bias = 0;
}
return uint256(int256(lastPoint.bias));
}
}
/**
* @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_,
uint256 block_
) external view returns (uint256) {
// Copying and pasting totalSupply code because Vyper cannot pass by
// reference yet
require(block_ <= block.number, "Cannot look up future block");
// Binary search
uint256 min_;
uint256 max_ = userPointEpoch[addr_];
unchecked {
for (uint i; i < 128; ++i) {
// Will be always enough for 128-bit numbers
if (min_ >= max_) {
break;
}
uint256 mid_ = (min_ + max_ + 1) / 2;
if (userPointHistory[addr_][mid_].blk <= block_) {
min_ = mid_;
} else {
max_ = mid_ - 1;
}
}
}
Point memory _upoint = userPointHistory[addr_][min_];
uint256 _maxEpoch = epoch;
uint256 _epoch = findBlockEpoch(block_, _maxEpoch);
Point memory _point0 = pointHistory[_epoch];
uint256 _dBlock;
uint256 _dt;
if (_epoch < _maxEpoch) {
Point memory _point1 = pointHistory[_epoch + 1];
_dBlock = _point1.blk - _point0.blk;
_dt = _point1.ts - _point0.ts;
} else {
_dBlock = block.number - _point0.blk;
_dt = block.timestamp - _point0.ts;
}
uint256 _blockTime = _point0.ts;
if (_dBlock != 0) {
_blockTime += (_dt * (block_ - _point0.blk)) / _dBlock;
}
_upoint.bias -=
_upoint.slope *
int128(int256(_blockTime) - int256(_upoint.ts));
if (_upoint.bias >= 0) {
return uint256(int256(_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 supplyAt(
Point memory point_,
uint256 t_
) internal view returns (uint256) {
Point memory _lastPoint = point_;
uint256 _ti;
unchecked {
_ti = (_lastPoint.ts / WEEK) * WEEK;
}
for (uint256 i; i < 255; ) {
_ti += WEEK;
int128 _dSlope;
if (_ti > t_) {
_ti = t_;
} else {
_dSlope = slopeChanges[_ti];
}
_lastPoint.bias -=
_lastPoint.slope *
int128(int256(_ti) - int256(_lastPoint.ts));
if (_ti == t_) {
break;
}
_lastPoint.slope += _dSlope;
_lastPoint.ts = _ti;
unchecked {
++i;
}
}
if (_lastPoint.bias < 0) {
_lastPoint.bias = 0;
}
return uint256(int256(_lastPoint.bias));
}
/**
* @notice Calculate total voting power
* @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
* @param t_ Time to calculate the total voting power at
* @return Total voting power
*/
function totalSupply(uint256 t_) external view returns (uint256) {
uint256 _epoch = epoch;
Point memory _lastPoint = pointHistory[_epoch];
return supplyAt(_lastPoint, t_);
}
/**
* @notice Calculate total voting power
* @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
* @return Total voting power
*/
function totalSupply() external view returns (uint256) {
uint256 _epoch = epoch;
Point memory _lastPoint = pointHistory[_epoch];
return supplyAt(_lastPoint, 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(uint256 block_) external view returns (uint256) {
require(block_ <= block.number, "Invalid block number");
uint256 _epoch = epoch;
uint256 _targetEpoch = findBlockEpoch(block_, _epoch);
Point memory _point = pointHistory[_targetEpoch];
// to avoid underflow revert
if (_point.blk > block_) {
return 0;
}
uint256 _dt;
if (_targetEpoch < _epoch) {
Point memory _pointNext = pointHistory[_targetEpoch + 1];
if (_point.blk != _pointNext.blk) {
_dt =
((block_ - _point.blk) * (_pointNext.ts - _point.ts)) /
(_pointNext.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 supplyAt(_point, _point.ts + _dt);
}
function castUint256ToInt128(uint256 value) public pure returns (int128) {
return value.toInt256().toInt128();
}
function castInt256ToInt128(int256 value) public pure returns (int128) {
return value.toInt128();
}
}
{
"compilationTarget": {
"contracts/veYMT.sol": "veYMT"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 1000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"tokenAddr_","type":"address"}],"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":"uint128","name":"_type","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"ts","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"prevSupply","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"supply","type":"uint256"}],"name":"Supply","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":[],"name":"MULTIPLIER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WEEK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr_","type":"address"},{"internalType":"uint256","name":"t_","type":"uint256"}],"name":"balanceOf","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":"int256","name":"value","type":"int256"}],"name":"castInt256ToInt128","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"castUint256ToInt128","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"checkpoint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value_","type":"uint256"},{"internalType":"uint256","name":"unlockTime_","type":"uint256"}],"name":"createLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr_","type":"address"},{"internalType":"uint256","name":"value_","type":"uint256"}],"name":"depositFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"depositForAllAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"depositForAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"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":"getLastUserSlope","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"value_","type":"uint256"}],"name":"increaseAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"unlockTime_","type":"uint256"}],"name":"increaseUnlockTime","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":"lockedEnd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"pointHistory","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":"uint256","name":"","type":"uint256"}],"name":"slopeChanges","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"supply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"toggleDepositForAllApproval","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"depositor_","type":"address"}],"name":"toggleDepositForApproval","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"t_","type":"uint256"}],"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":[],"name":"transfersEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userPointEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"userPointHistory","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":"userPointHistoryTs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]