Diff
checker
文本
文本
圖像
文檔
Excel
文件夾
Legal
Enterprise
桌面版
定價
登入
下載 Diffchecker 桌面版
比較文本
尋找兩個文字檔案之間的差異
工具
歷史
即時編輯器
隱藏空白變更
摺疊未變更行
關閉換行
檢視
拆分
統一
比對精度
智能
單詞
字符
文字樣式
變更外觀
語法突出顯示
選擇語法
忽略
文字轉換
前往第一個差異
編輯輸入
Diffchecker Desktop
執行Diffchecker最安全的方式。取得Diffchecker桌面應用程式:您的差異永遠不會離開您的電腦!
取得桌面版
2024-03-12-Lynex-VotingEscrowV2Upgradeable-Version2-diff
建立於
2 年前
差異永不過期
清除
匯出
分享
解釋
26 刪除
行
總計
刪除
字符
總計
刪除
要繼續使用此功能,請升級到
Diff
checker
Pro
查看價格
620 行
全部複製
26 新增
行
總計
新增
字符
總計
新增
要繼續使用此功能,請升級到
Diff
checker
Pro
查看價格
617 行
全部複製
// 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 currentDeleg
atee = 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
已保存差異
原始文本
開啟檔案
// SPDX-License-Identifier: MIT pragma solidity 0.8.13; import {IERC721EnumerableUpgradeable, ERC721EnumerableUpgradeable, IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import {ERC5725Upgradeable} from "./erc5725/ERC5725Upgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {IVotingEscrowV2Upgradeable, IVotes} from "./interfaces/IVotingEscrowV2Upgradeable.sol"; import {IVeArtProxy} from "../../interfaces/IVeArtProxy.sol"; import {SafeCastLibrary} from "./libraries/SafeCastLibrary.sol"; import {EscrowDelegateCheckpoints, Checkpoints} from "./libraries/EscrowDelegateCheckpoints.sol"; import {EscrowDelegateStorage} from "./libraries/EscrowDelegateStorage.sol"; /** * @title VotingEscrow * @dev This contract is used for locking tokens and voting. * * - tokenIds always have a delegatee, with the owner being the default (see createLock) * - On transfers, delegation is reset. (See _update) * - */ contract VotingEscrowV2Upgradeable is Initializable, IVotingEscrowV2Upgradeable, ERC5725Upgradeable, EscrowDelegateStorage, EIP712Upgradeable, ReentrancyGuard { using SafeERC20Upgradeable for IERC20Upgradeable; using SafeCastLibrary for uint256; using EscrowDelegateCheckpoints for EscrowDelegateCheckpoints.EscrowDelegateStore; enum DepositType { DEPOSIT_FOR_TYPE, CREATE_LOCK_TYPE, INCREASE_LOCK_AMOUNT, INCREASE_UNLOCK_TIME, MERGE_TYPE, SPLIT_TYPE } /// @notice The token being locked IERC20Upgradeable public _token; /// @notice Total locked supply uint256 public supply; uint8 public constant decimals = 18; address public artProxy; /// @notice The EIP-712 typehash for the delegation struct used by the contract bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); /// @notice A record of states for signing / validating signatures mapping(address => uint256) public nonces; /// @dev OpenZeppelin v5 IVotes error error VotesExpiredSignature(uint256 expiry); /** * @notice The constructor is disabled for this upgradeable contract. */ constructor() { /// @dev Disable the initializers for implementation contracts to ensure that the contract is not left uninitialized. _disableInitializers(); } /** * @dev Initializes the contract with the given parameters. * @param _name The name to set for the token. * @param _symbol The symbol to set for the token. * @param version The version of the contract. * @param mainToken The main token address that will be locked in the escrow. * @param _artProxy The address of the art proxy contract. */ function initialize( string memory _name, string memory _symbol, string memory version, IERC20Upgradeable mainToken, address _artProxy ) public initializer { __ERC5725_init(_name, _symbol); __EIP712_init(_name, version); _token = mainToken; artProxy = _artProxy; // Reset MAX_TIME in proxy storage MAX_TIME = uint256(uint128(EscrowDelegateCheckpoints.MAX_TIME)); } modifier checkAuthorized(uint256 _tokenId) { address owner = _ownerOf(_tokenId); if (owner == address(0)) { revert ERC721NonexistentToken(_tokenId); } address sender = _msgSender(); if (!_isAuthorized(owner, sender, _tokenId)) { revert ERC721InsufficientApproval(sender, _tokenId); } _; } /// @dev Returns current token URI metadata /// @param _tokenId Token ID to fetch URI for. function tokenURI(uint _tokenId) public view override validToken(_tokenId) returns (string memory) { LockDetails memory _locked = _lockDetails[_tokenId]; return IVeArtProxy(artProxy)._tokenURI( _tokenId, balanceOfNFT(_tokenId), _locked.endTime, uint(int256(_locked.amount)) ); } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface( bytes4 interfaceId ) public view virtual override(ERC5725Upgradeable, IERC165Upgradeable) returns (bool supported) { return interfaceId == type(IVotingEscrowV2Upgradeable).interfaceId || super.supportsInterface(interfaceId); } /** * @dev See {IERC721-_beforeTokenTransfer}. * Clears the approval of a given `tokenId` when the token is transferred or burned. */ function _beforeTokenTransfer( address from, address to, uint256 firstTokenId, uint256 batchSize ) internal virtual override { super._beforeTokenTransfer(from, to, firstTokenId, batchSize); for (uint256 i = 0; i < batchSize; i++) { uint256 tokenId = firstTokenId + i; if (from != to) { /// @dev Sets delegatee to new owner on transfers (address oldDelegatee, address newDelegatee) = edStore.delegate( tokenId, to, _lockDetails[tokenId].endTime ); emit DelegateChanged(to, oldDelegatee, newDelegatee); emit LockDelegateChanged(tokenId, to, oldDelegatee, newDelegatee); } } } /** * ERC-5725 and token-locking logic */ /// @notice maps the vesting data with tokenIds mapping(uint256 => LockDetails) public _lockDetails; /// @notice tracker of current NFT id uint256 public totalNftsMinted = 0; /** * @notice Creates a new vesting NFT and mints it * @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 duration Duration in seconds of the lock * @param to The receiver of the lock */ function _createLock( uint256 value, uint256 duration, address to, address delegatee, bool permanent, DepositType depositType ) internal virtual returns (uint256) { if (value == 0) revert ZeroAmount(); uint256 unlockTime; totalNftsMinted++; uint256 newTokenId = totalNftsMinted; if (!permanent) { unlockTime = toGlobalClock(block.timestamp + duration); // Locktime is rounded down to global clock (days) if (unlockTime <= block.timestamp) revert LockDurationNotInFuture(); if (unlockTime > block.timestamp + MAX_TIME) revert LockDurationTooLong(); } _safeMint(to, newTokenId); _lockDetails[newTokenId].startTime = block.timestamp; /// @dev Checkpoint created in _updateLock _updateLock(newTokenId, value, unlockTime, _lockDetails[newTokenId], permanent, depositType); edStore.delegate(newTokenId, delegatee, unlockTime); emit LockCreated(newTokenId, delegatee, value, unlockTime, permanent); emit DelegateChanged(to, address(0), delegatee); emit LockDelegateChanged(newTokenId, to, address(0), delegatee); return newTokenId; } /** * @notice Creates a lock for the sender * @param _value The total assets to be locked over time * @param _lockDuration Duration in seconds of the lock * @param _permanent Whether the lock is permanent or not * @return The id of the newly created token */ function createLock( uint256 _value, uint256 _lockDuration, bool _permanent ) external nonReentrant returns (uint256) { return _createLock(_value, _lockDuration, _msgSender(), _msgSender(), _permanent, DepositType.CREATE_LOCK_TYPE); } /** * @notice Creates a lock for a specified address * @param _value The total assets to be locked over time * @param _lockDuration Duration in seconds of the lock * @param _to The receiver of the lock * @param _permanent Whether the lock is permanent or not * @return The id of the newly created token */ function createLockFor( uint256 _value, uint256 _lockDuration, address _to, bool _permanent ) external nonReentrant returns (uint256) { return _createLock(_value, _lockDuration, _to, _to, _permanent, DepositType.CREATE_LOCK_TYPE); } /** * @notice Creates a lock for a specified address * @param _value The total assets to be locked over time * @param _lockDuration Duration in seconds of the lock * @param _to The receiver of the lock * @param _delegatee The receiver of the lock * @param _permanent Whether the lock is permanent or not * @return The id of the newly created token */ function createDelegatedLockFor( uint256 _value, uint256 _lockDuration, address _to, address _delegatee, bool _permanent ) external nonReentrant returns (uint256) { return _createLock(_value, _lockDuration, _to, _delegatee, _permanent, DepositType.CREATE_LOCK_TYPE); } /** * @notice Updates the global checkpoint */ function globalCheckpoint() public nonReentrant { return edStore.globalCheckpoint(); } function checkpoint() external override { globalCheckpoint(); } /** * @notice Updates the checkpoint for a delegatee * @param _delegateeAddress The address of the delegatee */ function checkpointDelegatee(address _delegateeAddress) external nonReentrant { edStore.baseCheckpointDelegatee(_delegateeAddress); } /// @notice Deposit & update lock tokens for a user /// @dev The supply is increased by the _value amount /// @param _tokenId NFT that holds lock /// @param _increasedValue Amount to deposit /// @param _unlockTime New time when to unlock the tokens, or 0 if unchanged /// @param _oldLocked Previous locked amount / timestamp function _updateLock( uint256 _tokenId, uint256 _increasedValue, uint256 _unlockTime, LockDetails memory _oldLocked, bool isPermanent, DepositType depositType ) internal { uint256 supplyBefore = supply; supply += _increasedValue; // Set newLocked to _oldLocked without mangling memory LockDetails memory newLocked; (newLocked.amount, newLocked.startTime, newLocked.endTime, newLocked.isPermanent) = ( _oldLocked.amount, _oldLocked.startTime, _oldLocked.endTime, _oldLocked.isPermanent ); // Adding to existing lock, or if a lock is expired - creating a new one newLocked.amount += _increasedValue; if (_unlockTime != 0 && !isPermanent) { newLocked.endTime = _unlockTime; } if (isPermanent) { newLocked.endTime = 0; newLocked.isPermanent = true; } _lockDetails[_tokenId] = newLocked; emit LockUpdated(_tokenId, _increasedValue, _unlockTime, isPermanent); // Possibilities: // Both _oldLocked.end could be current or expired (>/< block.timestamp) // 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) // newLocked.end > block.timestamp (always) _checkpointLock(_tokenId, _oldLocked, newLocked); if (_increasedValue != 0 && depositType != DepositType.SPLIT_TYPE) { _token.safeTransferFrom(_msgSender(), address(this), _increasedValue); } emit SupplyUpdated(supply, supplyBefore + _increasedValue); } /// @notice Record global and per-user data to checkpoints. Used by VotingEscrow system. /// @param _tokenId NFT token ID. No user checkpoint if 0 /// @param _oldLocked Previous locked amount / end lock time for the user /// @param _newLocked New locked amount / end lock time for the user function _checkpointLock( uint256 _tokenId, IVotingEscrowV2Upgradeable.LockDetails memory _oldLocked, IVotingEscrowV2Upgradeable.LockDetails memory _newLocked ) internal { edStore.checkpoint( _tokenId, _oldLocked.amount.toInt128(), _newLocked.amount.toInt128(), _oldLocked.endTime, _newLocked.endTime ); } /// @notice Deposit `_value` tokens for `_tokenId` and add to the lock /// @dev Anyone (even a smart contract) can deposit for someone else, but /// cannot extend their locktime and deposit for a brand new user /// @param _tokenId lock NFT /// @param _value Amount to add to user's lock function increaseAmount(uint256 _tokenId, uint256 _value) external nonReentrant { if (_value == 0) revert ZeroAmount(); IVotingEscrowV2Upgradeable.LockDetails memory oldLocked = _lockDetails[_tokenId]; if (oldLocked.amount <= 0) revert NoLockFound(); if (oldLocked.endTime <= block.timestamp && !oldLocked.isPermanent) revert LockExpired(); _updateLock(_tokenId, _value, 0, oldLocked, oldLocked.isPermanent, DepositType.INCREASE_LOCK_AMOUNT); } /** * @notice Increases the unlock time of a lock * @param _tokenId The id of the token to increase the unlock time for * @param _lockDuration The new duration of the lock * @param _permanent Whether the lock is permanent or not */ function increaseUnlockTime( uint256 _tokenId, uint256 _lockDuration, bool _permanent ) external nonReentrant checkAuthorized(_tokenId) { LockDetails memory oldLocked = _lockDetails[_tokenId]; if (oldLocked.isPermanent) revert PermanentLock(); uint256 unlockTime; if (!_permanent) { unlockTime = toGlobalClock(block.timestamp + _lockDuration); // Locktime is rounded down to global clock (days) if (oldLocked.endTime <= block.timestamp) revert LockExpired(); if (unlockTime <= oldLocked.endTime) revert LockDurationNotInFuture(); if (unlockTime > block.timestamp + MAX_TIME) revert LockDurationTooLong(); } _updateLock(_tokenId, 0, unlockTime, oldLocked, _permanent, DepositType.INCREASE_UNLOCK_TIME); emit LockDurationExtended(_tokenId, unlockTime, _permanent); } /** * @notice Unlocks a permanent lock * @param _tokenId The id of the token to unlock */ function unlockPermanent(uint256 _tokenId) external nonReentrant checkAuthorized(_tokenId) { LockDetails memory newLocked = _lockDetails[_tokenId]; if (!newLocked.isPermanent) revert NotPermanentLock(); // Set the end time to the maximum possible time newLocked.endTime = toGlobalClock(block.timestamp + MAX_TIME); // Set the lock to not be permanent newLocked.isPermanent = false; // Update the lock details _checkpointLock(_tokenId, _lockDetails[_tokenId], newLocked); _lockDetails[_tokenId] = newLocked; emit UnlockPermanent(_tokenId, _msgSender(), newLocked.endTime); } /** * @notice Claims the payout for a token * @param _tokenId The id of the token to claim the payout for */ function _claim(uint256 _tokenId) internal validToken(_tokenId) nonReentrant checkAuthorized(_tokenId) { IVotingEscrowV2Upgradeable.LockDetails memory oldLocked = _lockDetails[_tokenId]; if (oldLocked.isPermanent) revert PermanentLock(); uint256 amountClaimed = claimablePayout(_tokenId); if (amountClaimed == 0) revert LockNotExpired(); // Burn the NFT _burn(_tokenId); // Reset the lock details _lockDetails[_tokenId] = IVotingEscrowV2Upgradeable.LockDetails(0, 0, 0, false); // Update the total supply uint256 supplyBefore = supply; supply -= amountClaimed; // Update the lock details _checkpointLock(_tokenId, oldLocked, _lockDetails[_tokenId]); /// @notice ERC-5725 event emit PayoutClaimed(_tokenId, msg.sender, amountClaimed); // IERC5725 - Update the total amount claimed _payoutClaimed[_tokenId] += amountClaimed; // Transfer the claimed amount to the sender IERC20Upgradeable(_payoutToken(_tokenId)).safeTransfer(msg.sender, amountClaimed); emit SupplyUpdated(supplyBefore, supply); } /** * @notice Claims the payout for a token * @param _tokenId The id of the token to claim the payout for */ function claim(uint256 _tokenId) external override(ERC5725Upgradeable) { _claim(_tokenId); } /** * @notice Merges two tokens together * @param _from The id of the token to merge from * @param _to The id of the token to merge to */ function merge(uint256 _from, uint256 _to) external nonReentrant checkAuthorized(_from) checkAuthorized(_to) { if (_from == _to) revert SameNFT(); IVotingEscrowV2Upgradeable.LockDetails memory oldLockedTo = _lockDetails[_to]; if (oldLockedTo.endTime <= block.timestamp && !oldLockedTo.isPermanent) revert LockExpired(); IVotingEscrowV2Upgradeable.LockDetails memory oldLockedFrom = _lockDetails[_from]; if (oldLockedFrom.isPermanent != oldLockedTo.isPermanent) revert PermanentLockMismatch(); // Calculate the new end time uint256 end = oldLockedFrom.endTime >= oldLockedTo.endTime ? oldLockedFrom.endTime : oldLockedTo.endTime; // Burn the token being merged from _burn(_from); // Reset the lock details _lockDetails[_from] = LockDetails(0, 0, 0, false); // Update the lock details _checkpointLock(_from, oldLockedFrom, _lockDetails[_from]); // Calculate the new lock details LockDetails memory newLockedTo; newLockedTo.amount = oldLockedTo.amount + oldLockedFrom.amount; newLockedTo.isPermanent = oldLockedTo.isPermanent; if (!newLockedTo.isPermanent) { newLockedTo.endTime = end; } // Update the lock details _checkpointLock(_to, oldLockedTo, newLockedTo); _lockDetails[_to] = newLockedTo; emit LockMerged(_from, _to, newLockedTo.amount, end, newLockedTo.isPermanent); } /** * @notice Splits a token into multiple tokens * @param _weights The percentages to split the token into * @param _tokenId The id of the token to split */ function split(uint256[] memory _weights, uint256 _tokenId) external nonReentrant checkAuthorized(_tokenId) { LockDetails memory locked = _lockDetails[_tokenId]; uint256 currentTime = block.timestamp; /// @dev Pulling directly from locked struct to avoid stack-too-deep if (locked.endTime <= currentTime && !locked.isPermanent) revert LockExpired(); if (locked.amount == 0 || _weights.length < 2) revert ZeroAmount(); // reset supply, _deposit_for increase it supply -= uint256(int256(locked.amount)); // Capture owner for split address owner = _ownerOf(_tokenId); uint256 totalWeight = 0; for (uint256 i = 0; i < _weights.length; 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 amountLeftToSplit = locked.amount; for (uint256 i = 0; i < _weights.length; i++) { uint256 value = (uint256(int256(locked.amount)) * _weights[i]) / totalWeight; if(i == _weights.length - 1) { /// @dev Ensure no rounding errors occur by passing the remainder to the last split value = amountLeftToSplit; } amountLeftToSplit -= value; _createLock(value, duration, owner, owner, locked.isPermanent, DepositType.SPLIT_TYPE); } emit LockSplit(_weights, _tokenId); } /*/////////////////////////////////////////////////////////////// GAUGE REWARDS LOGIC //////////////////////////////////////////////////////////////*/ function balanceOfNFT(uint256 _tokenId) public view returns (uint256) { return edStore.getAdjustedEscrowBias(_tokenId, block.timestamp); } function balanceOfNFTAt(uint256 _tokenId, uint256 _timestamp) external view returns (uint256) { return edStore.getAdjustedEscrowBias(_tokenId, _timestamp); } function getPastEscrowPoint( uint256 _tokenId, uint256 _timestamp ) external view override returns (Checkpoints.Point memory, uint48) { return edStore.getAdjustedEscrow(_tokenId, _timestamp); } function getFirstEscrowPoint(uint256 _tokenId) external view override returns (Checkpoints.Point memory, uint48) { return edStore.getFirstEscrowPoint(_tokenId); } function totalSupply() public view override(ERC721EnumerableUpgradeable, IERC721EnumerableUpgradeable) returns (uint256) { return edStore.getAdjustedGlobalVotes(block.timestamp.toUint48()); } /*/////////////////////////////////////////////////////////////// @dev See {IVotes}. //////////////////////////////////////////////////////////////*/ /** * @notice Gets the votes for a delegatee * @param account The address of the delegatee * @return The number of votes the delegatee has */ function getVotes(address account) external view override(IVotes) returns (uint256) { return edStore.getAdjustedVotes(account, block.timestamp.toUint48()); } /** * @notice Gets the past votes for a delegatee at a specific time point * @param account The address of the delegatee * @param timepoint The time point to get the votes at * @return The number of votes the delegatee had at the time point */ function getPastVotes(address account, uint256 timepoint) external view override(IVotes) returns (uint256) { return edStore.getAdjustedVotes(account, timepoint.toUint48()); } /** * @notice Gets the total supply at a specific time point * @param _timePoint The time point to get the total supply at * @return The total supply at the time point */ function getPastTotalSupply(uint256 _timePoint) external view override(IVotes) returns (uint256) { return edStore.getAdjustedGlobalVotes(_timePoint.toUint48()); } /** * @notice Delegates votes to a delegatee * @param delegatee The account to delegate votes to */ function delegate(address delegatee) external override(IVotes) { _delegate(_msgSender(), delegatee); } /** * @notice Gets the delegate of a delegatee * @dev This function implements IVotes interface. * An account can have multiple delegates in this contract. If multiple * different delegates are found, this function returns address(1) to * indicate that there is not a single unique delegate. * @param account The delegatee to get the delegate of * @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) { address delegatee = address(0); uint256 balance = balanceOf(account); /// @dev out-of-gas protection uint256 runs = 50 > balance ? balance : 50; for (uint256 i = 0; i < runs; i++) { uint256 tokenId = tokenOfOwnerByIndex(account, i); address currentDelegatee = edStore.getEscrowDelegatee(tokenId); /// @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 specific lock to a delegatee * @param _tokenId The ID of the lock token delegating the votes * @param delegatee The address to which the votes are being delegated */ function delegate(uint256 _tokenId, address delegatee) external checkAuthorized(_tokenId) { (address fromDelegatee, address toDelegatee) = edStore.delegate( _tokenId, delegatee, _lockDetails[_tokenId].endTime ); emit LockDelegateChanged(_tokenId, _msgSender(), fromDelegatee, toDelegatee); } /** * @notice Gets the delegatee of a given lock * @param tokenId The ID of the lock token * @return The address of the delegatee for the specified token */ function getLockDelegatee(uint256 tokenId) external view returns (address) { return edStore.getEscrowDelegatee(tokenId); } /** * @notice Gets all delegates of a delegatee * @param account The delegatee to get the delegates of * @return An array of all delegates of the delegatee */ function getAccountDelegates(address account) external view returns (address[] memory) { uint256 balance = balanceOf(account); address[] memory allDelegates = new address[](balance); for (uint256 i = 0; i < balance; i++) { uint256 tokenId = tokenOfOwnerByIndex(account, i); allDelegates[i] = edStore.getEscrowDelegatee(tokenId); } return allDelegates; } /** * @notice Public function to get the delegatee of a lock * @param tokenId The ID of the token * @param timestamp The timestamp to get the delegate at * @return The address of the delegate */ function delegates(uint256 tokenId, uint48 timestamp) external view returns (address) { return edStore.getEscrowDelegateeAtTime(tokenId, timestamp); } /** * @notice Delegates votes by signature * @param delegatee The delegatee to delegate votes to * @param nonce The nonce for the signature * @param expiry The expiry time for the signature * @param v The recovery byte of the signature * @param r Half of the ECDSA signature pair * @param s Half of the ECDSA signature pair */ function delegateBySig( address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s ) external override(IVotes) { // Removed for gas considerations. The code below uncommented adds 1.289 kbs to the contract size. revert("delegateBySig: size cut"); /* if (delegatee == msg.sender || delegatee == address(0)) revert InvalidDelegatee(); bytes32 domainSeparator = _domainSeparatorV4(); bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry)); bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); address signatory = ECDSA.recover(digest, v, r, s); if (signatory == address(0)) revert InvalidSignature(); if (nonce != nonces[signatory]++) revert InvalidNonce(); if (block.timestamp > expiry) revert VotesExpiredSignature(expiry); return _delegate(signatory, delegatee); */ } /** * @notice Delegates votes from an owner to an delegatee * @param delegator The owner of the tokenId delegating votes * @param delegatee The account to delegate votes to */ function _delegate(address delegator, address delegatee) internal nonReentrant { uint256 balance = balanceOf(delegator); address fromDelegate = address(0); for (uint256 i = 0; i < balance; i++) { uint256 tokenId = tokenOfOwnerByIndex(delegator, i); (address oldDelegate, address newDelegate) = edStore.delegate( tokenId, delegatee, _lockDetails[tokenId].endTime ); emit LockDelegateChanged(tokenId, delegator, oldDelegate, newDelegate); /// @dev Hacky way to check if the delegatee is the same for all locks if (fromDelegate == address(0)) { fromDelegate = oldDelegate; } else if (fromDelegate != address(1)) { if (fromDelegate != oldDelegate) { fromDelegate = address(1); } } } emit DelegateChanged(delegator, fromDelegate, delegatee); } /*/////////////////////////////////////////////////////////////// @dev See {IERC5725}. //////////////////////////////////////////////////////////////*/ /** * @dev See {ERC5725Upgradeable}. */ function vestedPayoutAtTime( uint256 tokenId, uint256 timestamp ) public view override(ERC5725Upgradeable) validToken(tokenId) returns (uint256 payout) { if (timestamp >= _endTime(tokenId)) { return _payout(tokenId); } return 0; } /** * @dev See {ERC5725Upgradeable}. */ function _payoutToken(uint256 /*tokenId*/) internal view override returns (address) { return address(_token); } /** * @dev See {ERC5725Upgradeable}. */ function _payout(uint256 tokenId) internal view override returns (uint256) { return _lockDetails[tokenId].amount; } /** * @dev See {ERC5725Upgradeable}. */ function _startTime(uint256 tokenId) internal view override returns (uint256) { return _lockDetails[tokenId].startTime; } /** * @dev See {ERC5725Upgradeable}. */ function _endTime(uint256 tokenId) internal view override returns (uint256) { return _lockDetails[tokenId].endTime; } function token() external view returns (IERC20Upgradeable) { return _token; } function lockDetails(uint256 _tokenId) external view returns (LockDetails memory) { return _lockDetails[_tokenId]; } function isApprovedOrOwner(address user, uint tokenId) external view returns (bool) { return _isAuthorized(ownerOf(tokenId), user, tokenId); } }
更改後文本
開啟檔案
// SPDX-License-Identifier: MIT pragma solidity 0.8.13; import {IERC721EnumerableUpgradeable, ERC721EnumerableUpgradeable, IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import {ERC5725Upgradeable} from "./erc5725/ERC5725Upgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {IVotingEscrowV2Upgradeable, IVotes} from "./interfaces/IVotingEscrowV2Upgradeable.sol"; import {IVeArtProxy} from "../../interfaces/IVeArtProxy.sol"; import {SafeCastLibrary} from "./libraries/SafeCastLibrary.sol"; import {EscrowDelegateCheckpoints, Checkpoints} from "./libraries/EscrowDelegateCheckpoints.sol"; import {EscrowDelegateStorage} from "./libraries/EscrowDelegateStorage.sol"; /** * @title VotingEscrow * @dev This contract is used for locking tokens and voting. * * - tokenIds always have a delegatee, with the owner being the default (see createLock) * - On transfers, delegation is reset. (See _update) * - */ contract VotingEscrowV2Upgradeable is Initializable, IVotingEscrowV2Upgradeable, ERC5725Upgradeable, EscrowDelegateStorage, EIP712Upgradeable, ReentrancyGuard { using SafeERC20Upgradeable for IERC20Upgradeable; using SafeCastLibrary for uint256; using EscrowDelegateCheckpoints for EscrowDelegateCheckpoints.EscrowDelegateStore; enum DepositType { DEPOSIT_FOR_TYPE, CREATE_LOCK_TYPE, INCREASE_LOCK_AMOUNT, INCREASE_UNLOCK_TIME, MERGE_TYPE, SPLIT_TYPE } /// @notice The token being locked IERC20Upgradeable public _token; /// @notice Total locked supply uint256 public supply; uint8 public constant decimals = 18; address public artProxy; /// @notice The EIP-712 typehash for the delegation struct used by the contract bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); /// @notice A record of states for signing / validating signatures mapping(address => uint256) public nonces; /// @dev OpenZeppelin v5 IVotes error error VotesExpiredSignature(uint256 expiry); /** * @notice The constructor is disabled for this upgradeable contract. */ constructor() { /// @dev Disable the initializers for implementation contracts to ensure that the contract is not left uninitialized. _disableInitializers(); } /** * @dev Initializes the contract with the given parameters. * @param _name The name to set for the token. * @param _symbol The symbol to set for the token. * @param version The version of the contract. * @param mainToken The main token address that will be locked in the escrow. * @param _artProxy The address of the art proxy contract. */ function initialize( string memory _name, string memory _symbol, string memory version, IERC20Upgradeable mainToken, address _artProxy ) public initializer { __ERC5725_init(_name, _symbol); __EIP712_init(_name, version); _token = mainToken; artProxy = _artProxy; // Reset MAX_TIME in proxy storage MAX_TIME = uint256(uint128(EscrowDelegateCheckpoints.MAX_TIME)); } modifier checkAuthorized(uint256 _tokenId) { address owner = _ownerOf(_tokenId); if (owner == address(0)) { revert ERC721NonexistentToken(_tokenId); } address sender = _msgSender(); if (!_isAuthorized(owner, sender, _tokenId)) { revert ERC721InsufficientApproval(sender, _tokenId); } _; } /// @dev Returns current token URI metadata /// @param _tokenId Token ID to fetch URI for. function tokenURI(uint _tokenId) public view override validToken(_tokenId) returns (string memory) { LockDetails memory _locked = _lockDetails[_tokenId]; return IVeArtProxy(artProxy)._tokenURI( _tokenId, balanceOfNFT(_tokenId), _locked.endTime, uint(int256(_locked.amount)) ); } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface( bytes4 interfaceId ) public view virtual override(ERC5725Upgradeable, IERC165Upgradeable) returns (bool supported) { return interfaceId == type(IVotingEscrowV2Upgradeable).interfaceId || super.supportsInterface(interfaceId); } /** * @dev See {IERC721-_beforeTokenTransfer}. * Clears the approval of a given `tokenId` when the token is transferred or burned. */ function _beforeTokenTransfer( address from, address to, uint256 firstTokenId, uint256 batchSize ) internal virtual override { super._beforeTokenTransfer(from, to, firstTokenId, batchSize); for (uint256 i = 0; i < batchSize; i++) { uint256 tokenId = firstTokenId + i; if (from != to) { /// @dev Sets delegatee to new owner on transfers (address oldDelegatee, address newDelegatee) = edStore.delegate( tokenId, to, _lockDetails[tokenId].endTime ); emit DelegateChanged(to, oldDelegatee, newDelegatee); emit LockDelegateChanged(tokenId, to, oldDelegatee, newDelegatee); } } } /** * ERC-5725 and token-locking logic */ /// @notice maps the vesting data with tokenIds mapping(uint256 => LockDetails) public _lockDetails; /// @notice tracker of current NFT id uint256 public totalNftsMinted = 0; /** * @notice Creates a new vesting NFT and mints it * @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 duration Duration in seconds of the lock * @param to The receiver of the lock */ function _createLock( uint256 value, uint256 duration, address to, address delegatee, bool permanent, DepositType depositType ) internal virtual returns (uint256) { if (value == 0) revert ZeroAmount(); uint256 unlockTime; totalNftsMinted++; uint256 newTokenId = totalNftsMinted; if (!permanent) { unlockTime = toGlobalClock(block.timestamp + duration); // Locktime is rounded down to global clock (days) if (unlockTime <= block.timestamp) revert LockDurationNotInFuture(); if (unlockTime > block.timestamp + MAX_TIME) revert LockDurationTooLong(); } _safeMint(to, newTokenId); _lockDetails[newTokenId].startTime = block.timestamp; /// @dev Checkpoint created in _updateLock _updateLock(newTokenId, value, unlockTime, _lockDetails[newTokenId], permanent, depositType); edStore.delegate(newTokenId, delegatee, unlockTime); emit LockCreated(newTokenId, delegatee, value, unlockTime, permanent); emit DelegateChanged(to, address(0), delegatee); emit LockDelegateChanged(newTokenId, to, address(0), delegatee); return newTokenId; } /** * @notice Creates a lock for the sender * @param _value The total assets to be locked over time * @param _lockDuration Duration in seconds of the lock * @param _permanent Whether the lock is permanent or not * @return The id of the newly created token */ function createLock( uint256 _value, uint256 _lockDuration, bool _permanent ) external nonReentrant returns (uint256) { return _createLock(_value, _lockDuration, _msgSender(), _msgSender(), _permanent, DepositType.CREATE_LOCK_TYPE); } /** * @notice Creates a lock for a specified address * @param _value The total assets to be locked over time * @param _lockDuration Duration in seconds of the lock * @param _to The receiver of the lock * @param _permanent Whether the lock is permanent or not * @return The id of the newly created token */ function createLockFor( uint256 _value, uint256 _lockDuration, address _to, bool _permanent ) external nonReentrant returns (uint256) { return _createLock(_value, _lockDuration, _to, _to, _permanent, DepositType.CREATE_LOCK_TYPE); } /** * @notice Creates a lock for a specified address * @param _value The total assets to be locked over time * @param _lockDuration Duration in seconds of the lock * @param _to The receiver of the lock * @param _delegatee The receiver of the lock * @param _permanent Whether the lock is permanent or not * @return The id of the newly created token */ function createDelegatedLockFor( uint256 _value, uint256 _lockDuration, address _to, address _delegatee, bool _permanent ) external nonReentrant returns (uint256) { return _createLock(_value, _lockDuration, _to, _delegatee, _permanent, DepositType.CREATE_LOCK_TYPE); } /** * @notice Updates the global checkpoint */ function globalCheckpoint() public nonReentrant { return edStore.globalCheckpoint(); } function checkpoint() external override { globalCheckpoint(); } /** * @notice Updates the checkpoint for a delegatee * @param _delegateeAddress The address of the delegatee */ function checkpointDelegatee(address _delegateeAddress) external nonReentrant { edStore.baseCheckpointDelegatee(_delegateeAddress); } /// @notice Deposit & update lock tokens for a user /// @dev The supply is increased by the _value amount /// @param _tokenId NFT that holds lock /// @param _increasedValue Amount to deposit /// @param _unlockTime New time when to unlock the tokens, or 0 if unchanged /// @param _oldLocked Previous locked amount / timestamp function _updateLock( uint256 _tokenId, uint256 _increasedValue, uint256 _unlockTime, LockDetails memory _oldLocked, bool isPermanent, DepositType depositType ) internal { uint256 supplyBefore = supply; supply += _increasedValue; // Set newLocked to _oldLocked without mangling memory LockDetails memory newLocked; (newLocked.amount, newLocked.startTime, newLocked.endTime, newLocked.isPermanent) = ( _oldLocked.amount, _oldLocked.startTime, _oldLocked.endTime, _oldLocked.isPermanent ); // Adding to existing lock, or if a lock is expired - creating a new one newLocked.amount += _increasedValue; if (_unlockTime != 0 && !isPermanent) { newLocked.endTime = _unlockTime; } if (isPermanent) { newLocked.endTime = 0; newLocked.isPermanent = true; } _lockDetails[_tokenId] = newLocked; emit LockUpdated(_tokenId, _increasedValue, _unlockTime, isPermanent); // Possibilities: // Both _oldLocked.end could be current or expired (>/< block.timestamp) // 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) // newLocked.end > block.timestamp (always) _checkpointLock(_tokenId, _oldLocked, newLocked); if (_increasedValue != 0 && depositType != DepositType.SPLIT_TYPE) { _token.safeTransferFrom(_msgSender(), address(this), _increasedValue); } emit SupplyUpdated(supply, supplyBefore + _increasedValue); } /// @notice Record global and per-user data to checkpoints. Used by VotingEscrow system. /// @param _tokenId NFT token ID. No user checkpoint if 0 /// @param _oldLocked Previous locked amount / end lock time for the user /// @param _newLocked New locked amount / end lock time for the user function _checkpointLock( uint256 _tokenId, IVotingEscrowV2Upgradeable.LockDetails memory _oldLocked, IVotingEscrowV2Upgradeable.LockDetails memory _newLocked ) internal { edStore.checkpoint( _tokenId, _oldLocked.amount.toInt128(), _newLocked.amount.toInt128(), _oldLocked.endTime, _newLocked.endTime ); } /// @notice Deposit `_value` tokens for `_tokenId` and add to the lock /// @dev Anyone (even a smart contract) can deposit for someone else, but /// cannot extend their locktime and deposit for a brand new user /// @param _tokenId lock NFT /// @param _value Amount to add to user's lock function increaseAmount(uint256 _tokenId, uint256 _value) external nonReentrant { if (_value == 0) revert ZeroAmount(); IVotingEscrowV2Upgradeable.LockDetails memory oldLocked = _lockDetails[_tokenId]; if (_ownerOf(_tokenId) == address(0)) revert NoLockFound(); if (oldLocked.endTime <= block.timestamp && !oldLocked.isPermanent) revert LockExpired(); _updateLock(_tokenId, _value, 0, oldLocked, oldLocked.isPermanent, DepositType.INCREASE_LOCK_AMOUNT); } /** * @notice Increases the unlock time of a lock * @param _tokenId The id of the token to increase the unlock time for * @param _lockDuration The new duration of the lock * @param _permanent Whether the lock is permanent or not */ function increaseUnlockTime( uint256 _tokenId, uint256 _lockDuration, bool _permanent ) external nonReentrant checkAuthorized(_tokenId) { LockDetails memory oldLocked = _lockDetails[_tokenId]; if (oldLocked.isPermanent) revert PermanentLock(); uint256 unlockTime; if (!_permanent) { unlockTime = toGlobalClock(block.timestamp + _lockDuration); // Locktime is rounded down to global clock (days) if (oldLocked.endTime <= block.timestamp) revert LockExpired(); if (unlockTime <= oldLocked.endTime) revert LockDurationNotInFuture(); if (unlockTime > block.timestamp + MAX_TIME) revert LockDurationTooLong(); } _updateLock(_tokenId, 0, unlockTime, oldLocked, _permanent, DepositType.INCREASE_UNLOCK_TIME); emit LockDurationExtended(_tokenId, unlockTime, _permanent); } /** * @notice Unlocks a permanent lock * @param _tokenId The id of the token to unlock */ function unlockPermanent(uint256 _tokenId) external nonReentrant checkAuthorized(_tokenId) { LockDetails memory newLocked = _lockDetails[_tokenId]; if (!newLocked.isPermanent) revert NotPermanentLock(); // Set the end time to the maximum possible time newLocked.endTime = toGlobalClock(block.timestamp + MAX_TIME); // Set the lock to not be permanent newLocked.isPermanent = false; // Update the lock details _checkpointLock(_tokenId, _lockDetails[_tokenId], newLocked); _lockDetails[_tokenId] = newLocked; emit UnlockPermanent(_tokenId, _msgSender(), newLocked.endTime); } /** * @notice Claims the payout for a token * @param _tokenId The id of the token to claim the payout for */ function _claim(uint256 _tokenId) internal validToken(_tokenId) nonReentrant checkAuthorized(_tokenId) { IVotingEscrowV2Upgradeable.LockDetails memory oldLocked = _lockDetails[_tokenId]; if (oldLocked.isPermanent) revert PermanentLock(); uint256 amountClaimed = claimablePayout(_tokenId); if (amountClaimed == 0) revert LockNotExpired(); // Reset the lock details _lockDetails[_tokenId] = IVotingEscrowV2Upgradeable.LockDetails(0, 0, 0, false); // Update the total supply uint256 supplyBefore = supply; supply -= amountClaimed; // Update the lock details _checkpointLock(_tokenId, oldLocked, _lockDetails[_tokenId]); /// @notice ERC-5725 event emit PayoutClaimed(_tokenId, msg.sender, amountClaimed); // IERC5725 - Update the total amount claimed _payoutClaimed[_tokenId] += amountClaimed; // Transfer the claimed amount to the sender IERC20Upgradeable(_payoutToken(_tokenId)).safeTransfer(msg.sender, amountClaimed); emit SupplyUpdated(supplyBefore, supply); } /** * @notice Claims the payout for a token * @param _tokenId The id of the token to claim the payout for */ function claim(uint256 _tokenId) external override(ERC5725Upgradeable) { _claim(_tokenId); } /** * @notice Merges two tokens together * @param _from The id of the token to merge from * @param _to The id of the token to merge to */ function merge(uint256 _from, uint256 _to) external nonReentrant checkAuthorized(_from) checkAuthorized(_to) { if (_from == _to) revert SameNFT(); IVotingEscrowV2Upgradeable.LockDetails memory oldLockedTo = _lockDetails[_to]; if (oldLockedTo.amount == 0) revert ZeroAmount(); if (oldLockedTo.endTime <= block.timestamp && !oldLockedTo.isPermanent) revert LockExpired(); IVotingEscrowV2Upgradeable.LockDetails memory oldLockedFrom = _lockDetails[_from]; if (oldLockedFrom.amount == 0) revert ZeroAmount(); if (oldLockedFrom.isPermanent == true && oldLockedFrom.isPermanent != oldLockedTo.isPermanent) revert PermanentLockMismatch(); // Calculate the new end time uint256 end = oldLockedFrom.endTime >= oldLockedTo.endTime ? oldLockedFrom.endTime : oldLockedTo.endTime; // Set lock amount to 0 _lockDetails[_from].amount = 0; // Update the lock details _checkpointLock(_from, oldLockedFrom, _lockDetails[_from]); // Calculate the new lock details LockDetails memory newLockedTo; newLockedTo.amount = oldLockedTo.amount + oldLockedFrom.amount; newLockedTo.isPermanent = oldLockedTo.isPermanent; if (!newLockedTo.isPermanent) { newLockedTo.endTime = end; } // Update the lock details _checkpointLock(_to, oldLockedTo, newLockedTo); _lockDetails[_to] = newLockedTo; emit LockMerged(_from, _to, newLockedTo.amount, end, newLockedTo.isPermanent); } /** * @notice Splits a token into multiple tokens * @param _weights The percentages to split the token into * @param _tokenId The id of the token to split */ function split(uint256[] memory _weights, uint256 _tokenId) external nonReentrant checkAuthorized(_tokenId) { LockDetails memory locked = _lockDetails[_tokenId]; LockDetails storage lockedStorage = _lockDetails[_tokenId]; uint256 currentTime = block.timestamp; /// @dev Pulling directly from locked struct to avoid stack-too-deep if (locked.endTime <= currentTime && !locked.isPermanent) revert LockExpired(); if (locked.amount == 0 || _weights.length < 2) revert ZeroAmount(); // reset supply, _deposit_for increase it supply -= uint256(int256(locked.amount)); // Capture owner for split address owner = _ownerOf(_tokenId); uint256 totalWeight = 0; for (uint256 i = 0; i < _weights.length; i++) { totalWeight += _weights[i]; } uint256 duration = locked.isPermanent ? 0 : locked.endTime > currentTime ? locked.endTime - currentTime : 0; uint256 amountLeftToSplit = locked.amount; for (uint256 i = 0; i < _weights.length; i++) { uint256 value = (uint256(int256(locked.amount)) * _weights[i]) / totalWeight; if(i == _weights.length - 1) { /// @dev Ensure no rounding errors occur by passing the remainder to the last split value = amountLeftToSplit; } amountLeftToSplit -= value; 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); } /** * @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 //////////////////////////////////////////////////////////////*/ function balanceOfNFT(uint256 _tokenId) public view returns (uint256) { return edStore.getAdjustedEscrowBias(_tokenId, block.timestamp); } function balanceOfNFTAt(uint256 _tokenId, uint256 _timestamp) external view returns (uint256) { return edStore.getAdjustedEscrowBias(_tokenId, _timestamp); } function getPastEscrowPoint( uint256 _tokenId, uint256 _timestamp ) external view override returns (Checkpoints.Point memory, uint48) { return edStore.getAdjustedEscrow(_tokenId, _timestamp); } function getFirstEscrowPoint(uint256 _tokenId) external view override returns (Checkpoints.Point memory, uint48) { return edStore.getFirstEscrowPoint(_tokenId); } function totalSupply() public view override(ERC721EnumerableUpgradeable, IERC721EnumerableUpgradeable) returns (uint256) { return edStore.getAdjustedGlobalVotes(block.timestamp.toUint48()); } /*/////////////////////////////////////////////////////////////// @dev See {IVotes}. //////////////////////////////////////////////////////////////*/ /** * @notice Gets the votes for a delegatee * @param account The address of the delegatee * @return The number of votes the delegatee has */ function getVotes(address account) external view override(IVotes) returns (uint256) { return edStore.getAdjustedVotes(account, block.timestamp.toUint48()); } /** * @notice Gets the past votes for a delegatee at a specific time point * @param account The address of the delegatee * @param timepoint The time point to get the votes at * @return The number of votes the delegatee had at the time point */ function getPastVotes(address account, uint256 timepoint) external view override(IVotes) returns (uint256) { return edStore.getAdjustedVotes(account, timepoint.toUint48()); } /** * @notice Gets the total supply at a specific time point * @param _timePoint The time point to get the total supply at * @return The total supply at the time point */ function getPastTotalSupply(uint256 _timePoint) external view override(IVotes) returns (uint256) { return edStore.getAdjustedGlobalVotes(_timePoint.toUint48()); } /** * @notice Delegates votes to a delegatee * @param delegatee The account to delegate votes to */ function delegate(address delegatee) external override(IVotes) { _delegate(_msgSender(), delegatee); } /** * @notice Gets the delegate of a delegatee * @dev This function implements IVotes interface. * An account can have multiple delegates in this contract. If multiple * different delegates are found, this function returns address(1) to * indicate that there is not a single unique delegate. * @param account The delegatee to get the delegate of * @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) { address delegatee = address(0); uint256 balance = balanceOf(account); /// @dev out-of-gas protection uint256 runs = 50 > balance ? balance : 50; for (uint256 i = 0; i < runs; i++) { uint256 tokenId = tokenOfOwnerByIndex(account, i); address currentDelegatee = edStore.getEscrowDelegatee(tokenId); /// @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 specific lock to a delegatee * @param _tokenId The ID of the lock token delegating the votes * @param delegatee The address to which the votes are being delegated */ function delegate(uint256 _tokenId, address delegatee) external checkAuthorized(_tokenId) { (address fromDelegatee, address toDelegatee) = edStore.delegate( _tokenId, delegatee, _lockDetails[_tokenId].endTime ); emit LockDelegateChanged(_tokenId, _msgSender(), fromDelegatee, toDelegatee); } /** * @notice Gets the delegatee of a given lock * @param tokenId The ID of the lock token * @return The address of the delegatee for the specified token */ function getLockDelegatee(uint256 tokenId) external view returns (address) { return edStore.getEscrowDelegatee(tokenId); } /** * @notice Gets all delegates of a delegatee * @param account The delegatee to get the delegates of * @return An array of all delegates of the delegatee */ function getAccountDelegates(address account) external view returns (address[] memory) { uint256 balance = balanceOf(account); address[] memory allDelegates = new address[](balance); for (uint256 i = 0; i < balance; i++) { uint256 tokenId = tokenOfOwnerByIndex(account, i); allDelegates[i] = edStore.getEscrowDelegatee(tokenId); } return allDelegates; } /** * @notice Public function to get the delegatee of a lock * @param tokenId The ID of the token * @param timestamp The timestamp to get the delegate at * @return The address of the delegate */ function delegates(uint256 tokenId, uint48 timestamp) external view returns (address) { return edStore.getEscrowDelegateeAtTime(tokenId, timestamp); } /** * @notice Delegates votes by signature * @param delegatee The delegatee to delegate votes to * @param nonce The nonce for the signature * @param expiry The expiry time for the signature * @param v The recovery byte of the signature * @param r Half of the ECDSA signature pair * @param s Half of the ECDSA signature pair */ function delegateBySig( address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s ) external override(IVotes) { // Removed for gas considerations. The code below uncommented adds 1.289 kbs to the contract size. revert("delegateBySig: size cut"); /* if (delegatee == msg.sender || delegatee == address(0)) revert InvalidDelegatee(); bytes32 domainSeparator = _domainSeparatorV4(); bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry)); bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); address signatory = ECDSA.recover(digest, v, r, s); if (signatory == address(0)) revert InvalidSignature(); if (nonce != nonces[signatory]++) revert InvalidNonce(); if (block.timestamp > expiry) revert VotesExpiredSignature(expiry); return _delegate(signatory, delegatee); */ } /** * @notice Delegates votes from an owner to an delegatee * @param delegator The owner of the tokenId delegating votes * @param delegatee The account to delegate votes to */ function _delegate(address delegator, address delegatee) internal nonReentrant { uint256 balance = balanceOf(delegator); address fromDelegate = address(0); for (uint256 i = 0; i < balance; i++) { uint256 tokenId = tokenOfOwnerByIndex(delegator, i); (address oldDelegate, address newDelegate) = edStore.delegate( tokenId, delegatee, _lockDetails[tokenId].endTime ); emit LockDelegateChanged(tokenId, delegator, oldDelegate, newDelegate); /// @dev Hacky way to check if the delegatee is the same for all locks if (fromDelegate == address(0)) { fromDelegate = oldDelegate; } else if (fromDelegate != address(1)) { if (fromDelegate != oldDelegate) { fromDelegate = address(1); } } } emit DelegateChanged(delegator, fromDelegate, delegatee); } /*/////////////////////////////////////////////////////////////// @dev See {IERC5725}. //////////////////////////////////////////////////////////////*/ /** * @dev See {ERC5725Upgradeable}. */ function vestedPayoutAtTime( uint256 tokenId, uint256 timestamp ) public view override(ERC5725Upgradeable) validToken(tokenId) returns (uint256 payout) { if (timestamp >= _endTime(tokenId)) { return _payout(tokenId); } return 0; } /** * @dev See {ERC5725Upgradeable}. */ function _payoutToken(uint256 /*tokenId*/) internal view override returns (address) { return address(_token); } /** * @dev See {ERC5725Upgradeable}. */ function _payout(uint256 tokenId) internal view override returns (uint256) { return _lockDetails[tokenId].amount; } /** * @dev See {ERC5725Upgradeable}. */ function _startTime(uint256 tokenId) internal view override returns (uint256) { return _lockDetails[tokenId].startTime; } /** * @dev See {ERC5725Upgradeable}. */ function _endTime(uint256 tokenId) internal view override returns (uint256) { return _lockDetails[tokenId].endTime; } function token() external view returns (IERC20Upgradeable) { return _token; } function lockDetails(uint256 _tokenId) external view returns (LockDetails memory) { return _lockDetails[_tokenId]; } function isApprovedOrOwner(address user, uint tokenId) external view returns (bool) { return _isAuthorized(ownerOf(tokenId), user, tokenId); } }
尋找差異