2024-03-12-Lynex-VotingEscrowV2Upgradeable-Version2-diff

Created Diff never expires
27 removals
620 lines
27 additions
617 lines
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
pragma solidity 0.8.13;


import {IERC721EnumerableUpgradeable, ERC721EnumerableUpgradeable, IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import {IERC721EnumerableUpgradeable, ERC721EnumerableUpgradeable, IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";


import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {ERC5725Upgradeable} from "./erc5725/ERC5725Upgradeable.sol";
import {ERC5725Upgradeable} from "./erc5725/ERC5725Upgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";


import {IVotingEscrowV2Upgradeable, IVotes} from "./interfaces/IVotingEscrowV2Upgradeable.sol";
import {IVotingEscrowV2Upgradeable, IVotes} from "./interfaces/IVotingEscrowV2Upgradeable.sol";
import {IVeArtProxy} from "../../interfaces/IVeArtProxy.sol";
import {IVeArtProxy} from "../../interfaces/IVeArtProxy.sol";
import {SafeCastLibrary} from "./libraries/SafeCastLibrary.sol";
import {SafeCastLibrary} from "./libraries/SafeCastLibrary.sol";
import {EscrowDelegateCheckpoints, Checkpoints} from "./libraries/EscrowDelegateCheckpoints.sol";
import {EscrowDelegateCheckpoints, Checkpoints} from "./libraries/EscrowDelegateCheckpoints.sol";
import {EscrowDelegateStorage} from "./libraries/EscrowDelegateStorage.sol";
import {EscrowDelegateStorage} from "./libraries/EscrowDelegateStorage.sol";


/**
/**
* @title VotingEscrow
* @title VotingEscrow
* @dev This contract is used for locking tokens and voting.
* @dev This contract is used for locking tokens and voting.
*
*
* - tokenIds always have a delegatee, with the owner being the default (see createLock)
* - tokenIds always have a delegatee, with the owner being the default (see createLock)
* - On transfers, delegation is reset. (See _update)
* - On transfers, delegation is reset. (See _update)
* -
* -
*/
*/
contract VotingEscrowV2Upgradeable is
contract VotingEscrowV2Upgradeable is
Initializable,
Initializable,
IVotingEscrowV2Upgradeable,
IVotingEscrowV2Upgradeable,
ERC5725Upgradeable,
ERC5725Upgradeable,
EscrowDelegateStorage,
EscrowDelegateStorage,
EIP712Upgradeable,
EIP712Upgradeable,
ReentrancyGuard
ReentrancyGuard
{
{
using SafeERC20Upgradeable for IERC20Upgradeable;
using SafeERC20Upgradeable for IERC20Upgradeable;
using SafeCastLibrary for uint256;
using SafeCastLibrary for uint256;
using EscrowDelegateCheckpoints for EscrowDelegateCheckpoints.EscrowDelegateStore;
using EscrowDelegateCheckpoints for EscrowDelegateCheckpoints.EscrowDelegateStore;


enum DepositType {
enum DepositType {
DEPOSIT_FOR_TYPE,
DEPOSIT_FOR_TYPE,
CREATE_LOCK_TYPE,
CREATE_LOCK_TYPE,
INCREASE_LOCK_AMOUNT,
INCREASE_LOCK_AMOUNT,
INCREASE_UNLOCK_TIME,
INCREASE_UNLOCK_TIME,
MERGE_TYPE,
MERGE_TYPE,
SPLIT_TYPE
SPLIT_TYPE
}
}


/// @notice The token being locked
/// @notice The token being locked
IERC20Upgradeable public _token;
IERC20Upgradeable public _token;
/// @notice Total locked supply
/// @notice Total locked supply
uint256 public supply;
uint256 public supply;
uint8 public constant decimals = 18;
uint8 public constant decimals = 18;
address public artProxy;
address public artProxy;


/// @notice The EIP-712 typehash for the delegation struct used by the contract
/// @notice The EIP-712 typehash for the delegation struct used by the contract
bytes32 public constant DELEGATION_TYPEHASH =
bytes32 public constant DELEGATION_TYPEHASH =
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
/// @notice A record of states for signing / validating signatures
/// @notice A record of states for signing / validating signatures
mapping(address => uint256) public nonces;
mapping(address => uint256) public nonces;


/// @dev OpenZeppelin v5 IVotes error
/// @dev OpenZeppelin v5 IVotes error
error VotesExpiredSignature(uint256 expiry);
error VotesExpiredSignature(uint256 expiry);


/**
/**
* @notice The constructor is disabled for this upgradeable contract.
* @notice The constructor is disabled for this upgradeable contract.
*/
*/
constructor() {
constructor() {
/// @dev Disable the initializers for implementation contracts to ensure that the contract is not left uninitialized.
/// @dev Disable the initializers for implementation contracts to ensure that the contract is not left uninitialized.
_disableInitializers();
_disableInitializers();
}
}


/**
/**
* @dev Initializes the contract with the given parameters.
* @dev Initializes the contract with the given parameters.
* @param _name The name to set for the token.
* @param _name The name to set for the token.
* @param _symbol The symbol to set for the token.
* @param _symbol The symbol to set for the token.
* @param version The version of the contract.
* @param version The version of the contract.
* @param mainToken The main token address that will be locked in the escrow.
* @param mainToken The main token address that will be locked in the escrow.
* @param _artProxy The address of the art proxy contract.
* @param _artProxy The address of the art proxy contract.
*/
*/
function initialize(
function initialize(
string memory _name,
string memory _name,
string memory _symbol,
string memory _symbol,
string memory version,
string memory version,
IERC20Upgradeable mainToken,
IERC20Upgradeable mainToken,
address _artProxy
address _artProxy
) public initializer {
) public initializer {
__ERC5725_init(_name, _symbol);
__ERC5725_init(_name, _symbol);
__EIP712_init(_name, version);
__EIP712_init(_name, version);
_token = mainToken;
_token = mainToken;
artProxy = _artProxy;
artProxy = _artProxy;
// Reset MAX_TIME in proxy storage
// Reset MAX_TIME in proxy storage
MAX_TIME = uint256(uint128(EscrowDelegateCheckpoints.MAX_TIME));
MAX_TIME = uint256(uint128(EscrowDelegateCheckpoints.MAX_TIME));
}
}


modifier checkAuthorized(uint256 _tokenId) {
modifier checkAuthorized(uint256 _tokenId) {
address owner = _ownerOf(_tokenId);
address owner = _ownerOf(_tokenId);
if (owner == address(0)) {
if (owner == address(0)) {
revert ERC721NonexistentToken(_tokenId);
revert ERC721NonexistentToken(_tokenId);
}
}
address sender = _msgSender();
address sender = _msgSender();
if (!_isAuthorized(owner, sender, _tokenId)) {
if (!_isAuthorized(owner, sender, _tokenId)) {
revert ERC721InsufficientApproval(sender, _tokenId);
revert ERC721InsufficientApproval(sender, _tokenId);
}
}
_;
_;
}
}


/// @dev Returns current token URI metadata
/// @dev Returns current token URI metadata
/// @param _tokenId Token ID to fetch URI for.
/// @param _tokenId Token ID to fetch URI for.
function tokenURI(uint _tokenId) public view override validToken(_tokenId) returns (string memory) {
function tokenURI(uint _tokenId) public view override validToken(_tokenId) returns (string memory) {
LockDetails memory _locked = _lockDetails[_tokenId];
LockDetails memory _locked = _lockDetails[_tokenId];
return
return
IVeArtProxy(artProxy)._tokenURI(
IVeArtProxy(artProxy)._tokenURI(
_tokenId,
_tokenId,
balanceOfNFT(_tokenId),
balanceOfNFT(_tokenId),
_locked.endTime,
_locked.endTime,
uint(int256(_locked.amount))
uint(int256(_locked.amount))
);
);
}
}


/**
/**
* @dev See {IERC165-supportsInterface}.
* @dev See {IERC165-supportsInterface}.
*/
*/
function supportsInterface(
function supportsInterface(
bytes4 interfaceId
bytes4 interfaceId
) public view virtual override(ERC5725Upgradeable, IERC165Upgradeable) returns (bool supported) {
) public view virtual override(ERC5725Upgradeable, IERC165Upgradeable) returns (bool supported) {
return interfaceId == type(IVotingEscrowV2Upgradeable).interfaceId || super.supportsInterface(interfaceId);
return interfaceId == type(IVotingEscrowV2Upgradeable).interfaceId || super.supportsInterface(interfaceId);
}
}


/**
/**
* @dev See {IERC721-_beforeTokenTransfer}.
* @dev See {IERC721-_beforeTokenTransfer}.
* Clears the approval of a given `tokenId` when the token is transferred or burned.
* Clears the approval of a given `tokenId` when the token is transferred or burned.
*/
*/
function _beforeTokenTransfer(
function _beforeTokenTransfer(
address from,
address from,
address to,
address to,
uint256 firstTokenId,
uint256 firstTokenId,
uint256 batchSize
uint256 batchSize
) internal virtual override {
) internal virtual override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
for (uint256 i = 0; i < batchSize; i++) {
for (uint256 i = 0; i < batchSize; i++) {
uint256 tokenId = firstTokenId + i;
uint256 tokenId = firstTokenId + i;
if (from != to) {
if (from != to) {
/// @dev Sets delegatee to new owner on transfers
/// @dev Sets delegatee to new owner on transfers
(address oldDelegatee, address newDelegatee) = edStore.delegate(
(address oldDelegatee, address newDelegatee) = edStore.delegate(
tokenId,
tokenId,
to,
to,
_lockDetails[tokenId].endTime
_lockDetails[tokenId].endTime
);
);
emit DelegateChanged(to, oldDelegatee, newDelegatee);
emit DelegateChanged(to, oldDelegatee, newDelegatee);
emit LockDelegateChanged(tokenId, to, oldDelegatee, newDelegatee);
emit LockDelegateChanged(tokenId, to, oldDelegatee, newDelegatee);
}
}
}
}
}
}


/**
/**
* ERC-5725 and token-locking logic
* ERC-5725 and token-locking logic
*/
*/


/// @notice maps the vesting data with tokenIds
/// @notice maps the vesting data with tokenIds
mapping(uint256 => LockDetails) public _lockDetails;
mapping(uint256 => LockDetails) public _lockDetails;


/// @notice tracker of current NFT id
/// @notice tracker of current NFT id
uint256 public totalNftsMinted = 0;
uint256 public totalNftsMinted = 0;


/**
/**
* @notice Creates a new vesting NFT and mints it
* @notice Creates a new vesting NFT and mints it
* @dev Token amount should be approved to be transferred by this contract before executing create
* @dev Token amount should be approved to be transferred by this contract before executing create
* @param value The total assets to be locked over time
* @param value The total assets to be locked over time
* @param duration Duration in seconds of the lock
* @param duration Duration in seconds of the lock
* @param to The receiver of the lock
* @param to The receiver of the lock
*/
*/
function _createLock(
function _createLock(
uint256 value,
uint256 value,
uint256 duration,
uint256 duration,
address to,
address to,
address delegatee,
address delegatee,
bool permanent,
bool permanent,
DepositType depositType
DepositType depositType
) internal virtual returns (uint256) {
) internal virtual returns (uint256) {
if (value == 0) revert ZeroAmount();
if (value == 0) revert ZeroAmount();
uint256 unlockTime;
uint256 unlockTime;
totalNftsMinted++;
totalNftsMinted++;
uint256 newTokenId = totalNftsMinted;
uint256 newTokenId = totalNftsMinted;
if (!permanent) {
if (!permanent) {
unlockTime = toGlobalClock(block.timestamp + duration); // Locktime is rounded down to global clock (days)
unlockTime = toGlobalClock(block.timestamp + duration); // Locktime is rounded down to global clock (days)
if (unlockTime <= block.timestamp) revert LockDurationNotInFuture();
if (unlockTime <= block.timestamp) revert LockDurationNotInFuture();
if (unlockTime > block.timestamp + MAX_TIME) revert LockDurationTooLong();
if (unlockTime > block.timestamp + MAX_TIME) revert LockDurationTooLong();
}
}


_safeMint(to, newTokenId);
_safeMint(to, newTokenId);
_lockDetails[newTokenId].startTime = block.timestamp;
_lockDetails[newTokenId].startTime = block.timestamp;
/// @dev Checkpoint created in _updateLock
/// @dev Checkpoint created in _updateLock
_updateLock(newTokenId, value, unlockTime, _lockDetails[newTokenId], permanent, depositType);
_updateLock(newTokenId, value, unlockTime, _lockDetails[newTokenId], permanent, depositType);
edStore.delegate(newTokenId, delegatee, unlockTime);
edStore.delegate(newTokenId, delegatee, unlockTime);
emit LockCreated(newTokenId, delegatee, value, unlockTime, permanent);
emit LockCreated(newTokenId, delegatee, value, unlockTime, permanent);
emit DelegateChanged(to, address(0), delegatee);
emit DelegateChanged(to, address(0), delegatee);
emit LockDelegateChanged(newTokenId, to, address(0), delegatee);
emit LockDelegateChanged(newTokenId, to, address(0), delegatee);
return newTokenId;
return newTokenId;
}
}


/**
/**
* @notice Creates a lock for the sender
* @notice Creates a lock for the sender
* @param _value The total assets to be locked over time
* @param _value The total assets to be locked over time
* @param _lockDuration Duration in seconds of the lock
* @param _lockDuration Duration in seconds of the lock
* @param _permanent Whether the lock is permanent or not
* @param _permanent Whether the lock is permanent or not
* @return The id of the newly created token
* @return The id of the newly created token
*/
*/
function createLock(
function createLock(
uint256 _value,
uint256 _value,
uint256 _lockDuration,
uint256 _lockDuration,
bool _permanent
bool _permanent
) external nonReentrant returns (uint256) {
) external nonReentrant returns (uint256) {
return _createLock(_value, _lockDuration, _msgSender(), _msgSender(), _permanent, DepositType.CREATE_LOCK_TYPE);
return _createLock(_value, _lockDuration, _msgSender(), _msgSender(), _permanent, DepositType.CREATE_LOCK_TYPE);
}
}


/**
/**
* @notice Creates a lock for a specified address
* @notice Creates a lock for a specified address
* @param _value The total assets to be locked over time
* @param _value The total assets to be locked over time
* @param _lockDuration Duration in seconds of the lock
* @param _lockDuration Duration in seconds of the lock
* @param _to The receiver of the lock
* @param _to The receiver of the lock
* @param _permanent Whether the lock is permanent or not
* @param _permanent Whether the lock is permanent or not
* @return The id of the newly created token
* @return The id of the newly created token
*/
*/
function createLockFor(
function createLockFor(
uint256 _value,
uint256 _value,
uint256 _lockDuration,
uint256 _lockDuration,
address _to,
address _to,
bool _permanent
bool _permanent
) external nonReentrant returns (uint256) {
) external nonReentrant returns (uint256) {
return _createLock(_value, _lockDuration, _to, _to, _permanent, DepositType.CREATE_LOCK_TYPE);
return _createLock(_value, _lockDuration, _to, _to, _permanent, DepositType.CREATE_LOCK_TYPE);
}
}


/**
/**
* @notice Creates a lock for a specified address
* @notice Creates a lock for a specified address
* @param _value The total assets to be locked over time
* @param _value The total assets to be locked over time
* @param _lockDuration Duration in seconds of the lock
* @param _lockDuration Duration in seconds of the lock
* @param _to The receiver of the lock
* @param _to The receiver of the lock
* @param _delegatee The receiver of the lock
* @param _delegatee The receiver of the lock
* @param _permanent Whether the lock is permanent or not
* @param _permanent Whether the lock is permanent or not
* @return The id of the newly created token
* @return The id of the newly created token
*/
*/
function createDelegatedLockFor(
function createDelegatedLockFor(
uint256 _value,
uint256 _value,
uint256 _lockDuration,
uint256 _lockDuration,
address _to,
address _to,
address _delegatee,
address _delegatee,
bool _permanent
bool _permanent
) external nonReentrant returns (uint256) {
) external nonReentrant returns (uint256) {
return _createLock(_value, _lockDuration, _to, _delegatee, _permanent, DepositType.CREATE_LOCK_TYPE);
return _createLock(_value, _lockDuration, _to, _delegatee, _permanent, DepositType.CREATE_LOCK_TYPE);
}
}


/**
/**
* @notice Updates the global checkpoint
* @notice Updates the global checkpoint
*/
*/
function globalCheckpoint() public nonReentrant {
function globalCheckpoint() public nonReentrant {
return edStore.globalCheckpoint();
return edStore.globalCheckpoint();
}
}


function checkpoint() external override {
function checkpoint() external override {
globalCheckpoint();
globalCheckpoint();
}
}


/**
/**
* @notice Updates the checkpoint for a delegatee
* @notice Updates the checkpoint for a delegatee
* @param _delegateeAddress The address of the delegatee
* @param _delegateeAddress The address of the delegatee
*/
*/
function checkpointDelegatee(address _delegateeAddress) external nonReentrant {
function checkpointDelegatee(address _delegateeAddress) external nonReentrant {
edStore.baseCheckpointDelegatee(_delegateeAddress);
edStore.baseCheckpointDelegatee(_delegateeAddress);
}
}


/// @notice Deposit & update lock tokens for a user
/// @notice Deposit & update lock tokens for a user
/// @dev The supply is increased by the _value amount
/// @dev The supply is increased by the _value amount
/// @param _tokenId NFT that holds lock
/// @param _tokenId NFT that holds lock
/// @param _increasedValue Amount to deposit
/// @param _increasedValue Amount to deposit
/// @param _unlockTime New time when to unlock the tokens, or 0 if unchanged
/// @param _unlockTime New time when to unlock the tokens, or 0 if unchanged
/// @param _oldLocked Previous locked amount / timestamp
/// @param _oldLocked Previous locked amount / timestamp
function _updateLock(
function _updateLock(
uint256 _tokenId,
uint256 _tokenId,
uint256 _increasedValue,
uint256 _increasedValue,
uint256 _unlockTime,
uint256 _unlockTime,
LockDetails memory _oldLocked,
LockDetails memory _oldLocked,
bool isPermanent,
bool isPermanent,
DepositType depositType
DepositType depositType
) internal {
) internal {
uint256 supplyBefore = supply;
uint256 supplyBefore = supply;
supply += _increasedValue;
supply += _increasedValue;


// Set newLocked to _oldLocked without mangling memory
// Set newLocked to _oldLocked without mangling memory
LockDetails memory newLocked;
LockDetails memory newLocked;
(newLocked.amount, newLocked.startTime, newLocked.endTime, newLocked.isPermanent) = (
(newLocked.amount, newLocked.startTime, newLocked.endTime, newLocked.isPermanent) = (
_oldLocked.amount,
_oldLocked.amount,
_oldLocked.startTime,
_oldLocked.startTime,
_oldLocked.endTime,
_oldLocked.endTime,
_oldLocked.isPermanent
_oldLocked.isPermanent
);
);


// Adding to existing lock, or if a lock is expired - creating a new one
// Adding to existing lock, or if a lock is expired - creating a new one
newLocked.amount += _increasedValue;
newLocked.amount += _increasedValue;
if (_unlockTime != 0 && !isPermanent) {
if (_unlockTime != 0 && !isPermanent) {
newLocked.endTime = _unlockTime;
newLocked.endTime = _unlockTime;
}
}
if (isPermanent) {
if (isPermanent) {
newLocked.endTime = 0;
newLocked.endTime = 0;
newLocked.isPermanent = true;
newLocked.isPermanent = true;
}
}
_lockDetails[_tokenId] = newLocked;
_lockDetails[_tokenId] = newLocked;
emit LockUpdated(_tokenId, _increasedValue, _unlockTime, isPermanent);
emit LockUpdated(_tokenId, _increasedValue, _unlockTime, isPermanent);


// Possibilities:
// Possibilities:
// Both _oldLocked.end could be current or expired (>/< block.timestamp)
// Both _oldLocked.end could be current or expired (>/< block.timestamp)
// or if the lock is a permanent lock, then _oldLocked.end == 0
// or if the lock is a permanent lock, then _oldLocked.end == 0
// value == 0 (extend lock) or value > 0 (add to lock or extend lock)
// value == 0 (extend lock) or value > 0 (add to lock or extend lock)
// newLocked.end > block.timestamp (always)
// newLocked.end > block.timestamp (always)
_checkpointLock(_tokenId, _oldLocked, newLocked);
_checkpointLock(_tokenId, _oldLocked, newLocked);


if (_increasedValue != 0 && depositType != DepositType.SPLIT_TYPE) {
if (_increasedValue != 0 && depositType != DepositType.SPLIT_TYPE) {
_token.safeTransferFrom(_msgSender(), address(this), _increasedValue);
_token.safeTransferFrom(_msgSender(), address(this), _increasedValue);
}
}


emit SupplyUpdated(supply, supplyBefore + _increasedValue);
emit SupplyUpdated(supply, supplyBefore + _increasedValue);
}
}


/// @notice Record global and per-user data to checkpoints. Used by VotingEscrow system.
/// @notice Record global and per-user data to checkpoints. Used by VotingEscrow system.
/// @param _tokenId NFT token ID. No user checkpoint if 0
/// @param _tokenId NFT token ID. No user checkpoint if 0
/// @param _oldLocked Previous locked amount / end lock time for the user
/// @param _oldLocked Previous locked amount / end lock time for the user
/// @param _newLocked New locked amount / end lock time for the user
/// @param _newLocked New locked amount / end lock time for the user
function _checkpointLock(
function _checkpointLock(
uint256 _tokenId,
uint256 _tokenId,
IVotingEscrowV2Upgradeable.LockDetails memory _oldLocked,
IVotingEscrowV2Upgradeable.LockDetails memory _oldLocked,
IVotingEscrowV2Upgradeable.LockDetails memory _newLocked
IVotingEscrowV2Upgradeable.LockDetails memory _newLocked
) internal {
) internal {
edStore.checkpoint(
edStore.checkpoint(
_tokenId,
_tokenId,
_oldLocked.amount.toInt128(),
_oldLocked.amount.toInt128(),
_newLocked.amount.toInt128(),
_newLocked.amount.toInt128(),
_oldLocked.endTime,
_oldLocked.endTime,
_newLocked.endTime
_newLocked.endTime
);
);
}
}


/// @notice Deposit `_value` tokens for `_tokenId` and add to the lock
/// @notice Deposit `_value` tokens for `_tokenId` and add to the lock
/// @dev Anyone (even a smart contract) can deposit for someone else, but
/// @dev Anyone (even a smart contract) can deposit for someone else, but
/// cannot extend their locktime and deposit for a brand new user
/// cannot extend their locktime and deposit for a brand new user
/// @param _tokenId lock NFT
/// @param _tokenId lock NFT
/// @param _value Amount to add to user's lock
/// @param _value Amount to add to user's lock
function increaseAmount(uint256 _tokenId, uint256 _value) external nonReentrant {
function increaseAmount(uint256 _tokenId, uint256 _value) external nonReentrant {
if (_value == 0) revert ZeroAmount();
if (_value == 0) revert ZeroAmount();


IVotingEscrowV2Upgradeable.LockDetails memory oldLocked = _lockDetails[_tokenId];
IVotingEscrowV2Upgradeable.LockDetails memory oldLocked = _lockDetails[_tokenId];
if (oldLocked.amount <= 0) revert NoLockFound();
if (_ownerOf(_tokenId) == address(0)) revert NoLockFound();
if (oldLocked.endTime <= block.timestamp && !oldLocked.isPermanent) revert LockExpired();
if (oldLocked.endTime <= block.timestamp && !oldLocked.isPermanent) revert LockExpired();


_updateLock(_tokenId, _value, 0, oldLocked, oldLocked.isPermanent, DepositType.INCREASE_LOCK_AMOUNT);
_updateLock(_tokenId, _value, 0, oldLocked, oldLocked.isPermanent, DepositType.INCREASE_LOCK_AMOUNT);
}
}


/**
/**
* @notice Increases the unlock time of a lock
* @notice Increases the unlock time of a lock
* @param _tokenId The id of the token to increase the unlock time for
* @param _tokenId The id of the token to increase the unlock time for
* @param _lockDuration The new duration of the lock
* @param _lockDuration The new duration of the lock
* @param _permanent Whether the lock is permanent or not
* @param _permanent Whether the lock is permanent or not
*/
*/
function increaseUnlockTime(
function increaseUnlockTime(
uint256 _tokenId,
uint256 _tokenId,
uint256 _lockDuration,
uint256 _lockDuration,
bool _permanent
bool _permanent
) external nonReentrant checkAuthorized(_tokenId) {
) external nonReentrant checkAuthorized(_tokenId) {
LockDetails memory oldLocked = _lockDetails[_tokenId];
LockDetails memory oldLocked = _lockDetails[_tokenId];
if (oldLocked.isPermanent) revert PermanentLock();
if (oldLocked.isPermanent) revert PermanentLock();


uint256 unlockTime;
uint256 unlockTime;
if (!_permanent) {
if (!_permanent) {
unlockTime = toGlobalClock(block.timestamp + _lockDuration);
unlockTime = toGlobalClock(block.timestamp + _lockDuration);
// Locktime is rounded down to global clock (days)
// Locktime is rounded down to global clock (days)
if (oldLocked.endTime <= block.timestamp) revert LockExpired();
if (oldLocked.endTime <= block.timestamp) revert LockExpired();
if (unlockTime <= oldLocked.endTime) revert LockDurationNotInFuture();
if (unlockTime <= oldLocked.endTime) revert LockDurationNotInFuture();
if (unlockTime > block.timestamp + MAX_TIME) revert LockDurationTooLong();
if (unlockTime > block.timestamp + MAX_TIME) revert LockDurationTooLong();
}
}


_updateLock(_tokenId, 0, unlockTime, oldLocked, _permanent, DepositType.INCREASE_UNLOCK_TIME);
_updateLock(_tokenId, 0, unlockTime, oldLocked, _permanent, DepositType.INCREASE_UNLOCK_TIME);
emit LockDurationExtended(_tokenId, unlockTime, _permanent);
emit LockDurationExtended(_tokenId, unlockTime, _permanent);
}
}


/**
/**
* @notice Unlocks a permanent lock
* @notice Unlocks a permanent lock
* @param _tokenId The id of the token to unlock
* @param _tokenId The id of the token to unlock
*/
*/
function unlockPermanent(uint256 _tokenId) external nonReentrant checkAuthorized(_tokenId) {
function unlockPermanent(uint256 _tokenId) external nonReentrant checkAuthorized(_tokenId) {
LockDetails memory newLocked = _lockDetails[_tokenId];
LockDetails memory newLocked = _lockDetails[_tokenId];
if (!newLocked.isPermanent) revert NotPermanentLock();
if (!newLocked.isPermanent) revert NotPermanentLock();


// Set the end time to the maximum possible time
// Set the end time to the maximum possible time
newLocked.endTime = toGlobalClock(block.timestamp + MAX_TIME);
newLocked.endTime = toGlobalClock(block.timestamp + MAX_TIME);
// Set the lock to not be permanent
// Set the lock to not be permanent
newLocked.isPermanent = false;
newLocked.isPermanent = false;


// Update the lock details
// Update the lock details
_checkpointLock(_tokenId, _lockDetails[_tokenId], newLocked);
_checkpointLock(_tokenId, _lockDetails[_tokenId], newLocked);
_lockDetails[_tokenId] = newLocked;
_lockDetails[_tokenId] = newLocked;


emit UnlockPermanent(_tokenId, _msgSender(), newLocked.endTime);
emit UnlockPermanent(_tokenId, _msgSender(), newLocked.endTime);
}
}


/**
/**
* @notice Claims the payout for a token
* @notice Claims the payout for a token
* @param _tokenId The id of the token to claim the payout for
* @param _tokenId The id of the token to claim the payout for
*/
*/
function _claim(uint256 _tokenId) internal validToken(_tokenId) nonReentrant checkAuthorized(_tokenId) {
function _claim(uint256 _tokenId) internal validToken(_tokenId) nonReentrant checkAuthorized(_tokenId) {
IVotingEscrowV2Upgradeable.LockDetails memory oldLocked = _lockDetails[_tokenId];
IVotingEscrowV2Upgradeable.LockDetails memory oldLocked = _lockDetails[_tokenId];
if (oldLocked.isPermanent) revert PermanentLock();
if (oldLocked.isPermanent) revert PermanentLock();


uint256 amountClaimed = claimablePayout(_tokenId);
uint256 amountClaimed = claimablePayout(_tokenId);
if (amountClaimed == 0) revert LockNotExpired();
if (amountClaimed == 0) revert LockNotExpired();


// Burn the NFT
_burn(_tokenId);
// Reset the lock details
// Reset the lock details
_lockDetails[_tokenId] = IVotingEscrowV2Upgradeable.LockDetails(0, 0, 0, false);
_lockDetails[_tokenId] = IVotingEscrowV2Upgradeable.LockDetails(0, 0, 0, false);
// Update the total supply
// Update the total supply
uint256 supplyBefore = supply;
uint256 supplyBefore = supply;
supply -= amountClaimed;
supply -= amountClaimed;


// Update the lock details
// Update the lock details
_checkpointLock(_tokenId, oldLocked, _lockDetails[_tokenId]);
_checkpointLock(_tokenId, oldLocked, _lockDetails[_tokenId]);


/// @notice ERC-5725 event
/// @notice ERC-5725 event
emit PayoutClaimed(_tokenId, msg.sender, amountClaimed);
emit PayoutClaimed(_tokenId, msg.sender, amountClaimed);


// IERC5725 - Update the total amount claimed
// IERC5725 - Update the total amount claimed
_payoutClaimed[_tokenId] += amountClaimed;
_payoutClaimed[_tokenId] += amountClaimed;
// Transfer the claimed amount to the sender
// Transfer the claimed amount to the sender
IERC20Upgradeable(_payoutToken(_tokenId)).safeTransfer(msg.sender, amountClaimed);
IERC20Upgradeable(_payoutToken(_tokenId)).safeTransfer(msg.sender, amountClaimed);


emit SupplyUpdated(supplyBefore, supply);
emit SupplyUpdated(supplyBefore, supply);
}
}


/**
/**
* @notice Claims the payout for a token
* @notice Claims the payout for a token
* @param _tokenId The id of the token to claim the payout for
* @param _tokenId The id of the token to claim the payout for
*/
*/
function claim(uint256 _tokenId) external override(ERC5725Upgradeable) {
function claim(uint256 _tokenId) external override(ERC5725Upgradeable) {
_claim(_tokenId);
_claim(_tokenId);
}
}


/**
/**
* @notice Merges two tokens together
* @notice Merges two tokens together
* @param _from The id of the token to merge from
* @param _from The id of the token to merge from
* @param _to The id of the token to merge to
* @param _to The id of the token to merge to
*/
*/
function merge(uint256 _from, uint256 _to) external nonReentrant checkAuthorized(_from) checkAuthorized(_to) {
function merge(uint256 _from, uint256 _to) external nonReentrant checkAuthorized(_from) checkAuthorized(_to) {
if (_from == _to) revert SameNFT();
if (_from == _to) revert SameNFT();


IVotingEscrowV2Upgradeable.LockDetails memory oldLockedTo = _lockDetails[_to];
IVotingEscrowV2Upgradeable.LockDetails memory oldLockedTo = _lockDetails[_to];
if (oldLockedTo.amount == 0) revert ZeroAmount();
if (oldLockedTo.endTime <= block.timestamp && !oldLockedTo.isPermanent) revert LockExpired();
if (oldLockedTo.endTime <= block.timestamp && !oldLockedTo.isPermanent) revert LockExpired();


IVotingEscrowV2Upgradeable.LockDetails memory oldLockedFrom = _lockDetails[_from];
IVotingEscrowV2Upgradeable.LockDetails memory oldLockedFrom = _lockDetails[_from];
if (oldLockedFrom.isPermanent != oldLockedTo.isPermanent) revert PermanentLockMismatch();
if (oldLockedFrom.amount == 0) revert ZeroAmount();
if (oldLockedFrom.isPermanent == true && oldLockedFrom.isPermanent != oldLockedTo.isPermanent) revert PermanentLockMismatch();
// Calculate the new end time
// Calculate the new end time
uint256 end = oldLockedFrom.endTime >= oldLockedTo.endTime ? oldLockedFrom.endTime : oldLockedTo.endTime;
uint256 end = oldLockedFrom.endTime >= oldLockedTo.endTime ? oldLockedFrom.endTime : oldLockedTo.endTime;


// Burn the token being merged from
// Set lock amount to 0
_burn(_from);
_lockDetails[_from].amount = 0;
// Reset the lock details
_lockDetails[_from] = LockDetails(0, 0, 0, false);
// Update the lock details
// Update the lock details
_checkpointLock(_from, oldLockedFrom, _lockDetails[_from]);
_checkpointLock(_from, oldLockedFrom, _lockDetails[_from]);


// Calculate the new lock details
// Calculate the new lock details
LockDetails memory newLockedTo;
LockDetails memory newLockedTo;
newLockedTo.amount = oldLockedTo.amount + oldLockedFrom.amount;
newLockedTo.amount = oldLockedTo.amount + oldLockedFrom.amount;
newLockedTo.isPermanent = oldLockedTo.isPermanent;
newLockedTo.isPermanent = oldLockedTo.isPermanent;
if (!newLockedTo.isPermanent) {
if (!newLockedTo.isPermanent) {
newLockedTo.endTime = end;
newLockedTo.endTime = end;
}
}


// Update the lock details
// Update the lock details
_checkpointLock(_to, oldLockedTo, newLockedTo);
_checkpointLock(_to, oldLockedTo, newLockedTo);
_lockDetails[_to] = newLockedTo;
_lockDetails[_to] = newLockedTo;
emit LockMerged(_from, _to, newLockedTo.amount, end, newLockedTo.isPermanent);
emit LockMerged(_from, _to, newLockedTo.amount, end, newLockedTo.isPermanent);
}
}


/**
/**
* @notice Splits a token into multiple tokens
* @notice Splits a token into multiple tokens
* @param _weights The percentages to split the token into
* @param _weights The percentages to split the token into
* @param _tokenId The id of the token to split
* @param _tokenId The id of the token to split
*/
*/
function split(uint256[] memory _weights, uint256 _tokenId) external nonReentrant checkAuthorized(_tokenId) {
function split(uint256[] memory _weights, uint256 _tokenId) external nonReentrant checkAuthorized(_tokenId) {
LockDetails memory locked = _lockDetails[_tokenId];
LockDetails memory locked = _lockDetails[_tokenId];
LockDetails storage lockedStorage = _lockDetails[_tokenId];
uint256 currentTime = block.timestamp;
uint256 currentTime = block.timestamp;
/// @dev Pulling directly from locked struct to avoid stack-too-deep
/// @dev Pulling directly from locked struct to avoid stack-too-deep
if (locked.endTime <= currentTime && !locked.isPermanent) revert LockExpired();
if (locked.endTime <= currentTime && !locked.isPermanent) revert LockExpired();
if (locked.amount == 0 || _weights.length < 2) revert ZeroAmount();
if (locked.amount == 0 || _weights.length < 2) revert ZeroAmount();


// reset supply, _deposit_for increase it
// reset supply, _deposit_for increase it
supply -= uint256(int256(locked.amount));
supply -= uint256(int256(locked.amount));
// Capture owner for split
// Capture owner for split
address owner = _ownerOf(_tokenId);
address owner = _ownerOf(_tokenId);
uint256 totalWeight = 0;
uint256 totalWeight = 0;
for (uint256 i = 0; i < _weights.length; i++) {
for (uint256 i = 0; i < _weights.length; i++) {
totalWeight += _weights[i];
totalWeight += _weights[i];
}
}


// remove old data
_lockDetails[_tokenId] = LockDetails(0, 0, 0, false);
_checkpointLock(_tokenId, locked, _lockDetails[_tokenId]);
_burn(_tokenId);

uint256 duration = locked.isPermanent ? 0 : locked.endTime > currentTime ? locked.endTime - currentTime : 0;
uint256 duration = locked.isPermanent ? 0 : locked.endTime > currentTime ? locked.endTime - currentTime : 0;


uint256 amountLeftToSplit = locked.amount;
uint256 amountLeftToSplit = locked.amount;
for (uint256 i = 0; i < _weights.length; i++) {
for (uint256 i = 0; i < _weights.length; i++) {
uint256 value = (uint256(int256(locked.amount)) * _weights[i]) / totalWeight;
uint256 value = (uint256(int256(locked.amount)) * _weights[i]) / totalWeight;
if(i == _weights.length - 1) {
if(i == _weights.length - 1) {
/// @dev Ensure no rounding errors occur by passing the remainder to the last split
/// @dev Ensure no rounding errors occur by passing the remainder to the last split
value = amountLeftToSplit;
value = amountLeftToSplit;
}
}
amountLeftToSplit -= value;
amountLeftToSplit -= value;
_createLock(value, duration, owner, owner, locked.isPermanent, DepositType.SPLIT_TYPE);
if (i == 0) {
lockedStorage.amount = value;
_checkpointLock(_tokenId, locked, lockedStorage);
} else {
_createLock(value, duration, owner, owner, locked.isPermanent, DepositType.SPLIT_TYPE);
}
}
}
emit LockSplit(_weights, _tokenId);
emit LockSplit(_weights, _tokenId);
}
}


/**
* @notice Burns a token
* @param _tokenId The ids of the tokens to burn
*/
function burn(uint256 _tokenId) external {
if (_ownerOf(_tokenId) != _msgSender()) revert NotLockOwner();
if(_lockDetails[_tokenId].amount > 0) revert LockHoldsValue();
_burn(_tokenId);
}

/*///////////////////////////////////////////////////////////////
/*///////////////////////////////////////////////////////////////
GAUGE REWARDS LOGIC
GAUGE REWARDS LOGIC
//////////////////////////////////////////////////////////////*/
//////////////////////////////////////////////////////////////*/


function balanceOfNFT(uint256 _tokenId) public view returns (uint256) {
function balanceOfNFT(uint256 _tokenId) public view returns (uint256) {
return edStore.getAdjustedEscrowBias(_tokenId, block.timestamp);
return edStore.getAdjustedEscrowBias(_tokenId, block.timestamp);
}
}


function balanceOfNFTAt(uint256 _tokenId, uint256 _timestamp) external view returns (uint256) {
function balanceOfNFTAt(uint256 _tokenId, uint256 _timestamp) external view returns (uint256) {
return edStore.getAdjustedEscrowBias(_tokenId, _timestamp);
return edStore.getAdjustedEscrowBias(_tokenId, _timestamp);
}
}


function getPastEscrowPoint(
function getPastEscrowPoint(
uint256 _tokenId,
uint256 _tokenId,
uint256 _timestamp
uint256 _timestamp
) external view override returns (Checkpoints.Point memory, uint48) {
) external view override returns (Checkpoints.Point memory, uint48) {
return edStore.getAdjustedEscrow(_tokenId, _timestamp);
return edStore.getAdjustedEscrow(_tokenId, _timestamp);
}
}


function getFirstEscrowPoint(uint256 _tokenId) external view override returns (Checkpoints.Point memory, uint48) {
function getFirstEscrowPoint(uint256 _tokenId) external view override returns (Checkpoints.Point memory, uint48) {
return edStore.getFirstEscrowPoint(_tokenId);
return edStore.getFirstEscrowPoint(_tokenId);
}
}


function totalSupply() public view override(ERC721EnumerableUpgradeable, IERC721EnumerableUpgradeable) returns (uint256) {
function totalSupply() public view override(ERC721EnumerableUpgradeable, IERC721EnumerableUpgradeable) returns (uint256) {
return edStore.getAdjustedGlobalVotes(block.timestamp.toUint48());
return edStore.getAdjustedGlobalVotes(block.timestamp.toUint48());
}
}




/*///////////////////////////////////////////////////////////////
/*///////////////////////////////////////////////////////////////
@dev See {IVotes}.
@dev See {IVotes}.
//////////////////////////////////////////////////////////////*/
//////////////////////////////////////////////////////////////*/


/**
/**
* @notice Gets the votes for a delegatee
* @notice Gets the votes for a delegatee
* @param account The address of the delegatee
* @param account The address of the delegatee
* @return The number of votes the delegatee has
* @return The number of votes the delegatee has
*/
*/
function getVotes(address account) external view override(IVotes) returns (uint256) {
function getVotes(address account) external view override(IVotes) returns (uint256) {
return edStore.getAdjustedVotes(account, block.timestamp.toUint48());
return edStore.getAdjustedVotes(account, block.timestamp.toUint48());
}
}


/**
/**
* @notice Gets the past votes for a delegatee at a specific time point
* @notice Gets the past votes for a delegatee at a specific time point
* @param account The address of the delegatee
* @param account The address of the delegatee
* @param timepoint The time point to get the votes at
* @param timepoint The time point to get the votes at
* @return The number of votes the delegatee had at the time point
* @return The number of votes the delegatee had at the time point
*/
*/
function getPastVotes(address account, uint256 timepoint) external view override(IVotes) returns (uint256) {
function getPastVotes(address account, uint256 timepoint) external view override(IVotes) returns (uint256) {
return edStore.getAdjustedVotes(account, timepoint.toUint48());
return edStore.getAdjustedVotes(account, timepoint.toUint48());
}
}


/**
/**
* @notice Gets the total supply at a specific time point
* @notice Gets the total supply at a specific time point
* @param _timePoint The time point to get the total supply at
* @param _timePoint The time point to get the total supply at
* @return The total supply at the time point
* @return The total supply at the time point
*/
*/
function getPastTotalSupply(uint256 _timePoint) external view override(IVotes) returns (uint256) {
function getPastTotalSupply(uint256 _timePoint) external view override(IVotes) returns (uint256) {
return edStore.getAdjustedGlobalVotes(_timePoint.toUint48());
return edStore.getAdjustedGlobalVotes(_timePoint.toUint48());
}
}


/**
/**
* @notice Delegates votes to a delegatee
* @notice Delegates votes to a delegatee
* @param delegatee The account to delegate votes to
* @param delegatee The account to delegate votes to
*/
*/
function delegate(address delegatee) external override(IVotes) {
function delegate(address delegatee) external override(IVotes) {
_delegate(_msgSender(), delegatee);
_delegate(_msgSender(), delegatee);
}
}


/**
/**
* @notice Gets the delegate of a delegatee
* @notice Gets the delegate of a delegatee
* @dev This function implements IVotes interface.
* @dev This function implements IVotes interface.
* An account can have multiple delegates in this contract. If multiple
* An account can have multiple delegates in this contract. If multiple
* different delegates are found, this function returns address(1) to
* different delegates are found, this function returns address(1) to
* indicate that there is not a single unique delegate.
* indicate that there is not a single unique delegate.
* @param account The delegatee to get the delegate of
* @param account The delegatee to get the delegate of
* @return The delegate of the delegatee, or address(1) if multiple different delegates are found
* @return The delegate of the delegatee, or address(1) if multiple different delegates are found
*/
*/
function delegates(address account) external view override(IVotes) returns (address) {
function delegates(address account) external view override(IVotes) returns (address) {
address delegatee = address(0);
address delegatee = address(0);
uint256 balance = balanceOf(account);
uint256 balance = balanceOf(account);
/// @dev out-of-gas protection
/// @dev out-of-gas protection
uint256 runs = 50 > balance ? balance : 50;
uint256 runs = 50 > balance ? balance : 50;
for (uint256 i = 0; i < runs; i++) {
for (uint256 i = 0; i < runs; i++) {
uint256 tokenId = tokenOfOwnerByIndex(account, i);
uint256 tokenId = tokenOfOwnerByIndex(account, i);
address currentDelegatee = edStore.getEscrowDelegatee(tokenId);
address currentDeleg
/// @dev Hacky way to check if the delegatee is the same for all locks
if (delegatee == address(0)) {
delegatee = currentDelegatee;
} else if (delegatee != currentDelegatee) {
return address(1);
}
}
return delegatee;
}

/**
* @notice Delegates votes from a spe