forked from ethereum/EIPs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ERC-5006: Change Status to Review (ethereum#5147)
* Change Status to Review * update by Pandapip1's suggestion Co-authored-by: Anders <[email protected]>
- Loading branch information
1 parent
1bb7f9f
commit 9b7155c
Showing
9 changed files
with
391 additions
and
235 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
--- | ||
eip: 5006 | ||
title: ERC-1155 Usage Rights Extension | ||
description: Add a user role with restricted permissions to ERC-1155 tokens. | ||
title: Rental NFT, NFT User Extension | ||
description: Add a user role with restricted permissions to EIP-1155 tokens | ||
author: Lance (@LanceSnow), Anders (@0xanders), Shrug <[email protected]> | ||
discussions-to: https://ethereum-magicians.org/t/eip5006-erc-1155-usage-rights-extension/8941 | ||
status: Draft | ||
status: Review | ||
type: Standards Track | ||
category: ERC | ||
created: 2022-04-12 | ||
|
@@ -13,11 +13,11 @@ requires: 165, 1155 | |
|
||
## Abstract | ||
|
||
This standard is an extension of ERC-1155. It proposes an additional role (`user`) which can be granted to addresses that represent a `user` of the assets rather than an `owner`. | ||
This standard is an extension of [EIP-1155](./eip-1155.md). It proposes an additional role (`user`) which can be granted to addresses that represent a `user` of the assets rather than an `owner`. | ||
|
||
## Motivation | ||
|
||
Like ERC-721, ERC-1155 tokens may have utility of some kind. The people who “use” the token may be different than the people who own it (such as in a rental). Thus, it would be useful to have separate roles for the “owner” and the “user” so that the “user” would not be able to take actions that the owner could (for example, transferring ownership). | ||
Like [EIP-721](./eip-721.md), [EIP-1155](./eip-1155.md) tokens may have utility of some kind. The people who “use” the token may be different than the people who own it (such as in a rental). Thus, it would be useful to have separate roles for the “owner” and the “user” so that the “user” would not be able to take actions that the owner could (for example, transferring ownership). | ||
|
||
## Specification | ||
|
||
|
@@ -29,6 +29,18 @@ pragma solidity ^0.8.0; | |
import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; | ||
interface IERC5006 is IERC165 { | ||
/** | ||
* @dev Logged when the user of a NFT is changed | ||
* | ||
* Requirements: | ||
* | ||
* - `operator` msg.sender | ||
* - `from` the address that change usage rights from | ||
* - `to` the address that change usage rights to | ||
* - `id` token id | ||
* - `value` token amount | ||
*/ | ||
event UpdateUser( | ||
address indexed operator, | ||
address indexed from, | ||
|
@@ -68,18 +80,21 @@ interface IERC5006 is IERC165 { | |
* | ||
* - `user` cannot be the zero address. | ||
* - `owner` cannot be the zero address. | ||
*/ | ||
*/ | ||
function balanceOfUserFromOwner( | ||
address user, | ||
address owner, | ||
uint256 id | ||
) external view returns (uint256); | ||
/// @notice set the user of a NFT | ||
/// @dev The zero address indicates there is no user | ||
/// Throws if `tokenId` is not valid NFT | ||
/// @param user The new user of the NFT | ||
/// @param amount The new user could use | ||
/** | ||
* @dev set the `user` of a NFT | ||
* | ||
* Requirements: | ||
* | ||
* - `user` The new user of the NFT, the zero address indicates there is no user | ||
* - `amount` The new user could use | ||
*/ | ||
function setUser( | ||
address owner, | ||
address user, | ||
|
@@ -94,249 +109,38 @@ The `supportsInterface` method MUST return `true` when called with `0x17fa3123`. | |
|
||
## Rationale | ||
|
||
This model is intended to facilitate easy implementation. Here are some of the problems that are solved by this standard. | ||
This model is intended to facilitate easy implementation. The following are some problems that are solved by this standard: | ||
|
||
### Clear Rights Assignment | ||
|
||
With Dual “owner” and “user” roles, it becomes significantly easier to manage what lenders and borrowers can and cannot do with the NFT (in other words, their rights). For example, for the right to transfer ownership, the project simply needs to check whether the address taking the action represents the owner or the user and prevent the transaction if it’s the user. Additionally, owners can control who the user is and it’s easy for other projects to assign their own rights to either the owners or the users. | ||
With Dual “owner” and “user” roles, it becomes significantly easier to manage what lenders and borrowers can and cannot do with the NFT (in other words, their rights). For example, for the right to transfer ownership, the project simply needs to check whether the address taking the action represents the owner or the user and prevent the transaction if it is the user. Additionally, owners can control who the user is and it is easy for other projects to assign their own rights to either the owners or the users. | ||
|
||
### Easy Third-Party Integration | ||
|
||
In the spirit of permissionless interoperability, this standard makes it easier for third-party protocols to manage NFT usage rights without permission from the NFT issuer or the NFT application. Once a project has adopted the additional `user` role and `expires`, any other project can directly interact with these features and implement their own type of transaction. For example, a PFP NFT using this standard can be integrated into both a rental platform where users can rent the NFT for 30 days AND, at the same time, a mortgage platform where users can use the NFT while eventually buying ownership of the NFT with installment payments. This would all be done without needing the permission of the original PFP project. | ||
In the spirit of permissionless interoperability, this standard makes it easier for third-party protocols to manage NFT usage rights without permission from the NFT issuer or the NFT application. Once a project has adopted the additional `user` role, any other project can directly interact with these features and implement their own type of transaction. For example, a PFP NFT using this standard can be integrated into both a rental platform where users can rent the NFT for 30 days AND, at the same time, a mortgage platform where users can use the NFT while eventually buying ownership of the NFT with installment payments. This would all be done without needing the permission of the original PFP project. | ||
|
||
## Backwards Compatibility | ||
|
||
As mentioned in the specifications section, this standard can be fully ERC compatible by adding an extension function set. | ||
As mentioned in the specifications section, this standard can be fully ERC compatible by adding an extension function set, and there are no conflicts between EIP-5006 and EIP-1155. | ||
|
||
In addition, new functions introduced in this standard have many similarities with the existing functions in ERC-1155. This allows developers to easily adopt the standard quickly. | ||
In addition, new functions introduced in this standard have many similarities with the existing functions in EIP-1155. This allows developers to easily adopt the standard quickly. | ||
|
||
## Test Cases | ||
|
||
### Test contract: | ||
```solidity | ||
// SPDX-License-Identifier: CC0-1.0 | ||
pragma solidity ^0.8.0; | ||
import "./ERC5006Demo.sol"; | ||
contract ERC5006Demo is ERC5006 { | ||
function mint( | ||
address to, | ||
uint256 id, | ||
uint256 amount | ||
) public { | ||
_mint(to, id, amount, ""); | ||
} | ||
function burn( | ||
address from, | ||
uint256 id, | ||
uint256 amount | ||
) public { | ||
_burn(from, id, amount); | ||
} | ||
function getInterface() public pure returns (bytes4) { | ||
return type(IERC5006).interfaceId; | ||
} | ||
} | ||
``` | ||
|
||
### Test Code | ||
Run in terminal: ```npm hardhat test``` | ||
|
||
```TypeScript | ||
import { expect } from "chai"; | ||
import { ethers } from "hardhat"; | ||
|
||
describe("Test ERC5006", function () { | ||
let alice, bob, carl; | ||
let contract; | ||
|
||
beforeEach(async function () { | ||
[alice, bob, carl] = await ethers.getSigners(); | ||
|
||
const ERC5006Demo = await ethers.getContractFactory("ERC5006Demo"); | ||
|
||
contract = await ERC5006Demo.deploy(); | ||
}); | ||
|
||
describe("", function () { | ||
it("Should set user to bob", async function () { | ||
|
||
await contract.mint(alice.address, 1, 100); | ||
|
||
await contract.setUser(alice.address, bob.address, 1, 10); | ||
|
||
expect(await contract.balanceOfUser(bob.address, 1)).equals(10); | ||
|
||
expect(await contract.balanceOfUserFromOwner(bob.address, alice.address, 1)).equals(10); | ||
|
||
expect(await contract.frozenOfOwner(alice.address, 1)).equals(10); | ||
|
||
await contract.setUser(alice.address, bob.address, 1, 80); | ||
|
||
expect(await contract.balanceOfUser(bob.address, 1)).equals(80); | ||
|
||
expect(await contract.balanceOfUserFromOwner(bob.address, alice.address, 1)).equals(80); | ||
|
||
expect(await contract.frozenOfOwner(alice.address, 1)).equals(80); | ||
|
||
await contract.setUser(alice.address, bob.address, 1, 0); | ||
|
||
expect(await contract.balanceOfUser(bob.address, 1)).equals(0); | ||
|
||
expect(await contract.balanceOfUserFromOwner(bob.address, alice.address, 1)).equals(0); | ||
|
||
expect(await contract.frozenOfOwner(alice.address, 1)).equals(0); | ||
|
||
}); | ||
|
||
it("Should transfer success", async function () { | ||
Test cases are included in [test.js](../assets/eip-5006/test/test.ts). | ||
|
||
await contract.mint(alice.address, 1, 100); | ||
|
||
await contract.setUser(alice.address, bob.address, 1, 10); | ||
|
||
await contract.safeTransferFrom(alice.address, carl.address, 1, 90, "0x"); | ||
|
||
expect(await contract.balanceOfUser(bob.address, 1)).equals(10); | ||
|
||
expect(await contract.balanceOfUserFromOwner(bob.address, alice.address, 1)).equals(10); | ||
|
||
expect(await contract.frozenOfOwner(alice.address, 1)).equals(10); | ||
|
||
expect(await contract.balanceOf(alice.address, 1)).equals(10); | ||
|
||
}); | ||
|
||
it("Should burn success", async function () { | ||
|
||
await contract.mint(alice.address, 1, 100); | ||
|
||
await contract.setUser(alice.address, bob.address, 1, 10); | ||
|
||
await contract.burn(alice.address, 1, 90); | ||
|
||
expect(await contract.balanceOfUser(bob.address, 1)).equals(10); | ||
|
||
expect(await contract.balanceOfUserFromOwner(bob.address, alice.address, 1)).equals(10); | ||
|
||
expect(await contract.frozenOfOwner(alice.address, 1)).equals(10); | ||
|
||
expect(await contract.balanceOf(alice.address, 1)).equals(10); | ||
}); | ||
|
||
}); | ||
}); | ||
``` | ||
Run in terminal: | ||
1. ```cd ../assets/eip-5006``` | ||
1. ```npm install``` | ||
1. ```npx hardhat test ./test/test.ts``` | ||
|
||
## Reference Implementation | ||
|
||
```solidity | ||
// SPDX-License-Identifier: CC0-1.0 | ||
pragma solidity ^0.8.0; | ||
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; | ||
import "./IERC5006.sol"; | ||
contract ERC5006 is ERC1155, IERC5006 { | ||
/**mapping(tokenId=>mapping(user=>amount)) */ | ||
mapping(uint256 => mapping(address => uint256)) private _userAllowances; | ||
/**mapping(tokenId=>mapping(owner=>amount)) */ | ||
mapping(uint256 => mapping(address => uint256)) private _frozen; | ||
/** mapping(tokenId=>mapping(owner=>mapping(user=>amount))) */ | ||
mapping(uint256 => mapping(address => mapping(address => uint256))) | ||
private _allowances; | ||
constructor() ERC1155("") {} | ||
function balanceOfUser(address user, uint256 id) | ||
public | ||
view | ||
returns (uint256) | ||
{ | ||
return _userAllowances[id][user]; | ||
} | ||
function balanceOfUserFromOwner( | ||
address user, | ||
address owner, | ||
uint256 id | ||
) public view returns (uint256) { | ||
return _allowances[id][owner][user]; | ||
} | ||
function frozenOfOwner(address owner, uint256 id) | ||
external | ||
view | ||
returns (uint256) | ||
{ | ||
return _frozen[id][owner]; | ||
} | ||
function setUser( | ||
address owner, | ||
address user, | ||
uint256 id, | ||
uint256 amount | ||
) public virtual { | ||
require(user != address(0), "ERROR: transfer to the zero address"); | ||
address operator = msg.sender; | ||
uint256 fromBalance = balanceOf(owner, id); | ||
_frozen[id][owner] -= _allowances[id][owner][user]; | ||
uint256 frozen = _frozen[id][owner]; | ||
require( | ||
fromBalance - frozen >= amount, | ||
"ERROR: insufficient balance for setUser" | ||
); | ||
unchecked { | ||
_frozen[id][owner] = frozen + amount; | ||
} | ||
_userAllowances[id][user] -= _allowances[id][owner][user]; | ||
_userAllowances[id][user] += amount; | ||
_allowances[id][owner][user] = amount; | ||
emit UpdateUser(operator, owner, user, id, amount); | ||
} | ||
function _beforeTokenTransfer( | ||
address operator, | ||
address from, | ||
address to, | ||
uint256[] memory ids, | ||
uint256[] memory amounts, | ||
bytes memory data | ||
) internal virtual override { | ||
for (uint256 i = 0; i < ids.length; i++) { | ||
if (from != address(0)) { | ||
uint256 id = ids[i]; | ||
uint256 fromBalance = balanceOf(from, id); | ||
uint256 frozen = _frozen[id][from]; | ||
require( | ||
fromBalance - frozen >= amounts[i], | ||
"ERROR: insufficient balance for transfer" | ||
); | ||
} | ||
} | ||
} | ||
/// @dev See {IERC165-supportsInterface}. | ||
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC1155) returns (bool) { | ||
return interfaceId == type(IERC5006).interfaceId || super.supportsInterface(interfaceId); | ||
} | ||
} | ||
``` | ||
See [ERC5006.sol](../assets/eip-5006/contracts/ERC5006.sol). | ||
|
||
## Security Considerations | ||
|
||
This EIP standard can completely protect the rights of the owner, the owner can change the NFT user. | ||
This EIP standard can completely protect the rights of the owner, the owner can change the NFT user, the user can not transfer the NFT. | ||
|
||
## Copyright | ||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). | ||
Copyright and related rights waived via [CC0](../LICENSE.md). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules/ | ||
package-lock.json | ||
typechain/ | ||
cache/ | ||
artifacts/ |
Oops, something went wrong.