diff --git a/EIPS/eip-7432.md b/EIPS/eip-7432.md index 4663ef4ca7e1b..e354d4c0b27f4 100644 --- a/EIPS/eip-7432.md +++ b/EIPS/eip-7432.md @@ -4,8 +4,7 @@ title: Non-Fungible Token Roles description: Role Management for NFTs. Enables accounts to share the utility of NFTs via expirable role assignments. author: Ernani São Thiago (@ernanirst), Daniel Lima (@karacurt) discussions-to: https://ethereum-magicians.org/t/eip-7432-non-fungible-token-roles/15298 -status: Last Call -last-call-deadline: 2023-09-26 +status: Review type: Standards Track category: ERC created: 2023-07-14 @@ -42,9 +41,9 @@ Compliant contracts MUST implement the following interface: ```solidity /// @title ERC-7432 Non-Fungible Token Roles /// @dev See https://eips.ethereum.org/EIPS/eip-7432 -/// Note: the ERC-165 identifier for this interface is 0xd7e151ef. +/// Note: the ERC-165 identifier for this interface is 0x25be10b2. interface IERC7432 /* is ERC165 */ { - + /** Events **/ /// @notice Emitted when a role is granted. @@ -54,6 +53,7 @@ interface IERC7432 /* is ERC165 */ { /// @param _grantor The user assigning the role. /// @param _grantee The user receiving the role. /// @param _expirationDate The expiration date of the role. + /// @param _revocable Whether the role is revocable or not. /// @param _data Any additional data about the role. event RoleGranted( bytes32 indexed _role, @@ -62,6 +62,7 @@ interface IERC7432 /* is ERC165 */ { address _grantor, address _grantee, uint64 _expirationDate, + bool _revocable, bytes _data ); @@ -101,7 +102,7 @@ interface IERC7432 /* is ERC165 */ { bool _isApproved ); - /** External Functions **/ + /** External Functions **/ /// @notice Grants a role to a user. /// @param _role The role identifier. @@ -109,6 +110,7 @@ interface IERC7432 /* is ERC165 */ { /// @param _tokenId The token identifier. /// @param _grantee The user receiving the role. /// @param _expirationDate The expiration date of the role. + /// @param _revocable Whether the role is revocable or not. /// @param _data Any additional data about the role. function grantRole( bytes32 _role, @@ -116,6 +118,7 @@ interface IERC7432 /* is ERC165 */ { uint256 _tokenId, address _grantee, uint64 _expirationDate, + bool _revocable, bytes calldata _data ) external; @@ -138,6 +141,7 @@ interface IERC7432 /* is ERC165 */ { /// @param _grantor The user assigning the role. /// @param _grantee The user that receives the role. /// @param _expirationDate The expiration date of the role. + /// @param _revocable Whether the role is revocable or not. /// @param _data Any additional data about the role. function grantRoleFrom( bytes32 _role, @@ -146,6 +150,7 @@ interface IERC7432 /* is ERC165 */ { address _grantor, address _grantee, uint64 _expirationDate, + bool _revocable, bytes calldata _data ) external; @@ -200,7 +205,7 @@ interface IERC7432 /* is ERC165 */ { address _grantor, address _grantee ) external view returns (bool); - + /// @notice Checks if a user has a unique role. /// @param _role The role identifier. /// @param _tokenAddress The token address. @@ -214,7 +219,7 @@ interface IERC7432 /* is ERC165 */ { address _grantor, address _grantee ) external view returns (bool); - + /// @notice Returns the custom data of a role assignment. /// @param _role The role identifier. /// @param _tokenAddress The token address. @@ -228,7 +233,7 @@ interface IERC7432 /* is ERC165 */ { address _grantor, address _grantee ) external view returns (bytes memory data_); - + /// @notice Returns the expiration date of a role assignment. /// @param _role The role identifier. /// @param _tokenAddress The token address. @@ -388,8 +393,8 @@ for complex tuple types. `external`. * The `grantRoleFrom` function MUST revert if the `_expirationDate` is in the past or if the `msg.sender` is not approved to grant roles on behalf of the `_grantor`. It MAY be implemented as `public` or `external`. -* The `revokeRole` function MAY be implemented as `public` or `external`. -* The `revokeRoleFrom` function MUST revert if the `msg.sender` is not approved to revoke roles on behalf of the `_revoker`. +* The `revokeRole` and `revokeRoleFrom` functions SHOULD revert if `_revocable` is `false`. They MAY be implemented as `public` or `external`. +* The `revokeRoleFrom` function MUST revert if the `msg.sender` is not approved to revoke roles on behalf of the `_revoker` or the `_grantee`. It MAY be implemented as `public` or `external`. * The `setRoleApprovalForAll` function MAY be implemented as `public` or `external`. * The `approveRole` function MAY be implemented as `public` or `external`. @@ -420,7 +425,16 @@ the expiration date, and `hasRole` checks if the role is expired by comparing wi (`block.timestamp`). Since `uint256` is not natively supported by most programming languages, dates are represented as `uint64` on this standard. The maximum UNIX timestamp represented by a `uint64` is about the year `584,942,417,355`, which should be enough to be considered "permanent". For this reason, it's RECOMMENDED using `type(uint64).max` when -calling the `grantRole` function to support use cases that require an assignment never to expire. +calling the `grantRole` function to support use cases that require an assignment never to expire. + +### Revocable Roles + +In certain scenarios, the grantor may need to revoke a role before its expiration date, while in others, the grantee +requires assurance that the grantor cannot revoke the role. The `_revocable` parameter was introduced to the `grantRole` +function to support both use cases and specify whether the `grantor` can revoke assigned roles. + +Regardless of the value of `_revocable`, it's RECOMMENDED always to enable the `grantee` to revoke received roles, +allowing recipients to eliminate undesirable assignments. ### Unique and Non-Unique Roles diff --git a/assets/eip-7432/ERC7432.sol b/assets/eip-7432/ERC7432.sol index f6a5e628b03e8..a6278a171e628 100644 --- a/assets/eip-7432/ERC7432.sol +++ b/assets/eip-7432/ERC7432.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.9; import { IERC7432 } from "./interfaces/IERC7432.sol"; contract ERC7432 is IERC7432 { - // grantor => grantee => tokenAddress => tokenId => role => struct(expirationDate, data) mapping(address => mapping(address => mapping(address => mapping(uint256 => mapping(bytes32 => RoleData))))) public roleAssignments; @@ -16,7 +15,7 @@ contract ERC7432 is IERC7432 { // grantor => tokenAddress => tokenId => operator => isApproved mapping(address => mapping(address => mapping(uint256 => mapping(address => bool)))) public tokenIdApprovals; - // grantor => operator => tokenAddress => isApproved + // grantor => tokenAddress => operator => isApproved mapping(address => mapping(address => mapping(address => bool))) public tokenApprovals; modifier validExpirationDate(uint64 _expirationDate) { @@ -24,10 +23,9 @@ contract ERC7432 is IERC7432 { _; } - modifier onlyApproved(address _tokenAddress, uint256 _tokenId, address _grantor) { + modifier onlyApproved(address _tokenAddress, uint256 _tokenId, address _account) { require( - isRoleApprovedForAll(_tokenAddress, _grantor, msg.sender) || - getApprovedRole(_tokenAddress, _tokenId, _grantor, msg.sender), + _isRoleApproved(_tokenAddress, _tokenId, _account, msg.sender), "ERC7432: sender must be approved" ); _; @@ -39,9 +37,10 @@ contract ERC7432 is IERC7432 { uint256 _tokenId, address _grantee, uint64 _expirationDate, + bool _revocable, bytes calldata _data ) external { - _grantRole(_role, _tokenAddress, _tokenId, msg.sender, _grantee, _expirationDate, _data); + _grantRole(_role, _tokenAddress, _tokenId, msg.sender, _grantee, _expirationDate, _revocable, _data); } function grantRoleFrom( @@ -51,9 +50,10 @@ contract ERC7432 is IERC7432 { address _grantor, address _grantee, uint64 _expirationDate, + bool _revocable, bytes calldata _data ) external override onlyApproved(_tokenAddress, _tokenId, _grantor) { - _grantRole(_role, _tokenAddress, _tokenId, _grantor, _grantee, _expirationDate, _data); + _grantRole(_role, _tokenAddress, _tokenId, _grantor, _grantee, _expirationDate, _revocable, _data); } function _grantRole( @@ -63,15 +63,16 @@ contract ERC7432 is IERC7432 { address _grantor, address _grantee, uint64 _expirationDate, + bool _revocable, bytes calldata _data ) internal validExpirationDate(_expirationDate) { - roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role] = RoleData(_expirationDate, _data); + roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role] = RoleData(_expirationDate, _revocable, _data); latestGrantees[_grantor][_tokenAddress][_tokenId][_role] = _grantee; - emit RoleGranted( _role, _tokenAddress, _tokenId, _grantor, _grantee, _expirationDate, _data); + emit RoleGranted(_role, _tokenAddress, _tokenId, _grantor, _grantee, _expirationDate, _revocable, _data); } function revokeRole(bytes32 _role, address _tokenAddress, uint256 _tokenId, address _grantee) external { - _revokeRole(_role, _tokenAddress, _tokenId, msg.sender, _grantee); + _revokeRole(_role, _tokenAddress, _tokenId, msg.sender, _grantee, msg.sender); } function revokeRoleFrom( @@ -80,8 +81,19 @@ contract ERC7432 is IERC7432 { uint256 _tokenId, address _revoker, address _grantee - ) external override onlyApproved(_tokenAddress, _tokenId, _revoker) { - _revokeRole(_role, _tokenAddress, _tokenId, _revoker, _grantee); + ) external override { + address _caller = _getApprovedCaller(_tokenAddress, _tokenId, _revoker, _grantee); + _revokeRole(_role, _tokenAddress, _tokenId, _revoker, _grantee, _caller); + } + + function _getApprovedCaller(address _tokenAddress, uint256 _tokenId, address _revoker, address _grantee) internal view returns (address) { + if (_isRoleApproved(_tokenAddress, _tokenId, _grantee, msg.sender)) { + return _grantee; + } else if(_isRoleApproved(_tokenAddress, _tokenId, _revoker, msg.sender)){ + return _revoker; + } else { + revert("ERC7432: sender must be approved"); + } } function _revokeRole( @@ -89,8 +101,11 @@ contract ERC7432 is IERC7432 { address _tokenAddress, uint256 _tokenId, address _revoker, - address _grantee + address _grantee, + address _caller ) internal { + bool _isRevocable = roleAssignments[_revoker][_grantee][_tokenAddress][_tokenId][_role].revocable; + require(_isRevocable || _caller == _grantee, "ERC7432: Role is not revocable or caller is not the grantee"); delete roleAssignments[_revoker][_grantee][_tokenAddress][_tokenId][_role]; delete latestGrantees[_revoker][_tokenAddress][_tokenId][_role]; emit RoleRevoked(_role, _tokenAddress, _tokenId, _revoker, _grantee); @@ -113,8 +128,7 @@ contract ERC7432 is IERC7432 { address _grantor, address _grantee ) external view returns (bool) { - return latestGrantees[_grantor][_tokenAddress][_tokenId][_role] == _grantee && roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role].expirationDate > - block.timestamp; + return latestGrantees[_grantor][_tokenAddress][_tokenId][_role] == _grantee && roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role].expirationDate > block.timestamp; } function roleData( @@ -128,13 +142,13 @@ contract ERC7432 is IERC7432 { return (_roleData.data); } - function roleExpirationDate( + function roleExpirationDate( bytes32 _role, address _tokenAddress, uint256 _tokenId, address _grantor, address _grantee - ) external view returns (uint64 expirationDate_){ + ) external view returns (uint64 expirationDate_) { RoleData memory _roleData = roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role]; return (_roleData.expirationDate); } @@ -178,4 +192,13 @@ contract ERC7432 is IERC7432 { ) public view override returns (bool) { return tokenIdApprovals[_grantor][_tokenAddress][_tokenId][_operator]; } + + function _isRoleApproved( + address _tokenAddress, + uint256 _tokenId, + address _grantor, + address _operator + ) internal view returns (bool) { + return isRoleApprovedForAll(_tokenAddress, _grantor, _operator) || getApprovedRole(_tokenAddress, _tokenId, _grantor, _operator); + } } diff --git a/assets/eip-7432/interfaces/IERC7432.sol b/assets/eip-7432/interfaces/IERC7432.sol index 432a3fa2468a8..c346dce2f3eb3 100644 --- a/assets/eip-7432/interfaces/IERC7432.sol +++ b/assets/eip-7432/interfaces/IERC7432.sol @@ -4,13 +4,13 @@ pragma solidity 0.8.9; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - /// @title ERC-7432 Non-Fungible Token Roles /// @dev See https://eips.ethereum.org/EIPS/eip-7432 -/// Note: the ERC-165 identifier for this interface is 0xd7e151ef. +/// Note: the ERC-165 identifier for this interface is 0x25be10b2. interface IERC7432 is IERC165 { struct RoleData { uint64 expirationDate; + bool revocable; bytes data; } @@ -23,6 +23,7 @@ interface IERC7432 is IERC165 { /// @param _grantor The user assigning the role. /// @param _grantee The user receiving the role. /// @param _expirationDate The expiration date of the role. + /// @param _revocable Whether the role is revocable or not. /// @param _data Any additional data about the role. event RoleGranted( bytes32 indexed _role, @@ -31,6 +32,7 @@ interface IERC7432 is IERC165 { address _grantor, address _grantee, uint64 _expirationDate, + bool _revocable, bytes _data ); @@ -78,6 +80,7 @@ interface IERC7432 is IERC165 { /// @param _tokenId The token identifier. /// @param _grantee The user receiving the role. /// @param _expirationDate The expiration date of the role. + /// @param _revocable Whether the role is revocable or not. /// @param _data Any additional data about the role. function grantRole( bytes32 _role, @@ -85,6 +88,7 @@ interface IERC7432 is IERC165 { uint256 _tokenId, address _grantee, uint64 _expirationDate, + bool _revocable, bytes calldata _data ) external; @@ -107,6 +111,7 @@ interface IERC7432 is IERC165 { /// @param _grantor The user assigning the role. /// @param _grantee The user that receives the role. /// @param _expirationDate The expiration date of the role. + /// @param _revocable Whether the role is revocable or not. /// @param _data Any additional data about the role. function grantRoleFrom( bytes32 _role, @@ -115,6 +120,7 @@ interface IERC7432 is IERC165 { address _grantor, address _grantee, uint64 _expirationDate, + bool _revocable, bytes calldata _data ) external; @@ -233,5 +239,4 @@ interface IERC7432 is IERC165 { address _grantor, address _operator ) external view returns (bool); - -} \ No newline at end of file +}