VoteEscrow
Description:
This contract implements the core logic for a Voting Escrow system, based on the original Curve Finance design. Users lock an underlying ERC20 token (token
) for a specified duration (up to 4 years) to receive a non-fungible token (NFT, veNFT
) representing their lock. The voting power associated with the NFT is time-weighted, decreasing linearly towards zero as the lock expiration approaches. This contract manages the creation, modification (increasing amount or duration), merging, and withdrawal of locks. It calculates and provides historical voting power for individual NFTs and the total supply. It conforms to ERC721 and ERC165 standards and includes metadata generation for the NFTs. It interacts with a Voter
contract to track voting status and attachments, an optional IIncentivesManager
for potential features like early unlock conditions, and a IUserRegistry
to record minting events.
Constructor
Description:
Initializes the VoteEscrow
contract upon deployment. Sets the underlying ERC20 token address (token_addr
), the address of the UserRegistry
contract (user_registry
), and sets the deployer (msg.sender
) as the initial voter
address (which typically controls voting/attachment status updates). It also initializes the first global checkpoint (epoch 0) and sets up support for ERC165/ERC721 interfaces.
Input Parameters:
token_addr
address
The address of the underlying ERC20 token to be locked.
user_registry
address
The address of the IUserRegistry
contract for mint tracking.
supportsInterface
Description:
Standard ERC165 function to check if the contract implements a specific interface ID. Returns true
for ERC165 (0x01ffc9a7
), ERC721 (0x80ac58cd
), and ERC721Metadata (0x5b5e139f
).
Input Parameters:
_interfaceID
bytes4
The interface identifier to check for.
Return Value:
bool
true
if the interface is supported, false
otherwise.
get_last_user_slope
Description:
Retrieves the most recently recorded rate of voting power decay (slope) for a specific veNFT (_tokenId
). The slope represents how much voting power the lock loses per second.
Input Parameters:
_tokenId
uint
The ID of the veNFT lock to query.
Return Value:
int128
The last recorded slope (rate of decay) for the specified NFT lock.
user_point_history__ts
Description:
Retrieves the timestamp (ts
) of a specific historical checkpoint (_idx
) for a given user's veNFT lock (_tokenId
).
Input Parameters:
_tokenId
uint
The ID of the veNFT lock to query.
_idx
uint
The index of the user's checkpoint history.
Return Value:
uint
The Unix timestamp of the specified user checkpoint.
locked__end
Description:
Retrieves the Unix timestamp when the lock for the specified veNFT (_tokenId
) expires.
Input Parameters:
_tokenId
uint
The ID of the veNFT lock to query.
Return Value:
uint
The timestamp when the lock expires.
balanceOf (ERC721)
Description:
Standard ERC721 function. Returns the number of veNFTs owned by a specific address (_owner
).
Input Parameters:
_owner
address
The address to query the balance for.
Return Value:
uint
The number of veNFTs owned by _owner
.
ownerOf
Description:
Standard ERC721 function. Returns the owner address of the specified veNFT (_tokenId
).
Input Parameters:
_tokenId
uint
The ID of the veNFT to query.
Return Value:
address
The owner address of the _tokenId
.
getApproved
Description:
Standard ERC721 function. Returns the address that is approved to manage the specified veNFT (_tokenId
), or the zero address if none is set.
Input Parameters:
_tokenId
uint
The ID of the veNFT to query approval for.
Return Value:
address
The approved address for the _tokenId
.
isApprovedForAll
Description:
Standard ERC721 function. Checks if an _operator
address is approved to manage all veNFTs owned by _owner
.
Input Parameters:
_owner
address
The address of the NFT owner.
_operator
address
The address of the potential operator.
Return Value:
bool
true
if _operator
is approved for all of _owner
's NFTs.
tokenOfOwnerByIndex
Description:
Optional ERC721Enumerable function. Returns the token ID of the veNFT at a specific index (_tokenIndex
) within the list of NFTs owned by _owner
.
Input Parameters:
_owner
address
The address of the NFT owner.
_tokenIndex
uint
The index of the token in the owner's list (0-based).
Return Value:
uint
The token ID at the specified index for the owner.
isApprovedOrOwner
Description:
Helper function to check if an address (_spender
) is either the owner of, approved for, or an approved operator for the owner of a specific veNFT (_tokenId
).
Input Parameters:
_spender
address
The address to check permissions for.
_tokenId
uint
The ID of the veNFT.
Return Value:
bool
true
if _spender
has control permissions over the _tokenId
.
transferFrom
Description:
Standard ERC721 function. Transfers ownership of a veNFT (_tokenId
) from _from
to _to
. Requires the caller (msg.sender
) to be the owner, approved, or an operator. The NFT cannot be transferred if it is attached or marked as voted (attachments[_tokenId] != 0 || voted[_tokenId]
).
Input Parameters:
_from
address
The current owner's address.
_to
address
The recipient's address.
_tokenId
uint
The ID of the veNFT to transfer.
Events Emitted:
Transfer(address indexed from, address indexed to, uint indexed tokenId)
safeTransferFrom (without data)
Description:
Standard ERC721 safe transfer function. Transfers ownership of a veNFT (_tokenId
) from _from
to _to
. Performs the same checks as transferFrom
and additionally checks if _to
is a contract; if so, it requires _to
to implement onERC721Received
. The NFT cannot be transferred if it is attached or marked as voted.
Input Parameters:
_from
address
The current owner's address.
_to
address
The recipient's address.
_tokenId
uint
The ID of the veNFT to transfer.
Events Emitted:
Transfer(address indexed from, address indexed to, uint indexed tokenId)
safeTransferFrom (with data)
Description:
Standard ERC721 safe transfer function with additional data. Transfers ownership of a veNFT (_tokenId
) from _from
to _to
, passing _data
to the recipient if it is a contract implementing onERC721Received
. Performs the same checks as the other transfer functions. The NFT cannot be transferred if it is attached or marked as voted.
Input Parameters:
_from
address
The current owner's address.
_to
address
The recipient's address.
_tokenId
uint
The ID of the veNFT to transfer.
_data
bytes
Additional data to send to the recipient.
Events Emitted:
Transfer(address indexed from, address indexed to, uint indexed tokenId)
approve
Description:
Standard ERC721 function. Grants approval to another address (_approved
) to manage a specific veNFT (_tokenId
). Requires the caller (msg.sender
) to be the owner or an approved operator for the owner. Setting _approved
to the zero address removes approval.
Input Parameters:
_approved
address
The address to grant approval to.
_tokenId
uint
The ID of the veNFT to approve.
Events Emitted:
Approval(address indexed owner, address indexed approved, uint indexed tokenId)
setApprovalForAll
Description:
Standard ERC721 function. Grants or revokes approval for an _operator
address to manage all veNFTs owned by the caller (msg.sender
).
Input Parameters:
_operator
address
The address to set as an operator.
_approved
bool
true
to grant approval, false
to revoke it.
Events Emitted:
ApprovalForAll(address indexed owner, address indexed operator, bool approved)
setIncentivesManager
Description:
Allows the current voter
address to set or update the _incentivesManager
address.
Input Parameters:
incentivesManager
address
The address of the IIncentivesManager
contract.
setVoter
Description:
Allows the current voter
address to transfer the voter
role to a new address (_voter
). The voter
address is responsible for calling functions like voting
, abstain
, attach
, detach
.
Input Parameters:
_voter
address
The address of the new voter contract.
voting
Description:
Marks a specific veNFT (_tokenId
) as having participated in voting. This is typically called by the Voter
contract when a user casts votes. Prevents the NFT from being transferred, merged, or withdrawn while marked as voted.
Input Parameters:
_tokenId
uint
The ID of the veNFT that has voted.
abstain
Description:
Removes the 'voted' status from a specific veNFT (_tokenId
). This is typically called by the Voter
contract when votes are reset or changed. Allows the NFT to be transferred, merged, or withdrawn again (if not attached).
Input Parameters:
_tokenId
uint
The ID of the veNFT that is abstaining.
attach
Description:
Increments the attachment counter for a veNFT (_tokenId
). Prevents transfer, merge, and withdrawal while attached (attachments > 0
).
Input Parameters:
_tokenId
uint
The ID of the veNFT being attached.
detach
Description:
Decrements the attachment counter for a veNFT (_tokenId
). Called when the NFT is unstaked or no longer needs to be attached. Allows transfer, merge, or withdrawal once the counter reaches zero (and voted
is false).
Input Parameters:
_tokenId
uint
The ID of the veNFT being detached.
merge
Description:
Merges the lock (amount
and end
time) from one veNFT (_from
) into another (_to
). The resulting lock in _to
will have the combined amount and the later of the two end times. The _from
NFT is burned. Requires the caller to own or be approved for both NFTs, and the _from
NFT must not be attached or voted.
Input Parameters:
_from
uint
The ID of the veNFT to merge from (will be burned).
_to
uint
The ID of the veNFT to merge into.
Events Emitted:
Deposit
, Supply
, Transfer
(for burn).
block_number
Description:
Returns the current block number.
Return Value:
uint
The current block number.
checkpoint
Description:
Triggers a global checkpoint, updating the global epoch and point history based on the current block time and any scheduled slope changes. Does not require or affect a specific user NFT.
deposit_for
Description:
Adds more underlying tokens (_value
) to an existing, non-expired lock (_tokenId
) without changing its unlock time. Can be called by anyone.
Input Parameters:
_tokenId
uint
The ID of the veNFT lock to add to.
_value
uint
The amount of underlying token to add.
Events Emitted:
Deposit
, Supply
.
create_lock_for
Description:
Creates a new lock with the specified _value
and _lock_duration
for a designated recipient address _to
. Mints a new veNFT and transfers the underlying tokens from the caller (msg.sender
).
Input Parameters:
_value
uint
The amount of underlying token to lock.
_lock_duration
uint
The duration in seconds for which to lock the tokens (max 4 years).
_to
address
The address that will own the new veNFT lock.
Return Value:
uint
The token ID of the newly created veNFT.
Events Emitted:
Transfer
(mint), Deposit
, Supply
.
create_lock
Description:
Creates a new lock with the specified _value
and _lock_duration
for the caller (msg.sender
). Mints a new veNFT owned by the caller and transfers the underlying tokens from the caller.
Input Parameters:
_value
uint
The amount of underlying token to lock.
_lock_duration
uint
The duration in seconds for which to lock the tokens (max 4 years).
Return Value:
uint
The token ID of the newly created veNFT.
Events Emitted:
Transfer
(mint), Deposit
, Supply
.
increase_amount
Description:
Adds more underlying tokens (_value
) to an existing, non-expired lock (_tokenId
) without changing its unlock time. Requires the caller (msg.sender
) to be the owner or approved for the _tokenId
.
Input Parameters:
_tokenId
uint
The ID of the veNFT lock to add to.
_value
uint
The amount of underlying token to add.
Events Emitted:
Deposit
, Supply
.
increase_unlock_time
Description:
Extends the unlock time of an existing, non-expired lock (_tokenId
). The new unlock time is calculated based on the current block time plus _lock_duration
(rounded down to the nearest week), up to the maximum lock time (4 years from now). Requires the caller (msg.sender
) to be the owner or approved for the _tokenId
.
Input Parameters:
_tokenId
uint
The ID of the veNFT lock to extend.
_lock_duration
uint
The duration in seconds to extend the lock from the current time (max 4 years).
Events Emitted:
Deposit
.
withdraw
Description:
Allows the owner or approved address to withdraw underlying tokens associated with a veNFT (_tokenId
). Requires the NFT to not be attached or voted. Two scenarios:
Expired Lock: If
block.timestamp >= lock.end
, the entire remaininglocked.amount
is withdrawn, and the veNFT is burned._amount
parameter is ignored in this case (implicitly withdraws all).Early Unlock: If the lock is active (
block.timestamp < lock.end
) AND early unlock conditions are met (current incentive rate from_incentivesManager
is lower than the rate at which the user locked, and there's a global excess of locked tokens above the new cap), the user can withdraw up to_amount
tokens, provided_amount
does not exceed their share of the global excess or their total locked amount. The lock amount is reduced, but the NFT remains (unless_amount
equals the total locked amount, in which case it's burned).
Input Parameters:
_tokenId
uint
The ID of the veNFT lock from which to withdraw.
_amount
uint
The amount to attempt to withdraw during an early unlock. Ignored if the lock has expired (full withdrawal occurs).
Events Emitted:
Withdraw
, Supply
, Transfer
(burn, if applicable).
calculateExcessLockedTokens
Description:
Calculates the global amount of tokens locked in the system that exceeds the dynamically calculated cap, based on a provided historical LockInRate
(presumably the rate when a user initially locked) and the current rate/cap from the _incentivesManager
. This value represents the total pool of tokens available for all eligible users to withdraw early. Returns 0 if the current rate is not lower than LockInRate
or if the total locked supply is within the allowed cap.
Input Parameters:
LockInRate
uint256
The incentive rate present when a specific lock was created/last updated.
Return Value:
uint256
The total amount of excess locked tokens currently available for early withdrawal globally.
tokenURI
Description:
Standard ERC721Metadata function. Returns the Uniform Resource Identifier (URI) for a given veNFT (_tokenId
). The URI contains JSON metadata including the NFT's name, description, and an embedded SVG image displaying its ID, current voting balance, lock end time, and original locked value.
Input Parameters:
_tokenId
uint
The ID of the veNFT to get the URI for.
Return Value:
string
The data URI containing the JSON metadata.
balanceOfNFT
Description:
Calculates the current voting power (ve balance) of a specific veNFT (_tokenId
) at the current block timestamp. Returns 0 if the NFT was transferred in the current block (flash loan protection).
Input Parameters:
_tokenId
uint
The ID of the veNFT lock to query.
Return Value:
uint
The current voting power of the _tokenId
.
balanceOfNFTAt
Description:
Calculates the voting power (ve balance) of a specific veNFT (_tokenId
) at a given past timestamp (_t
).
Input Parameters:
_tokenId
uint
The ID of the veNFT lock to query.
_t
uint
The past timestamp to calculate balance at.
Return Value:
uint
The voting power of the _tokenId
at time _t
.
balanceOfAtNFT
Description:
Calculates the voting power (ve balance) of a specific veNFT (_tokenId
) at a given past block number (_block
). It estimates the timestamp corresponding to the block number using historical checkpoints.
Input Parameters:
_tokenId
uint
The ID of the veNFT lock to query.
_block
uint
The past block number to calculate balance at.
Return Value:
uint
The voting power of the _tokenId
at block _block
.
totalSupplyAtT
Description:
Calculates the total voting power (total ve supply) across all locks in the contract at a specific timestamp t
.
Input Parameters:
t
uint
The timestamp to calculate supply at.
Return Value:
uint
The total voting power at time t
.
totalSupply
Description:
Calculates the current total voting power (total ve supply) across all locks at the current block timestamp.
Return Value:
uint
The current total voting power.
totalSupplyAt
Description:
Calculates the total voting power (total ve supply) across all locks at a specific past block number (_block
). Estimates the timestamp corresponding to the block number.
Input Parameters:
_block
uint
The past block number to calculate supply at.
Return Value:
uint
The total voting power at block _block
.
Last updated