// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
/**
@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` (4 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 (4 years?)
*/
/// [MIT License]
/// @title Base64
/// @notice Provides a function for encoding some bytes in base64
/// @author Brecht Devos <brecht@loopring.org>
library Base64 {
bytes internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/// @notice Encodes some bytes to the base64 representation
function encode(bytes memory data) internal pure returns (string memory) {
uint len = data.length;
if (len == 0) return "";
// multiply by 4/3 rounded up
uint encodedLen = 4 * ((len + 2) / 3);
// Add some extra buffer at the end
bytes memory result = new bytes(encodedLen + 32);
bytes memory table = TABLE;
assembly {
let tablePtr := add(table, 1)
let resultPtr := add(result, 32)
for {
let i := 0
} lt(i, len) {
} {
i := add(i, 3)
let input := and(mload(add(data, i)), 0xffffff)
let out := mload(add(tablePtr, and(shr(18, input), 0x3F)))
out := shl(8, out)
out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF))
out := shl(8, out)
out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF))
out := shl(8, out)
out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF))
out := shl(224, out)
mstore(resultPtr, out)
resultPtr := add(resultPtr, 4)
}
switch mod(len, 3)
case 1 {
mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
}
case 2 {
mstore(sub(resultPtr, 1), shl(248, 0x3d))
}
mstore(result, encodedLen)
}
return string(result);
}
}
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint tokenId
) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint tokenId
) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint tokenId) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint tokenId) external view returns (address operator);
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool _approved) external;
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint tokenId,
bytes calldata data
) external;
}
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint tokenId,
bytes calldata data
) external returns (bytes4);
}
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint tokenId) external view returns (string memory);
}
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
}
struct Point {
int128 bias;
int128 slope; // # -dweight / dt
uint ts;
uint 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;
uint end;
}
contract ve is IERC721, IERC721Metadata {
enum DepositType {
DEPOSIT_FOR_TYPE,
CREATE_LOCK_TYPE,
INCREASE_LOCK_AMOUNT,
INCREASE_UNLOCK_TIME,
MERGE_TYPE
}
event Deposit(
address indexed provider,
uint tokenId,
uint value,
uint indexed locktime,
DepositType deposit_type,
uint ts
);
event Withdraw(address indexed provider, uint tokenId, uint value, uint ts);
event Supply(uint prevSupply, uint supply);
uint internal constant WEEK = 1 weeks;
uint internal constant MAXTIME = 4 * 365 * 86400;
int128 internal constant iMAXTIME = 4 * 365 * 86400;
uint internal constant MULTIPLIER = 1 ether;
address immutable public token;
uint public supply;
uint public nftSupply;
mapping(uint => LockedBalance) public locked;
mapping(uint => uint) public ownership_change;
uint public epoch;
mapping(uint => Point) public point_history; // epoch -> unsigned point
mapping(uint => Point[1000000000]) public user_point_history; // user -> Point[user_epoch]
mapping(uint => uint) public user_point_epoch;
mapping(uint => int128) public slope_changes; // time -> signed slope change
mapping(uint => uint) public attachments;
mapping(uint => bool) public voted;
address public voter;
string constant public name = "veMULTI NFT";
string constant public symbol = "veMULTI";
string constant public version = "1.0.0";
uint8 constant public decimals = 18;
/// @dev Current count of token
uint internal tokenId;
/// @dev Mapping from NFT ID to the address that owns it.
mapping(uint => address) internal idToOwner;
/// @dev Mapping from NFT ID to approved address.
mapping(uint => address) internal idToApprovals;
/// @dev Mapping from owner address to count of his tokens.
mapping(address => uint) internal ownerToNFTokenCount;
/// @dev Mapping from owner address to mapping of index to tokenIds
mapping(address => mapping(uint => uint)) internal ownerToNFTokenIdList;
/// @dev Mapping from NFT ID to index of owner
mapping(uint => uint) internal tokenToOwnerIndex;
/// @dev Mapping from owner address to mapping of operator addresses.
mapping(address => mapping(address => bool)) internal ownerToOperators;
/// @dev Mapping of interface id to bool about whether or not it's supported
mapping(bytes4 => bool) internal supportedInterfaces;
/// @dev ERC165 interface ID of ERC165
bytes4 internal constant ERC165_INTERFACE_ID = 0x01ffc9a7;
/// @dev ERC165 interface ID of ERC721
bytes4 internal constant ERC721_INTERFACE_ID = 0x80ac58cd;
/// @dev ERC165 interface ID of ERC721Metadata
bytes4 internal constant ERC721_METADATA_INTERFACE_ID = 0x5b5e139f;
/// @dev reentrancy guard
uint8 internal constant _not_entered = 1;
uint8 internal constant _entered = 2;
uint8 internal _entered_state = 1;
modifier nonreentrant() {
require(_entered_state == _not_entered);
_entered_state = _entered;
_;
_entered_state = _not_entered;
}
/// @notice Contract constructor
/// @param token_addr `ERC20CRV` token address
constructor(
address token_addr
) {
token = token_addr;
voter = msg.sender;
point_history[0].blk = block.number;
point_history[0].ts = block.timestamp;
supportedInterfaces[ERC165_INTERFACE_ID] = true;
supportedInterfaces[ERC721_INTERFACE_ID] = true;
supportedInterfaces[ERC721_METADATA_INTERFACE_ID] = true;
// mint-ish
emit Transfer(address(0), address(this), tokenId);
// burn-ish
emit Transfer(address(this), address(0), tokenId);
}
/// @dev Interface identification is specified in ERC-165.
/// @param _interfaceID Id of the interface
function supportsInterface(bytes4 _interfaceID) external view returns (bool) {
return supportedInterfaces[_interfaceID];
}
/// @notice Get the most recently recorded rate of voting power decrease for `_tokenId`
/// @param _tokenId token of the NFT
/// @return Value of the slope
function get_last_user_slope(uint _tokenId) external view returns (int128) {
uint uepoch = user_point_epoch[_tokenId];
return user_point_history[_tokenId][uepoch].slope;
}
/// @notice Get the timestamp for checkpoint `_idx` for `_tokenId`
/// @param _tokenId token of the NFT
/// @param _idx User epoch number
/// @return Epoch time of the checkpoint
function user_point_history__ts(uint _tokenId, uint _idx) external view returns (uint) {
return user_point_history[_tokenId][_idx].ts;
}
/// @notice Get timestamp when `_tokenId`'s lock finishes
/// @param _tokenId User NFT
/// @return Epoch time of the lock end
function locked__end(uint _tokenId) external view returns (uint) {
return locked[_tokenId].end;
}
/// @dev Returns the number of NFTs owned by `_owner`.
/// Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
/// @param _owner Address for whom to query the balance.
function _balance(address _owner) internal view returns (uint) {
return ownerToNFTokenCount[_owner];
}
/// @dev Returns the number of NFTs owned by `_owner`.
/// Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
/// @param _owner Address for whom to query the balance.
function balanceOf(address _owner) external view returns (uint) {
return _balance(_owner);
}
function totalNFTSupply() external view returns (uint) {
return nftSupply;
}
/// @dev Returns the address of the owner of the NFT.
/// @param _tokenId The identifier for an NFT.
function ownerOf(uint _tokenId) public view returns (address) {
address owner = idToOwner[_tokenId];
require(owner != address(0), "VE NFT: owner query for nonexistent token");
return owner;
}
/// @dev Get the approved address for a single NFT.
/// @param _tokenId ID of the NFT to query the approval of.
function getApproved(uint _tokenId) external view returns (address) {
return idToApprovals[_tokenId];
}
/// @dev Checks if `_operator` is an approved operator for `_owner`.
/// @param _owner The address that owns the NFTs.
/// @param _operator The address that acts on behalf of the owner.
function isApprovedForAll(address _owner, address _operator) external view returns (bool) {
return (ownerToOperators[_owner])[_operator];
}
/// @dev Get token by index
function tokenOfOwnerByIndex(address _owner, uint _tokenIndex) external view returns (uint) {
return ownerToNFTokenIdList[_owner][_tokenIndex];
}
/// @dev Returns whether the given spender can transfer a given token ID
/// @param _spender address of the spender to query
/// @param _tokenId uint ID of the token to be transferred
/// @return bool whether the msg.sender is approved for the given token ID, is an operator of the owner, or is the owner of the token
function _isApprovedOrOwner(address _spender, uint _tokenId) internal view returns (bool) {
address owner = idToOwner[_tokenId];
bool spenderIsOwner = owner == _spender;
bool spenderIsApproved = _spender == idToApprovals[_tokenId];
bool spenderIsApprovedForAll = (ownerToOperators[owner])[_spender];
return spenderIsOwner || spenderIsApproved || spenderIsApprovedForAll;
}
function isApprovedOrOwner(address _spender, uint _tokenId) external view returns (bool) {
return _isApprovedOrOwner(_spender, _tokenId);
}
/// @dev Add a NFT to an index mapping to a given address
/// @param _to address of the receiver
/// @param _tokenId uint ID Of the token to be added
function _addTokenToOwnerList(address _to, uint _tokenId) internal {
uint current_count = _balance(_to);
ownerToNFTokenIdList[_to][current_count] = _tokenId;
tokenToOwnerIndex[_tokenId] = current_count;
}
/// @dev Remove a NFT from an index mapping to a given address
/// @param _from address of the sender
/// @param _tokenId uint ID Of the token to be removed
function _removeTokenFromOwnerList(address _from, uint _tokenId) internal {
// Delete
uint current_count = _balance(_from)-1;
uint current_index = tokenToOwnerIndex[_tokenId];
if (current_count == current_index) {
// update ownerToNFTokenIdList
ownerToNFTokenIdList[_from][current_count] = 0;
// update tokenToOwnerIndex
tokenToOwnerIndex[_tokenId] = 0;
} else {
uint lastTokenId = ownerToNFTokenIdList[_from][current_count];
// Add
// update ownerToNFTokenIdList
ownerToNFTokenIdList[_from][current_index] = lastTokenId;
// update tokenToOwnerIndex
tokenToOwnerIndex[lastTokenId] = current_index;
// Delete
// update ownerToNFTokenIdList
ownerToNFTokenIdList[_from][current_count] = 0;
// update tokenToOwnerIndex
tokenToOwnerIndex[_tokenId] = 0;
}
}
/// @dev Add a NFT to a given address
/// Throws if `_tokenId` is owned by someone.
function _addTokenTo(address _to, uint _tokenId) internal {
// Throws if `_tokenId` is owned by someone
assert(idToOwner[_tokenId] == address(0));
// Change the owner
idToOwner[_tokenId] = _to;
// Update owner token index tracking
_addTokenToOwnerList(_to, _tokenId);
// Change count tracking
ownerToNFTokenCount[_to] += 1;
}
/// @dev Remove a NFT from a given address
/// Throws if `_from` is not the current owner.
function _removeTokenFrom(address _from, uint _tokenId) internal {
// Throws if `_from` is not the current owner
assert(idToOwner[_tokenId] == _from);
// Change the owner
idToOwner[_tokenId] = address(0);
// Update owner token index tracking
_removeTokenFromOwnerList(_from, _tokenId);
// Change count tracking
ownerToNFTokenCount[_from] -= 1;
}
/// @dev Clear an approval of a given address
/// Throws if `_owner` is not the current owner.
function _clearApproval(address _owner, uint _tokenId) internal {
// Throws if `_owner` is not the current owner
assert(idToOwner[_tokenId] == _owner);
if (idToApprovals[_tokenId] != address(0)) {
// Reset approvals
idToApprovals[_tokenId] = address(0);
}
}
/// @dev Exeute transfer of a NFT.
/// Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
/// address for this NFT. (NOTE: `msg.sender` not allowed in internal function so pass `_sender`.)
/// Throws if `_to` is the zero address.
/// Throws if `_from` is not the current owner.
/// Throws if `_tokenId` is not a valid NFT.
function _transferFrom(
address _from,
address _to,
uint _tokenId,
address _sender
) internal {
require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
// Check requirements
require(_isApprovedOrOwner(_sender, _tokenId));
// Clear approval. Throws if `_from` is not the current owner
_clearApproval(_from, _tokenId);
// Remove NFT. Throws if `_tokenId` is not a valid NFT
_removeTokenFrom(_from, _tokenId);
// Add NFT
_addTokenTo(_to, _tokenId);
// Set the block of ownership transfer (for Flash NFT protection)
ownership_change[_tokenId] = block.number;
// Log the transfer
emit Transfer(_from, _to, _tokenId);
}
/* TRANSFER FUNCTIONS */
/// @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this NFT.
/// Throws if `_from` is not the current owner.
/// Throws if `_to` is the zero address.
/// Throws if `_tokenId` is not a valid NFT.
/// @notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else
/// they maybe be permanently lost.
/// @param _from The current owner of the NFT.
/// @param _to The new owner.
/// @param _tokenId The NFT to transfer.
function transferFrom(
address _from,
address _to,
uint _tokenId
) external {
_transferFrom(_from, _to, _tokenId, msg.sender);
}
function _isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
/// @dev Transfers the ownership of an NFT from one address to another address.
/// Throws unless `msg.sender` is the current owner, an authorized operator, or the
/// approved address for this NFT.
/// Throws if `_from` is not the current owner.
/// Throws if `_to` is the zero address.
/// Throws if `_tokenId` is not a valid NFT.
/// If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
/// the return value is not `bytes4(keccak256("onERC721Received(address,address,uint,bytes)"))`.
/// @param _from The current owner of the NFT.
/// @param _to The new owner.
/// @param _tokenId The NFT to transfer.
/// @param _data Additional data with no specified format, sent in call to `_to`.
function safeTransferFrom(
address _from,
address _to,
uint _tokenId,
bytes memory _data
) public {
_transferFrom(_from, _to, _tokenId, msg.sender);
if (_isContract(_to)) {
// Throws if transfer destination is a contract which does not implement 'onERC721Received'
try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4 retval) {
require(retval == IERC721Receiver.onERC721Received.selector, "ERC721: transfer to non ERC721Receiver implementer");
} catch (
bytes memory reason
) {
if (reason.length == 0) {
revert('ERC721: transfer to non ERC721Receiver implementer');
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
}
/// @dev Transfers the ownership of an NFT from one address to another address.
/// Throws unless `msg.sender` is the current owner, an authorized operator, or the
/// approved address for this NFT.
/// Throws if `_from` is not the current owner.
/// Throws if `_to` is the zero address.
/// Throws if `_tokenId` is not a valid NFT.
/// If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
/// the return value is not `bytes4(keccak256("onERC721Received(address,address,uint,bytes)"))`.
/// @param _from The current owner of the NFT.
/// @param _to The new owner.
/// @param _tokenId The NFT to transfer.
function safeTransferFrom(
address _from,
address _to,
uint _tokenId
) external {
safeTransferFrom(_from, _to, _tokenId, '');
}
/// @dev Set or reaffirm the approved address for an NFT. The zero address indicates there is no approved address.
/// Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.
/// Throws if `_tokenId` is not a valid NFT. (NOTE: This is not written the EIP)
/// Throws if `_approved` is the current owner. (NOTE: This is not written the EIP)
/// @param _approved Address to be approved for the given NFT ID.
/// @param _tokenId ID of the token to be approved.
function approve(address _approved, uint _tokenId) public {
address owner = idToOwner[_tokenId];
// Throws if `_tokenId` is not a valid NFT
require(owner != address(0));
// Throws if `_approved` is the current owner
require(_approved != owner);
// Check requirements
bool senderIsOwner = (idToOwner[_tokenId] == msg.sender);
bool senderIsApprovedForAll = (ownerToOperators[owner])[msg.sender];
require(senderIsOwner || senderIsApprovedForAll);
// Set the approval
idToApprovals[_tokenId] = _approved;
emit Approval(owner, _approved, _tokenId);
}
/// @dev Enables or disables approval for a third party ("operator") to manage all of
/// `msg.sender`'s assets. It also emits the ApprovalForAll event.
/// Throws if `_operator` is the `msg.sender`. (NOTE: This is not written the EIP)
/// @notice This works even if sender doesn't own any tokens at the time.
/// @param _operator Address to add to the set of authorized operators.
/// @param _approved True if the operators is approved, false to revoke approval.
function setApprovalForAll(address _operator, bool _approved) external {
// Throws if `_operator` is the `msg.sender`
assert(_operator != msg.sender);
ownerToOperators[msg.sender][_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
/// @dev Function to mint tokens
/// Throws if `_to` is zero address.
/// Throws if `_tokenId` is owned by someone.
/// @param _to The address that will receive the minted tokens.
/// @param _tokenId The token id to mint.
/// @return A boolean that indicates if the operation was successful.
function _mint(address _to, uint _tokenId) internal returns (bool) {
// Throws if `_to` is zero address
assert(_to != address(0));
// Add NFT. Throws if `_tokenId` is owned by someone
_addTokenTo(_to, _tokenId);
nftSupply++;
emit Transfer(address(0), _to, _tokenId);
return true;
}
/// @notice Record global and per-user data to checkpoint
/// @param _tokenId NFT token ID. No user checkpoint if 0
/// @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(
uint _tokenId,
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 (_tokenId != 0) {
// 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(int256(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(int256(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
Point memory initial_last_point = last_point;
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(int256(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 (_tokenId != 0) {
// 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 (_tokenId != 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 (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
uint user_epoch = user_point_epoch[_tokenId] + 1;
user_point_epoch[_tokenId] = user_epoch;
u_new.ts = block.timestamp;
u_new.blk = block.number;
user_point_history[_tokenId][user_epoch] = u_new;
}
}
/// @notice Deposit and lock tokens for a user
/// @param _tokenId NFT that holds lock
/// @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(
uint _tokenId,
uint _value,
uint unlock_time,
LockedBalance memory locked_balance,
DepositType deposit_type
) internal {
LockedBalance memory _locked = locked_balance;
uint supply_before = supply;
supply = supply_before + _value;
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(int256(_value));
if (unlock_time != 0) {
_locked.end = unlock_time;
}
locked[_tokenId] = _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(_tokenId, old_locked, _locked);
address from = msg.sender;
if (_value != 0 && deposit_type != DepositType.MERGE_TYPE) {
assert(IERC20(token).transferFrom(from, address(this), _value));
}
emit Deposit(from, _tokenId, _value, _locked.end, deposit_type, block.timestamp);
emit Supply(supply_before, supply_before + _value);
}
function setVoter(address _voter) external {
require(msg.sender == voter);
voter = _voter;
}
function voting(uint _tokenId) external {
require(msg.sender == voter);
voted[_tokenId] = true;
}
function abstain(uint _tokenId) external {
require(msg.sender == voter);
voted[_tokenId] = false;
}
function attach(uint _tokenId) external {
require(msg.sender == voter);
attachments[_tokenId] = attachments[_tokenId]+1;
}
function detach(uint _tokenId) external {
require(msg.sender == voter);
attachments[_tokenId] = attachments[_tokenId]-1;
}
function merge(uint _from, uint _to) external {
require(attachments[_from] == 0 && !voted[_from], "attached");
require(_from != _to);
require(_isApprovedOrOwner(msg.sender, _from));
require(_isApprovedOrOwner(msg.sender, _to));
LockedBalance memory _locked0 = locked[_from];
LockedBalance memory _locked1 = locked[_to];
uint value0 = uint(int256(_locked0.amount));
uint end = _locked0.end >= _locked1.end ? _locked0.end : _locked1.end;
locked[_from] = LockedBalance(0, 0);
_checkpoint(_from, _locked0, LockedBalance(0, 0));
_burn(_from);
_deposit_for(_to, value0, end, _locked1, DepositType.MERGE_TYPE);
}
function block_number() external view returns (uint) {
return block.number;
}
/// @notice Record global data to checkpoint
function checkpoint() external {
_checkpoint(0, LockedBalance(0, 0), LockedBalance(0, 0));
}
/// @notice Deposit `_value` tokens for `_tokenId` 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 _tokenId lock NFT
/// @param _value Amount to add to user's lock
function deposit_for(uint _tokenId, uint _value) external nonreentrant {
LockedBalance memory _locked = locked[_tokenId];
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(_tokenId, _value, 0, _locked, DepositType.DEPOSIT_FOR_TYPE);
}
/// @notice Deposit `_value` tokens for `_to` and lock for `_lock_duration`
/// @param _value Amount to deposit
/// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week)
/// @param _to Address to deposit
function _create_lock(uint _value, uint _lock_duration, address _to) internal returns (uint) {
uint unlock_time = (block.timestamp + _lock_duration) / WEEK * WEEK; // Locktime is rounded down to weeks
require(_value > 0); // dev: need non-zero value
require(unlock_time > block.timestamp, 'Can only lock until time in the future');
require(unlock_time <= block.timestamp + MAXTIME, 'Voting lock can be 4 years max');
++tokenId;
uint _tokenId = tokenId;
_mint(_to, _tokenId);
_deposit_for(_tokenId, _value, unlock_time, locked[_tokenId], DepositType.CREATE_LOCK_TYPE);
return _tokenId;
}
/// @notice Deposit `_value` tokens for `_to` and lock for `_lock_duration`
/// @param _value Amount to deposit
/// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week)
/// @param _to Address to deposit
function create_lock_for(uint _value, uint _lock_duration, address _to) external nonreentrant returns (uint) {
return _create_lock(_value, _lock_duration, _to);
}
/// @notice Deposit `_value` tokens for `msg.sender` and lock for `_lock_duration`
/// @param _value Amount to deposit
/// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week)
function create_lock(uint _value, uint _lock_duration) external nonreentrant returns (uint) {
return _create_lock(_value, _lock_duration, msg.sender);
}
/// @notice Deposit `_value` additional tokens for `_tokenId` without modifying the unlock time
/// @param _value Amount of tokens to deposit and add to the lock
function increase_amount(uint _tokenId, uint _value) external nonreentrant {
assert(_isApprovedOrOwner(msg.sender, _tokenId));
LockedBalance memory _locked = locked[_tokenId];
assert(_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(_tokenId, _value, 0, _locked, DepositType.INCREASE_LOCK_AMOUNT);
}
/// @notice Extend the unlock time for `_tokenId`
/// @param _lock_duration New number of seconds until tokens unlock
function increase_unlock_time(uint _tokenId, uint _lock_duration) external nonreentrant {
assert(_isApprovedOrOwner(msg.sender, _tokenId));
LockedBalance memory _locked = locked[_tokenId];
uint unlock_time = (block.timestamp + _lock_duration) / 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 4 years max');
_deposit_for(_tokenId, 0, unlock_time, _locked, DepositType.INCREASE_UNLOCK_TIME);
}
/// @notice Withdraw all tokens for `_tokenId`
/// @dev Only possible if the lock has expired
function withdraw(uint _tokenId) external nonreentrant {
assert(_isApprovedOrOwner(msg.sender, _tokenId));
require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
LockedBalance memory _locked = locked[_tokenId];
require(block.timestamp >= _locked.end, "The lock didn't expire");
uint value = uint(int256(_locked.amount));
locked[_tokenId] = LockedBalance(0,0);
uint supply_before = supply;
supply = supply_before - value;
// old_locked can have either expired <= timestamp or zero end
// _locked has only 0 end
// Both can have >= 0 amount
_checkpoint(_tokenId, _locked, LockedBalance(0,0));
address owner = ownerOf(_tokenId);
// Burn the NFT
_burn(_tokenId);
assert(IERC20(token).transfer(owner, value));
emit Withdraw(msg.sender, _tokenId, value, block.timestamp);
emit Supply(supply_before, supply_before - 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 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 `_tokenId`
/// @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
/// @param _tokenId NFT for lock
/// @param _t Epoch time to return voting power at
/// @return User voting power
function _balanceOfNFT(uint _tokenId, uint _t) internal view returns (uint) {
uint _epoch = user_point_epoch[_tokenId];
if (_epoch == 0) {
return 0;
} else {
Point memory last_point = user_point_history[_tokenId][_epoch];
last_point.bias -= last_point.slope * int128(int256(_t) - int256(last_point.ts));
if (last_point.bias < 0) {
last_point.bias = 0;
}
return uint(int256(last_point.bias));
}
}
/// @dev Returns current token URI metadata
/// @param _tokenId Token ID to fetch URI for.
function tokenURI(uint _tokenId) external view returns (string memory) {
require(idToOwner[_tokenId] != address(0), "Query for nonexistent token");
LockedBalance memory _locked = locked[_tokenId];
return
_tokenURI(
_tokenId,
_balanceOfNFT(_tokenId, block.timestamp),
_locked.end,
uint(int256(_locked.amount))
);
}
function balanceOfNFT(uint _tokenId) external view returns (uint) {
if (ownership_change[_tokenId] == block.number) return 0;
return _balanceOfNFT(_tokenId, block.timestamp);
}
function balanceOfNFTAt(uint _tokenId, uint _t) external view returns (uint) {
return _balanceOfNFT(_tokenId, _t);
}
/// @notice Measure voting power of `_tokenId` at block height `_block`
/// @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime
/// @param _tokenId User's wallet NFT
/// @param _block Block to calculate the voting power at
/// @return Voting power
function _balanceOfAtNFT(uint _tokenId, uint _block) internal view returns (uint) {
// Copying and pasting totalSupply code because Vyper cannot pass by
// reference yet
assert(_block <= block.number);
// Binary search
uint _min = 0;
uint _max = user_point_epoch[_tokenId];
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[_tokenId][_mid].blk <= _block) {
_min = _mid;
} else {
_max = _mid - 1;
}
}
Point memory upoint = user_point_history[_tokenId][_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(int256(block_time - upoint.ts));
if (upoint.bias >= 0) {
return uint(uint128(upoint.bias));
} else {
return 0;
}
}
function balanceOfAtNFT(uint _tokenId, uint _block) external view returns (uint) {
return _balanceOfAtNFT(_tokenId, _block);
}
/// @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(int256(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 totalSupplyAtT(uint t) public view returns (uint) {
uint _epoch = epoch;
Point memory last_point = point_history[_epoch];
return _supply_at(last_point, t);
}
function totalSupply() external view returns (uint) {
return totalSupplyAtT(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) {
assert(_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);
}
function _tokenURI(uint _tokenId, uint _balanceOf, uint _locked_end, uint _value) internal pure returns (string memory output) {
output = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: serif; font-size: 14px; }</style><rect width="100%" height="100%" fill="black" /><text x="10" y="20" class="base">';
output = string(abi.encodePacked(output, "token ", toString(_tokenId), '</text><text x="10" y="40" class="base">'));
output = string(abi.encodePacked(output, "balanceOf ", toString(_balanceOf), '</text><text x="10" y="60" class="base">'));
output = string(abi.encodePacked(output, "locked_end ", toString(_locked_end), '</text><text x="10" y="80" class="base">'));
output = string(abi.encodePacked(output, "value ", toString(_value), '</text></svg>'));
string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "lock #', toString(_tokenId), '", "description": "veMULTI NFT", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
output = string(abi.encodePacked('data:application/json;base64,', json));
}
function toString(uint value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT license
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint temp = value;
uint digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint(value % 10)));
value /= 10;
}
return string(buffer);
}
function _burn(uint _tokenId) internal {
require(_isApprovedOrOwner(msg.sender, _tokenId), "caller is not owner nor approved");
address owner = ownerOf(_tokenId);
// Clear approval
_clearApproval(owner, _tokenId);
// Remove token
_removeTokenFrom(owner, _tokenId);
nftSupply--;
emit Transfer(owner, address(0), _tokenId);
}
}
{
"compilationTarget": {
"ve.sol": "ve"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"token_addr","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"locktime","type":"uint256"},{"indexed":false,"internalType":"enum ve.DepositType","name":"deposit_type","type":"uint8"},{"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":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"ts","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"abstain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_approved","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"attach","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"attachments","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_block","type":"uint256"}],"name":"balanceOfAtNFT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"balanceOfNFT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_t","type":"uint256"}],"name":"balanceOfNFTAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"block_number","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"checkpoint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"_lock_duration","type":"uint256"}],"name":"create_lock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"_lock_duration","type":"uint256"},{"internalType":"address","name":"_to","type":"address"}],"name":"create_lock_for","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"deposit_for","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"detach","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"epoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"get_last_user_slope","outputs":[{"internalType":"int128","name":"","type":"int128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"increase_amount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_lock_duration","type":"uint256"}],"name":"increase_unlock_time","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"isApprovedOrOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"locked","outputs":[{"internalType":"int128","name":"amount","type":"int128"},{"internalType":"uint256","name":"end","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"locked__end","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_from","type":"uint256"},{"internalType":"uint256","name":"_to","type":"uint256"}],"name":"merge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nftSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"ownership_change","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_voter","type":"address"}],"name":"setVoter","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":"supply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_tokenIndex","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalNFTSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"user_point_epoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"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":"uint256","name":"_tokenId","type":"uint256"},{"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":"uint256","name":"","type":"uint256"}],"name":"voted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"voter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"voting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]