diff --git a/EIPS/eip-7432.md b/EIPS/eip-7432.md index e8226d681194e..fa40bf88e4092 100644 --- a/EIPS/eip-7432.md +++ b/EIPS/eip-7432.md @@ -41,22 +41,26 @@ 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 0xeec2fffb. +/// Note: the ERC-165 identifier for this interface is 0xd7e151ef. interface IERC7432 /* is ERC165 */ { + + /** Events **/ /// @notice Emitted when a role is granted. /// @param _role The role identifier. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. - /// @param _grantee The user that receives the role assignment. - /// @param _expirationDate The expiration date of the role assignment. - /// @param _data Any additional data about the role assignment. + /// @param _grantor The user assigning the role. + /// @param _grantee The user receiving the role. + /// @param _expirationDate The expiration date of the role. + /// @param _data Any additional data about the role. event RoleGranted( bytes32 indexed _role, address indexed _tokenAddress, uint256 indexed _tokenId, + address _grantor, address _grantee, - uint64 _expirationDate, + uint64 _expirationDate, bytes _data ); @@ -64,21 +68,47 @@ interface IERC7432 /* is ERC165 */ { /// @param _role The role identifier. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. + /// @param _revoker The user revoking the role. /// @param _grantee The user that receives the role revocation. event RoleRevoked( bytes32 indexed _role, address indexed _tokenAddress, uint256 indexed _tokenId, + address _revoker, address _grantee ); + /// @notice Emitted when a user is approved to manage any role on behalf of another user. + /// @param _tokenAddress The token address. + /// @param _operator The user approved to grant and revoke roles. + /// @param _isApproved The approval status. + event RoleApprovalForAll( + address indexed _tokenAddress, + address indexed _operator, + bool _isApproved + ); + + /// @notice Emitted when a user is approved to manage the roles of an NFT on behalf of another user. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @param _operator The user approved to grant and revoke roles. + /// @param _isApproved The approval status. + event RoleApproval( + address indexed _tokenAddress, + uint256 indexed _tokenId, + address _operator, + bool _isApproved + ); + + /** External Functions **/ + /// @notice Grants a role to a user. /// @param _role The role identifier. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. - /// @param _grantee The user that receives the role assignment. - /// @param _expirationDate The expiration date of the role assignment. - /// @param _data Any additional data about the role assignment. + /// @param _grantee The user receiving the role. + /// @param _expirationDate The expiration date of the role. + /// @param _data Any additional data about the role. function grantRole( bytes32 _role, address _tokenAddress, @@ -100,26 +130,82 @@ interface IERC7432 /* is ERC165 */ { address _grantee ) external; - /// @notice Checks if a user has a role. + /// @notice Grants a role on behalf of a user. /// @param _role The role identifier. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. - /// @param _grantor The role creator. + /// @param _grantor The user assigning the role. /// @param _grantee The user that receives the role. - function hasRole( + /// @param _expirationDate The expiration date of the role. + /// @param _data Any additional data about the role. + function grantRoleFrom( bytes32 _role, address _tokenAddress, uint256 _tokenId, address _grantor, + address _grantee, + uint64 _expirationDate, + bytes calldata _data + ) external; + + /// @notice Revokes a role on behalf of a user. + /// @param _role The role identifier. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @param _revoker The user revoking the role. + /// @param _grantee The user that receives the role revocation. + function revokeRoleFrom( + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + address _revoker, address _grantee - ) external view returns (bool); + ) external; + + /// @notice Approves operator to grant and revoke any roles on behalf of another user. + /// @param _tokenAddress The token address. + /// @param _operator The user approved to grant and revoke roles. + /// @param _approved The approval status. + function setRoleApprovalForAll( + address _tokenAddress, + address _operator, + bool _approved + ) external; + /// @notice Approves operator to grant and revoke roles of an NFT on behalf of another user. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @param _operator The user approved to grant and revoke roles. + /// @param _approved The approval status. + function approveRole( + address _tokenAddress, + uint256 _tokenId, + address _operator, + bool _approved + ) external; + + /** View Functions **/ + + /// @notice Checks if a user has a role. + /// @param _role The role identifier. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @param _grantor The user that assigned the role. + /// @param _grantee The user that received the role. + function hasRole( + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + 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. /// @param _tokenId The token identifier. - /// @param _grantor The role creator. - /// @param _grantee The user that receives the role. + /// @param _grantor The user that assigned the role. + /// @param _grantee The user that received the role. function hasUniqueRole( bytes32 _role, address _tokenAddress, @@ -127,34 +213,56 @@ 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. /// @param _tokenId The token identifier. - /// @param _grantor The role creator. - /// @param _grantee The user that receives the role. + /// @param _grantor The user that assigned the role. + /// @param _grantee The user that received the role. function roleData( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + 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. /// @param _tokenId The token identifier. - /// @param _grantor The role creator. - /// @param _grantee The user that receives the role. + /// @param _grantor The user that assigned the role. + /// @param _grantee The user that received the role. function roleExpirationDate( - bytes32 _role, + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + address _grantor, + address _grantee + ) external view returns (uint64 expirationDate_); + + /// @notice Checks if the grantor approved the operator for all NFTs. + /// @param _tokenAddress The token address. + /// @param _grantor The user that approved the operator. + /// @param _operator The user that can grant and revoke roles. + function isRoleApprovedForAll( + address _tokenAddress, + address _grantor, + address _operator + ) external view returns (bool); + + /// @notice Checks if the grantor approved the operator for a specific NFT. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @param _grantor The user that approved the operator. + /// @param _operator The user approved to grant and revoke roles. + function getApprovedRole( address _tokenAddress, uint256 _tokenId, address _grantor, - address _grantee - ) external view returns (uint64 expirationDate_); + address _operator + ) external view returns (bool); } ``` @@ -275,9 +383,15 @@ for complex tuple types. * Compliant contracts MUST implement the `IERC7432` interface. * A role is represented by a `bytes32`, and it's RECOMMENDED to use the `keccak256` of the role's name for this purpose: `bytes32 role = keccak256("ROLE_NAME")`. -* `grantRole` function MUST revert if the `_expirationDate` is in the past, and MAY be implemented as `public` or +* The `grantRole` function MUST revert if the `_expirationDate` is in the past, and MAY be implemented as `public` or `external`. -* `revokeRole` function MAY be implemented as `public` or `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`. + 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`. * The `hasRole` function MAY be implemented as `pure` or `view` and SHOULD only confirm if the role assignment exists and is not expired (supports simultaneous role assignments). * The `hasUniqueRole` function MAY be implemented as `pure` or `view` and SHOULD check if the assignment exists, is not @@ -286,6 +400,10 @@ for complex tuple types. assignment does not exist. * The `roleExpirationDate` function MAY be implemented as `pure` or `view`, and SHOULD return zero if the role assignment does not exist. +* The `isRoleApprovedForAll` function MAY be implemented as `pure` or `view` and SHOULD only return `true` if the + `_operator` is approved to grant and revoke roles for all NFTs on behalf of the `_grantor`. +* The `getApprovedRole` function MAY be implemented as `pure` or `view` and SHOULD only return `true` if the `_operator` + is approved to grant and revoke roles for the specified NFT on behalf of the `_grantor`. * Compliant contracts SHOULD support [ERC-165](./eip-165.md). ## Rationale @@ -321,6 +439,12 @@ generic type `bytes` to enable dApps to encode any role-specific information whe data is retrievable using the `roleData` function and is emitted with the `RoleGranted` event. With this approach, developers can integrate this information into their applications, both on-chain and off-chain. +### Role Approval + +Similar to [ERC-721](./eip-721.md), this standard enables users to approve other accounts to grant and revoke roles on +its behalf. Users can authorize an account to manage a single NFT or any NFTs in a collection. This functionality was +introduced to allow third-party contracts to interact with ERC-7432 without requiring ownership of the NFTs. + ## Backwards Compatibility On all functions and events, the standard requires both the `tokenAddress` and `tokenId` to be provided. This diff --git a/assets/eip-7432/ERC7432.sol b/assets/eip-7432/ERC7432.sol index 0b5cb076970c1..f6a5e628b03e8 100644 --- a/assets/eip-7432/ERC7432.sol +++ b/assets/eip-7432/ERC7432.sol @@ -11,13 +11,28 @@ contract ERC7432 is IERC7432 { public roleAssignments; // grantor => tokenAddress => tokenId => role => grantee - mapping(address => mapping(address => mapping(uint256 => mapping(bytes32 => address)))) public lastRoleAssignment; + mapping(address => mapping(address => mapping(uint256 => mapping(bytes32 => address)))) public latestGrantees; + + // grantor => tokenAddress => tokenId => operator => isApproved + mapping(address => mapping(address => mapping(uint256 => mapping(address => bool)))) public tokenIdApprovals; + + // grantor => operator => tokenAddress => isApproved + mapping(address => mapping(address => mapping(address => bool))) public tokenApprovals; modifier validExpirationDate(uint64 _expirationDate) { require(_expirationDate > block.timestamp, "ERC7432: expiration date must be in the future"); _; } + modifier onlyApproved(address _tokenAddress, uint256 _tokenId, address _grantor) { + require( + isRoleApprovedForAll(_tokenAddress, _grantor, msg.sender) || + getApprovedRole(_tokenAddress, _tokenId, _grantor, msg.sender), + "ERC7432: sender must be approved" + ); + _; + } + function grantRole( bytes32 _role, address _tokenAddress, @@ -25,16 +40,60 @@ contract ERC7432 is IERC7432 { address _grantee, uint64 _expirationDate, bytes calldata _data - ) external validExpirationDate(_expirationDate) { - roleAssignments[msg.sender][_grantee][_tokenAddress][_tokenId][_role] = RoleData(_expirationDate, _data); - lastRoleAssignment[msg.sender][_tokenAddress][_tokenId][_role] = _grantee; - emit RoleGranted(_role, _tokenAddress, _tokenId, _grantee, _expirationDate, _data); + ) external { + _grantRole(_role, _tokenAddress, _tokenId, msg.sender, _grantee, _expirationDate, _data); + } + + function grantRoleFrom( + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + address _grantor, + address _grantee, + uint64 _expirationDate, + bytes calldata _data + ) external override onlyApproved(_tokenAddress, _tokenId, _grantor) { + _grantRole(_role, _tokenAddress, _tokenId, _grantor, _grantee, _expirationDate, _data); + } + + function _grantRole( + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + address _grantor, + address _grantee, + uint64 _expirationDate, + bytes calldata _data + ) internal validExpirationDate(_expirationDate) { + roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role] = RoleData(_expirationDate, _data); + latestGrantees[_grantor][_tokenAddress][_tokenId][_role] = _grantee; + emit RoleGranted( _role, _tokenAddress, _tokenId, _grantor, _grantee, _expirationDate, _data); } function revokeRole(bytes32 _role, address _tokenAddress, uint256 _tokenId, address _grantee) external { - delete roleAssignments[msg.sender][_grantee][_tokenAddress][_tokenId][_role]; - delete lastRoleAssignment[msg.sender][_tokenAddress][_tokenId][_role]; - emit RoleRevoked(_role, _tokenAddress, _tokenId, _grantee); + _revokeRole(_role, _tokenAddress, _tokenId, msg.sender, _grantee); + } + + function revokeRoleFrom( + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + address _revoker, + address _grantee + ) external override onlyApproved(_tokenAddress, _tokenId, _revoker) { + _revokeRole(_role, _tokenAddress, _tokenId, _revoker, _grantee); + } + + function _revokeRole( + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + address _revoker, + address _grantee + ) internal { + delete roleAssignments[_revoker][_grantee][_tokenAddress][_tokenId][_role]; + delete latestGrantees[_revoker][_tokenAddress][_tokenId][_role]; + emit RoleRevoked(_role, _tokenAddress, _tokenId, _revoker, _grantee); } function hasRole( @@ -54,10 +113,8 @@ contract ERC7432 is IERC7432 { address _grantor, address _grantee ) external view returns (bool) { - bool isValid = roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role].expirationDate > + return latestGrantees[_grantor][_tokenAddress][_tokenId][_role] == _grantee && roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role].expirationDate > block.timestamp; - - return isValid && lastRoleAssignment[_grantor][_tokenAddress][_tokenId][_role] == _grantee; } function roleData( @@ -85,4 +142,40 @@ contract ERC7432 is IERC7432 { function supportsInterface(bytes4 interfaceId) external view virtual override returns (bool) { return interfaceId == type(IERC7432).interfaceId; } + + function setRoleApprovalForAll( + address _tokenAddress, + address _operator, + bool _isApproved + ) external override { + tokenApprovals[msg.sender][_tokenAddress][_operator] = _isApproved; + emit RoleApprovalForAll(_tokenAddress, _operator, _isApproved); + } + + function approveRole( + address _tokenAddress, + uint256 _tokenId, + address _operator, + bool _approved + ) external override { + tokenIdApprovals[msg.sender][_tokenAddress][_tokenId][_operator] = _approved; + emit RoleApproval(_tokenAddress, _tokenId, _operator, _approved); + } + + function isRoleApprovedForAll( + address _tokenAddress, + address _grantor, + address _operator + ) public view override returns (bool) { + return tokenApprovals[_grantor][_tokenAddress][_operator]; + } + + function getApprovedRole( + address _tokenAddress, + uint256 _tokenId, + address _grantor, + address _operator + ) public view override returns (bool) { + return tokenIdApprovals[_grantor][_tokenAddress][_tokenId][_operator]; + } } diff --git a/assets/eip-7432/interfaces/IERC7432.sol b/assets/eip-7432/interfaces/IERC7432.sol index 334b4caa0c6c9..432a3fa2468a8 100644 --- a/assets/eip-7432/interfaces/IERC7432.sol +++ b/assets/eip-7432/interfaces/IERC7432.sol @@ -7,26 +7,30 @@ 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 0xeec2fffb. +/// Note: the ERC-165 identifier for this interface is 0xd7e151ef. interface IERC7432 is IERC165 { struct RoleData { uint64 expirationDate; bytes data; } + /** Events **/ + /// @notice Emitted when a role is granted. /// @param _role The role identifier. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. - /// @param _grantee The user that receives the role assignment. - /// @param _expirationDate The expiration date of the role assignment. - /// @param _data Any additional data about the role assignment. + /// @param _grantor The user assigning the role. + /// @param _grantee The user receiving the role. + /// @param _expirationDate The expiration date of the role. + /// @param _data Any additional data about the role. event RoleGranted( bytes32 indexed _role, address indexed _tokenAddress, uint256 indexed _tokenId, + address _grantor, address _grantee, - uint64 _expirationDate, + uint64 _expirationDate, bytes _data ); @@ -34,21 +38,47 @@ interface IERC7432 is IERC165 { /// @param _role The role identifier. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. + /// @param _revoker The user revoking the role. /// @param _grantee The user that receives the role revocation. event RoleRevoked( bytes32 indexed _role, address indexed _tokenAddress, uint256 indexed _tokenId, + address _revoker, address _grantee ); + /// @notice Emitted when a user is approved to manage any role on behalf of another user. + /// @param _tokenAddress The token address. + /// @param _operator The user approved to grant and revoke roles. + /// @param _isApproved The approval status. + event RoleApprovalForAll( + address indexed _tokenAddress, + address indexed _operator, + bool _isApproved + ); + + /// @notice Emitted when a user is approved to manage the roles of an NFT on behalf of another user. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @param _operator The user approved to grant and revoke roles. + /// @param _isApproved The approval status. + event RoleApproval( + address indexed _tokenAddress, + uint256 indexed _tokenId, + address _operator, + bool _isApproved + ); + + /** External Functions **/ + /// @notice Grants a role to a user. /// @param _role The role identifier. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. - /// @param _grantee The user that receives the role assignment. - /// @param _expirationDate The expiration date of the role assignment. - /// @param _data Any additional data about the role assignment. + /// @param _grantee The user receiving the role. + /// @param _expirationDate The expiration date of the role. + /// @param _data Any additional data about the role. function grantRole( bytes32 _role, address _tokenAddress, @@ -70,12 +100,68 @@ interface IERC7432 is IERC165 { address _grantee ) external; - /// @notice Checks if a user has a role. + /// @notice Grants a role on behalf of a user. /// @param _role The role identifier. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. - /// @param _grantor The role creator. + /// @param _grantor The user assigning the role. /// @param _grantee The user that receives the role. + /// @param _expirationDate The expiration date of the role. + /// @param _data Any additional data about the role. + function grantRoleFrom( + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + address _grantor, + address _grantee, + uint64 _expirationDate, + bytes calldata _data + ) external; + + /// @notice Revokes a role on behalf of a user. + /// @param _role The role identifier. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @param _revoker The user revoking the role. + /// @param _grantee The user that receives the role revocation. + function revokeRoleFrom( + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + address _revoker, + address _grantee + ) external; + + /// @notice Approves operator to grant and revoke any roles on behalf of another user. + /// @param _tokenAddress The token address. + /// @param _operator The user approved to grant and revoke roles. + /// @param _approved The approval status. + function setRoleApprovalForAll( + address _tokenAddress, + address _operator, + bool _approved + ) external; + + /// @notice Approves operator to grant and revoke roles of an NFT on behalf of another user. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @param _operator The user approved to grant and revoke roles. + /// @param _approved The approval status. + function approveRole( + address _tokenAddress, + uint256 _tokenId, + address _operator, + bool _approved + ) external; + + /** View Functions **/ + + /// @notice Checks if a user has a role. + /// @param _role The role identifier. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @param _grantor The user that assigned the role. + /// @param _grantee The user that received the role. function hasRole( bytes32 _role, address _tokenAddress, @@ -88,22 +174,22 @@ interface IERC7432 is IERC165 { /// @param _role The role identifier. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. - /// @param _grantor The role creator. - /// @param _grantee The user that receives the role. + /// @param _grantor The user that assigned the role. + /// @param _grantee The user that received the role. function hasUniqueRole( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee + bytes32 _role, + address _tokenAddress, + uint256 _tokenId, + 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. /// @param _tokenId The token identifier. - /// @param _grantor The role creator. - /// @param _grantee The user that receives the role. + /// @param _grantor The user that assigned the role. + /// @param _grantee The user that received the role. function roleData( bytes32 _role, address _tokenAddress, @@ -116,8 +202,8 @@ interface IERC7432 is IERC165 { /// @param _role The role identifier. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. - /// @param _grantor The role creator. - /// @param _grantee The user that receives the role. + /// @param _grantor The user that assigned the role. + /// @param _grantee The user that received the role. function roleExpirationDate( bytes32 _role, address _tokenAddress, @@ -126,4 +212,26 @@ interface IERC7432 is IERC165 { address _grantee ) external view returns (uint64 expirationDate_); + /// @notice Checks if the grantor approved the operator for all NFTs. + /// @param _tokenAddress The token address. + /// @param _grantor The user that approved the operator. + /// @param _operator The user that can grant and revoke roles. + function isRoleApprovedForAll( + address _tokenAddress, + address _grantor, + address _operator + ) external view returns (bool); + + /// @notice Checks if the grantor approved the operator for a specific NFT. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @param _grantor The user that approved the operator. + /// @param _operator The user approved to grant and revoke roles. + function getApprovedRole( + address _tokenAddress, + uint256 _tokenId, + address _grantor, + address _operator + ) external view returns (bool); + } \ No newline at end of file