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

Created Diff never expires
27 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
620 lines
27 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
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