Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-5058:Lockable ERC-721 Standard #5058

Merged
merged 48 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
72cafb2
EIP-5058:Lockable ERC-721 Standard
radiocaca Apr 29, 2022
4696368
update eip
radiocaca May 1, 2022
49c31ff
Update eip-5058.md
radiocaca May 4, 2022
6b21dd1
Update eip-5058.md proposal language
sfumato00 May 10, 2022
1e4c9a0
Update eip-5058.md proposal language
sfumato00 May 10, 2022
47a04f3
Merge pull request #1 from sfumato00/master
radiocaca May 10, 2022
1e5f664
Update eip-5058.md
radiocaca May 10, 2022
fa18ed6
Update eip-5058.md
radiocaca May 10, 2022
a49ef35
upload example
radiocaca May 11, 2022
07f7fe6
Update assets/eip-5058/factory/IERC721Bound.sol
radiocaca May 11, 2022
456c60e
Update assets/eip-5058/ERC721Lockable.sol
radiocaca May 11, 2022
cdbe439
Update assets/eip-5058/IERC721Lockable.sol
radiocaca May 11, 2022
2a7e737
Update assets/eip-5058/factory/IEIP5058Factory.sol
radiocaca May 11, 2022
fcd52e2
Update assets/eip-5058/factory/ERC721Bound.sol
radiocaca May 11, 2022
5415e9c
Update assets/eip-5058/factory/EIP5058Factory.sol
radiocaca May 11, 2022
2c29f8c
Update assets/eip-5058/extensions/EIP5058Bound.sol
radiocaca May 11, 2022
82b6584
rename to ERC5058
radiocaca May 11, 2022
9676777
update revert domain
radiocaca May 11, 2022
9002b06
Update EIPS/eip-5058.md
radiocaca May 13, 2022
06d1955
Update EIPS/eip-5058.md
radiocaca May 17, 2022
38c75b8
Update EIPS/eip-5058.md
radiocaca May 17, 2022
135a48b
remove all external links and add lockExpiredTime function
radiocaca Jul 31, 2022
df1c064
add tests
radiocaca Jul 31, 2022
9999772
rename from to owner
radiocaca Jul 31, 2022
9610e08
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
ba9ebea
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
552e313
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
e5f1160
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
3f3d037
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
9d3980a
Update assets/eip-5058/IERC5058.sol
radiocaca Aug 3, 2022
286289b
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
eb1fc00
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
724e277
Update EIPS/eip-5058.md
radiocaca Aug 3, 2022
4810780
Apply suggestions from code review
radiocaca Aug 3, 2022
051e456
Apply suggestions from code review
radiocaca Aug 3, 2022
9a95d53
fix lockFrom
radiocaca Aug 3, 2022
21731b2
update md
radiocaca Aug 3, 2022
adaad9e
fix lockFrom tests
radiocaca Aug 3, 2022
0c6ee82
remove oz deps
radiocaca Aug 14, 2022
2a29457
modify md for eip change
radiocaca Aug 15, 2022
18afdec
refine tests
radiocaca Aug 17, 2022
ade97bf
Merge branch 'master' into master
radiocaca Aug 18, 2022
c0dce9b
Merge branch 'master' into master
radiocaca Aug 18, 2022
d68547a
Merge branch 'master' into master
radiocaca Aug 18, 2022
08db345
Merge branch 'master' into master
radiocaca Aug 19, 2022
2cec9fa
Merge branch 'master' into master
radiocaca Aug 22, 2022
e822eeb
Merge branch 'master' into master
radiocaca Aug 25, 2022
78f8908
Merge branch 'master' into master
Pandapip1 Aug 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion EIPS/eip-5058.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ Test case written using hardhat: [here](https://github.com/radiocaca/ERC721L/blo

## Reference Implementation

You can find an implementation of this standard in the assets folder: [RadioCaca](https://github.com/radiocaca/ERC721L/tree/main/contracts/EIP5058).
You can find an implementation of this standard in the [assets](../assets/eip-5058) folder.
radiocaca marked this conversation as resolved.
Show resolved Hide resolved

## Security Considerations

Expand Down
274 changes: 274 additions & 0 deletions assets/eip-5058/ERC721Lockable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
// SPDX-License-Identifier: MIT
radiocaca marked this conversation as resolved.
Show resolved Hide resolved
// Creator: tyler@radiocaca.com

pragma solidity ^0.8.8;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./IERC721Lockable.sol";

/**
* @dev Implementation ERC721 Lockable Token
*/
abstract contract ERC721Lockable is ERC721, IERC721Lockable {
// 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 {IERC721Lockable-lockApprove}.
*/
function lockApprove(address to, uint256 tokenId) public virtual override {
require(!isLocked(tokenId), "ERC721L: token is locked");
address owner = ERC721.ownerOf(tokenId);
require(to != owner, "ERC721L: lock approval to current owner");

require(
_msgSender() == owner || isLockApprovedForAll(owner, _msgSender()),
"ERC721L: lock approve caller is not owner nor approved for all"
);

_lockApprove(owner, to, tokenId);
}

/**
* @dev See {IERC721Lockable-getLockApproved}.
*/
function getLockApproved(uint256 tokenId) public view virtual override returns (address) {
require(_exists(tokenId), "ERC721L: lock approved query for nonexistent token");

return _lockApprovals[tokenId];
}

/**
* @dev See {IERC721Lockable-lockerOf}.
*/
function lockerOf(uint256 tokenId) public view virtual override returns (address) {
require(_exists(tokenId), "ERC721L: locker query for nonexistent token");
require(isLocked(tokenId), "ERC721L: locker query for non-locked token");

return _lockApprovals[tokenId];
}

/**
* @dev See {IERC721Lockable-setLockApprovalForAll}.
*/
function setLockApprovalForAll(address operator, bool approved) public virtual override {
_setLockApprovalForAll(_msgSender(), operator, approved);
}

/**
* @dev See {IERC721Lockable-isLockApprovedForAll}.
*/
function isLockApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _lockOperatorApprovals[owner][operator];
}

/**
* @dev See {IERC721Lockable-isLocked}.
*/
function isLocked(uint256 tokenId) public view virtual override returns (bool) {
return lockedTokens[tokenId] > block.number;
}

/**
* @dev See {IERC721Lockable-lockFrom}.
*/
function lockFrom(
address from,
uint256 tokenId,
uint256 expired
) public virtual override {
//solhint-disable-next-line max-line-length
require(_isLockApprovedOrOwner(_msgSender(), tokenId), "ERC721L: lock caller is not owner nor approved");
require(expired > block.number, "ERC721L: expired time must be greater than current block number");
require(!isLocked(tokenId), "ERC721L: token is locked");

_lock(_msgSender(), from, tokenId, expired);
}

/**
* @dev See {IERC721Lockable-unlockFrom}.
*/
function unlockFrom(address from, uint256 tokenId) public virtual override {
require(lockerOf(tokenId) == _msgSender(), "ERC721L: unlock caller is not lock operator");
require(ERC721.ownerOf(tokenId) == from, "ERC721L: unlock from incorrect owner");

_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,
address from,
uint256 tokenId,
uint256 expired
) internal virtual {
require(ERC721.ownerOf(tokenId) == from, "ERC721L: lock from incorrect owner");

_beforeTokenLock(operator, from, tokenId, expired);

lockedTokens[tokenId] = expired;
_lockApprovals[tokenId] = _msgSender();

emit Locked(operator, from, tokenId, expired);

_afterTokenLock(operator, from, 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, "ERC721L: lock mint for invalid lock block number");

_safeMint(to, tokenId, _data);

_lock(address(0), to, 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, "ERC721L: 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), "ERC721L: 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), "ERC721L: token transfer while locked");
}

/**
* @dev Hook that is called before any token lock/unlock.
*
* Calling conditions:
*
* - `from` 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 from,
uint256 tokenId,
uint256 expired
) internal virtual {}

/**
* @dev Hook that is called after any lock/unlock of tokens.
*
* Calling conditions:
*
* - `from` 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 from,
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(IERC721Lockable).interfaceId || super.supportsInterface(interfaceId);
}
}
Loading