276 lines
8.1 KiB
Solidity
276 lines
8.1 KiB
Solidity
// SPDX-License-Identifier: CC0-1.0
|
|
|
|
pragma solidity ^0.8.0;
|
|
|
|
import "./IERC5058.sol";
|
|
|
|
/**
|
|
* @dev Implementation ERC721 Lockable Token
|
|
*/
|
|
abstract contract ERC5058 is ERC721, IERC5058 {
|
|
// Mapping from token ID to unlock time
|
|
mapping(uint256 => uint256) public lockedTokens;
|
|
|
|
// Mapping from token ID to lock approved address
|
|
mapping(uint256 => address) private _lockApprovals;
|
|
|
|
// Mapping from owner to lock operator approvals
|
|
mapping(address => mapping(address => bool)) private _lockOperatorApprovals;
|
|
|
|
/**
|
|
* @dev See {IERC5058-lockApprove}.
|
|
*/
|
|
function lockApprove(address to, uint256 tokenId) public virtual override {
|
|
require(!isLocked(tokenId), "ERC5058: token is locked");
|
|
address owner = ERC721.ownerOf(tokenId);
|
|
require(to != owner, "ERC5058: lock approval to current owner");
|
|
|
|
require(
|
|
_msgSender() == owner || isLockApprovedForAll(owner, _msgSender()),
|
|
"ERC5058: lock approve caller is not owner nor approved for all"
|
|
);
|
|
|
|
_lockApprove(owner, to, tokenId);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC5058-getLockApproved}.
|
|
*/
|
|
function getLockApproved(uint256 tokenId) public view virtual override returns (address) {
|
|
require(_exists(tokenId), "ERC5058: lock approved query for nonexistent token");
|
|
|
|
return _lockApprovals[tokenId];
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC5058-lockerOf}.
|
|
*/
|
|
function lockerOf(uint256 tokenId) public view virtual override returns (address) {
|
|
require(_exists(tokenId), "ERC5058: locker query for nonexistent token");
|
|
require(isLocked(tokenId), "ERC5058: locker query for non-locked token");
|
|
|
|
return _lockApprovals[tokenId];
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC5058-setLockApprovalForAll}.
|
|
*/
|
|
function setLockApprovalForAll(address operator, bool approved) public virtual override {
|
|
_setLockApprovalForAll(_msgSender(), operator, approved);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC5058-isLockApprovedForAll}.
|
|
*/
|
|
function isLockApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
|
|
return _lockOperatorApprovals[owner][operator];
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC5058-isLocked}.
|
|
*/
|
|
function isLocked(uint256 tokenId) public view virtual override returns (bool) {
|
|
return lockedTokens[tokenId] > block.number;
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC5058-lockExpiredTime}.
|
|
*/
|
|
function lockExpiredTime(uint256 tokenId) public view virtual override returns (uint256) {
|
|
return lockedTokens[tokenId];
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC5058-lock}.
|
|
*/
|
|
function lock(uint256 tokenId, uint256 expired) public virtual override {
|
|
//solhint-disable-next-line max-line-length
|
|
require(_isLockApprovedOrOwner(_msgSender(), tokenId), "ERC5058: lock caller is not owner nor approved");
|
|
require(expired > block.number, "ERC5058: expired time must be greater than current block number");
|
|
require(!isLocked(tokenId), "ERC5058: token is locked");
|
|
|
|
_lock(_msgSender(), tokenId, expired);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC5058-unlock}.
|
|
*/
|
|
function unlock(uint256 tokenId) public virtual override {
|
|
require(lockerOf(tokenId) == _msgSender(), "ERC5058: unlock caller is not lock operator");
|
|
|
|
address from = ERC721.ownerOf(tokenId);
|
|
|
|
_beforeTokenLock(_msgSender(), from, tokenId, 0);
|
|
|
|
delete lockedTokens[tokenId];
|
|
|
|
emit Unlocked(_msgSender(), from, tokenId);
|
|
|
|
_afterTokenLock(_msgSender(), from, tokenId, 0);
|
|
}
|
|
|
|
/**
|
|
* @dev Locks `tokenId` from `from` until `expired`.
|
|
*
|
|
* Requirements:
|
|
*
|
|
* - `tokenId` token must be owned by `from`.
|
|
*
|
|
* Emits a {Locked} event.
|
|
*/
|
|
function _lock(
|
|
address operator,
|
|
uint256 tokenId,
|
|
uint256 expired
|
|
) internal virtual {
|
|
address owner = ERC721.ownerOf(tokenId);
|
|
|
|
_beforeTokenLock(operator, owner, tokenId, expired);
|
|
|
|
lockedTokens[tokenId] = expired;
|
|
_lockApprovals[tokenId] = operator;
|
|
|
|
emit Locked(operator, owner, tokenId, expired);
|
|
|
|
_afterTokenLock(operator, owner, tokenId, expired);
|
|
}
|
|
|
|
/**
|
|
* @dev Safely mints `tokenId` and transfers it to `to`, but the `tokenId` is locked and cannot be transferred.
|
|
*
|
|
* Requirements:
|
|
*
|
|
* - `tokenId` must not exist.
|
|
*
|
|
* Emits {Locked} and {Transfer} event.
|
|
*/
|
|
function _safeLockMint(
|
|
address to,
|
|
uint256 tokenId,
|
|
uint256 expired,
|
|
bytes memory _data
|
|
) internal virtual {
|
|
require(expired > block.number, "ERC5058: lock mint for invalid lock block number");
|
|
|
|
_safeMint(to, tokenId, _data);
|
|
|
|
_lock(_msgSender(), tokenId, expired);
|
|
}
|
|
|
|
/**
|
|
* @dev See {ERC721-_burn}. This override additionally clears the lock approvals for the token.
|
|
*/
|
|
function _burn(uint256 tokenId) internal virtual override {
|
|
address owner = ERC721.ownerOf(tokenId);
|
|
super._burn(tokenId);
|
|
|
|
_beforeTokenLock(_msgSender(), owner, tokenId, 0);
|
|
|
|
// clear lock approvals
|
|
delete lockedTokens[tokenId];
|
|
delete _lockApprovals[tokenId];
|
|
|
|
_afterTokenLock(_msgSender(), owner, tokenId, 0);
|
|
}
|
|
|
|
/**
|
|
* @dev Approve `to` to lock operate on `tokenId`
|
|
*
|
|
* Emits a {LockApproval} event.
|
|
*/
|
|
function _lockApprove(
|
|
address owner,
|
|
address to,
|
|
uint256 tokenId
|
|
) internal virtual {
|
|
_lockApprovals[tokenId] = to;
|
|
emit LockApproval(owner, to, tokenId);
|
|
}
|
|
|
|
/**
|
|
* @dev Approve `operator` to lock operate on all of `owner` tokens
|
|
*
|
|
* Emits a {LockApprovalForAll} event.
|
|
*/
|
|
function _setLockApprovalForAll(
|
|
address owner,
|
|
address operator,
|
|
bool approved
|
|
) internal virtual {
|
|
require(owner != operator, "ERC5058: lock approve to caller");
|
|
_lockOperatorApprovals[owner][operator] = approved;
|
|
emit LockApprovalForAll(owner, operator, approved);
|
|
}
|
|
|
|
/**
|
|
* @dev Returns whether `spender` is allowed to lock `tokenId`.
|
|
*
|
|
* Requirements:
|
|
*
|
|
* - `tokenId` must exist.
|
|
*/
|
|
function _isLockApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
|
|
require(_exists(tokenId), "ERC5058: lock operator query for nonexistent token");
|
|
address owner = ERC721.ownerOf(tokenId);
|
|
return (spender == owner || isLockApprovedForAll(owner, spender) || getLockApproved(tokenId) == spender);
|
|
}
|
|
|
|
/**
|
|
* @dev See {ERC721-_beforeTokenTransfer}.
|
|
*
|
|
* Requirements:
|
|
*
|
|
* - the `tokenId` must not be locked.
|
|
*/
|
|
function _beforeTokenTransfer(
|
|
address from,
|
|
address to,
|
|
uint256 tokenId
|
|
) internal virtual override {
|
|
super._beforeTokenTransfer(from, to, tokenId);
|
|
|
|
require(!isLocked(tokenId), "ERC5058: token transfer while locked");
|
|
}
|
|
|
|
/**
|
|
* @dev Hook that is called before any token lock/unlock.
|
|
*
|
|
* Calling conditions:
|
|
*
|
|
* - `owner` is non-zero.
|
|
* - When `expired` is zero, `tokenId` will be unlock for `from`.
|
|
* - When `expired` is non-zero, ``from``'s `tokenId` will be locked.
|
|
*
|
|
*/
|
|
function _beforeTokenLock(
|
|
address operator,
|
|
address owner,
|
|
uint256 tokenId,
|
|
uint256 expired
|
|
) internal virtual {}
|
|
|
|
/**
|
|
* @dev Hook that is called after any lock/unlock of tokens.
|
|
*
|
|
* Calling conditions:
|
|
*
|
|
* - `owner` is non-zero.
|
|
* - When `expired` is zero, `tokenId` will be unlock for `from`.
|
|
* - When `expired` is non-zero, ``from``'s `tokenId` will be locked.
|
|
*
|
|
*/
|
|
function _afterTokenLock(
|
|
address operator,
|
|
address owner,
|
|
uint256 tokenId,
|
|
uint256 expired
|
|
) internal virtual {}
|
|
|
|
/**
|
|
* @dev See {IERC165-supportsInterface}.
|
|
*/
|
|
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
|
|
return interfaceId == type(IERC5058).interfaceId || super.supportsInterface(interfaceId);
|
|
}
|
|
}
|