From 500ba41cc81a348156eaf99b054049249ddabc76 Mon Sep 17 00:00:00 2001 From: Daniel Lima Date: Mon, 25 Sep 2023 12:07:08 -0300 Subject: [PATCH 1/2] ON-483: Update ERC7432 interface, revocable roles --- contracts/RolesRegistry/RolesRegistry.sol | 72 +++-- .../RolesRegistry/interfaces/IERC7432.sol | 11 +- test/RolesRegistry.spec.ts | 275 ++++++++++++++++-- test/contants.ts | 2 +- 4 files changed, 311 insertions(+), 49 deletions(-) diff --git a/contracts/RolesRegistry/RolesRegistry.sol b/contracts/RolesRegistry/RolesRegistry.sol index 3ad185d..fa6bf0c 100644 --- a/contracts/RolesRegistry/RolesRegistry.sol +++ b/contracts/RolesRegistry/RolesRegistry.sol @@ -15,7 +15,7 @@ contract RolesRegistry is IERC7432 { // grantor => tokenAddress => tokenId => operator => isApproved mapping(address => mapping(address => mapping(uint256 => mapping(address => bool)))) public tokenIdApprovals; - // grantor => tokenAddress => operator => isApproved + // grantor => operator => tokenAddress => isApproved mapping(address => mapping(address => mapping(address => bool))) public tokenApprovals; modifier validExpirationDate(uint64 _expirationDate) { @@ -23,14 +23,9 @@ contract RolesRegistry 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), "RolesRegistry: sender must be approved" ); _; @@ -42,9 +37,10 @@ contract RolesRegistry 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( @@ -54,9 +50,10 @@ contract RolesRegistry 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( @@ -66,15 +63,16 @@ contract RolesRegistry 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( @@ -83,8 +81,19 @@ contract RolesRegistry 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("RolesRegistry: sender must be approved"); + } } function _revokeRole( @@ -92,8 +101,11 @@ contract RolesRegistry 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, "RolesRegistry: 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); @@ -116,9 +128,7 @@ contract RolesRegistry 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( @@ -147,12 +157,21 @@ contract RolesRegistry is IERC7432 { return interfaceId == type(IERC7432).interfaceId; } - function setRoleApprovalForAll(address _tokenAddress, address _operator, bool _isApproved) external override { + 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 { + 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); } @@ -173,4 +192,13 @@ contract RolesRegistry 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); + } +} \ No newline at end of file diff --git a/contracts/RolesRegistry/interfaces/IERC7432.sol b/contracts/RolesRegistry/interfaces/IERC7432.sol index 432a3fa..26d1133 100644 --- a/contracts/RolesRegistry/interfaces/IERC7432.sol +++ b/contracts/RolesRegistry/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 diff --git a/test/RolesRegistry.spec.ts b/test/RolesRegistry.spec.ts index 43fe31e..8aedeee 100644 --- a/test/RolesRegistry.spec.ts +++ b/test/RolesRegistry.spec.ts @@ -26,6 +26,7 @@ describe('RolesRegistry', () => { const PROPERTY_TENANT = solidityKeccak256(['string'], ['PROPERTY_TENANT']) const tokenId = 1 + const revocable = true before(async function () { // prettier-ignore @@ -74,8 +75,8 @@ describe('RolesRegistry', () => { }) beforeEach(async () => { - const ERC7432Factory = await ethers.getContractFactory('RolesRegistry') - RolesRegistry = await ERC7432Factory.deploy() + const RolesRegistryFactory = await ethers.getContractFactory('RolesRegistry') + RolesRegistry = await RolesRegistryFactory.deploy() const NftFactory = await ethers.getContractFactory('Nft') nft = await NftFactory.deploy() @@ -106,11 +107,21 @@ describe('RolesRegistry', () => { tokenId, userOne.address, expirationDate, + revocable, data, ), ) .to.emit(RolesRegistry, 'RoleGranted') - .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address, expirationDate, data) + .withArgs( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userOne.address, + expirationDate, + revocable, + data, + ) }) it('should NOT grant role if expiration date is in the past', async () => { const blockNumber = await hre.ethers.provider.getBlockNumber() @@ -124,6 +135,7 @@ describe('RolesRegistry', () => { tokenId, userOne.address, expirationDateInThePast, + revocable, HashZero, ), ).to.be.revertedWith('RolesRegistry: expiration date must be in the future') @@ -131,11 +143,58 @@ describe('RolesRegistry', () => { }) describe('Revoke role', async () => { + beforeEach(async () => { + await RolesRegistry.connect(grantor).grantRole( + PROPERTY_MANAGER, + AddressZero, + tokenId, + userOne.address, + expirationDate, + revocable, + data, + ) + }) it('should revoke role', async () => { await expect(RolesRegistry.connect(grantor).revokeRole(PROPERTY_MANAGER, AddressZero, tokenId, userOne.address)) .to.emit(RolesRegistry, 'RoleRevoked') .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address) }) + it('should revoke role if caller is the grantee', async () => { + await expect(RolesRegistry.connect(grantor).revokeRole(PROPERTY_MANAGER, AddressZero, tokenId, userOne.address)) + .to.emit(RolesRegistry, 'RoleRevoked') + .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address) + }) + it('should revoke role if role is not revocable, but grantor is also the grantee', async () => { + await RolesRegistry.connect(grantor).grantRole( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + expirationDate, + false, + data, + ) + await expect(RolesRegistry.connect(grantor).revokeRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address)) + .to.emit(RolesRegistry, 'RoleRevoked') + .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, grantor.address) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, grantor.address), + ).to.be.equal(false) + }) + it('should NOT revoke role if role is not revocable', async () => { + await RolesRegistry.connect(grantor).grantRole( + PROPERTY_MANAGER, + AddressZero, + tokenId, + userOne.address, + expirationDate, + false, + data, + ) + await expect( + RolesRegistry.connect(grantor).revokeRole(PROPERTY_MANAGER, AddressZero, tokenId, userOne.address), + ).to.be.revertedWith(`RolesRegistry: Role is not revocable or caller is not the grantee`) + }) }) describe('Has role', async () => { @@ -147,11 +206,21 @@ describe('RolesRegistry', () => { tokenId, userOne.address, expirationDate, + revocable, HashZero, ), ) .to.emit(RolesRegistry, 'RoleGranted') - .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address, expirationDate, HashZero) + .withArgs( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userOne.address, + expirationDate, + revocable, + HashZero, + ) await expect( RolesRegistry.connect(grantor).grantRole( @@ -160,11 +229,21 @@ describe('RolesRegistry', () => { tokenId, userTwo.address, expirationDate, + revocable, HashZero, ), ) .to.emit(RolesRegistry, 'RoleGranted') - .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userTwo.address, expirationDate, HashZero) + .withArgs( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userTwo.address, + expirationDate, + revocable, + HashZero, + ) }) describe('Unique Roles', async () => { @@ -234,6 +313,7 @@ describe('RolesRegistry', () => { tokenId, userOne.address, expirationDate, + revocable, customData, ), ) @@ -245,6 +325,7 @@ describe('RolesRegistry', () => { grantor.address, userOne.address, expirationDate, + revocable, customData, ) @@ -291,6 +372,7 @@ describe('RolesRegistry', () => { tokenId, userOne.address, expirationDate, + revocable, customData, ) @@ -303,7 +385,7 @@ describe('RolesRegistry', () => { ) const tenantRole = nftMetadata.roles.find((role: Role) => role.name === 'PROPERTY_TENANT') - const decodedData = defaultAbiCoder.decode([`${tenantRole?.inputs.map(input => input.type)}`], returnedData) + const decodedData = defaultAbiCoder.decode([`${tenantRole!.inputs.map(input => input.type)}`], returnedData) expect(returnedData).to.equal(customData) expect(decodedData[0]).to.deep.equal(rentalCost) @@ -337,6 +419,7 @@ describe('RolesRegistry', () => { grantor.address, userOne.address, expirationDate, + revocable, HashZero, ), ) @@ -348,6 +431,7 @@ describe('RolesRegistry', () => { grantor.address, userOne.address, expirationDate, + revocable, HashZero, ) }) @@ -366,6 +450,7 @@ describe('RolesRegistry', () => { grantor.address, userOne.address, expirationDate, + revocable, HashZero, ), ).to.be.revertedWith('RolesRegistry: sender must be approved') @@ -373,34 +458,178 @@ describe('RolesRegistry', () => { }) describe('Revoke role from', async () => { - it('should revoke role from', async () => { - await expect( - RolesRegistry.connect(operator).revokeRoleFrom( + describe('Revocable roles', async () => { + beforeEach(async () => { + await RolesRegistry.connect(operator).grantRoleFrom( PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address, - ), - ) - .to.emit(RolesRegistry, 'RoleRevoked') - .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address) - }) - it('should NOT revoke role from if operator is not approved', async () => { - if (approval === 'Approval for TokenId') { + expirationDate, + revocable, + HashZero, + ) + }) + it('should revoke role from', async () => { + await expect( + RolesRegistry.connect(operator).revokeRoleFrom( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userOne.address, + ), + ) + .to.emit(RolesRegistry, 'RoleRevoked') + .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address) + }) + it('should NOT revoke role from if operator is not approved', async () => { + if (approval === 'Approval for TokenId') { + await RolesRegistry.connect(grantor).approveRole(AddressZero, tokenId, operator.address, false) + } else { + await RolesRegistry.connect(grantor).setRoleApprovalForAll(AddressZero, operator.address, false) + } + await expect( + RolesRegistry.connect(operator).revokeRoleFrom( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userOne.address, + ), + ).to.be.revertedWith('RolesRegistry: sender must be approved') + }) + it('should revoke role from if operator is only approved by grantee', async () => { await RolesRegistry.connect(grantor).approveRole(AddressZero, tokenId, operator.address, false) - } else { - await RolesRegistry.connect(grantor).setRoleApprovalForAll(AddressZero, operator.address, false) - } - await expect( - RolesRegistry.connect(operator).revokeRoleFrom( + await RolesRegistry.connect(userOne).approveRole(AddressZero, tokenId, operator.address, true) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address), + ).to.be.equal(true) + await expect( + RolesRegistry.connect(operator).revokeRoleFrom( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userOne.address, + ), + ) + .to.emit(RolesRegistry, 'RoleRevoked') + .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address), + ).to.be.equal(false) + }) + it('should revoke role from if operator is approved by both grantor and grantee', async () => { + await RolesRegistry.connect(grantor).approveRole(AddressZero, tokenId, operator.address, true) + await RolesRegistry.connect(userOne).approveRole(AddressZero, tokenId, operator.address, true) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address), + ).to.be.equal(true) + await expect( + RolesRegistry.connect(operator).revokeRoleFrom( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userOne.address, + ), + ) + .to.emit(RolesRegistry, 'RoleRevoked') + .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address), + ).to.be.equal(false) + }) + it('should revoke role from if operator is only approved by grantor', async () => { + await RolesRegistry.connect(grantor).approveRole(AddressZero, tokenId, operator.address, true) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address), + ).to.be.equal(true) + await expect( + RolesRegistry.connect(operator).revokeRoleFrom( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userOne.address, + ), + ) + .to.emit(RolesRegistry, 'RoleRevoked') + .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address), + ).to.be.equal(false) + }) + }) + describe('Non-Revocable roles', async () => { + beforeEach(async () => { + await RolesRegistry.connect(operator).grantRoleFrom( PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address, - ), - ).to.be.revertedWith('RolesRegistry: sender must be approved') + expirationDate, + !revocable, + HashZero, + ) + }) + it('should revoke role from if operator is only approved by grantee', async () => { + await RolesRegistry.connect(grantor).approveRole(AddressZero, tokenId, operator.address, false) + await RolesRegistry.connect(userOne).approveRole(AddressZero, tokenId, operator.address, true) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address), + ).to.be.equal(true) + await expect( + RolesRegistry.connect(operator).revokeRoleFrom( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userOne.address, + ), + ) + .to.emit(RolesRegistry, 'RoleRevoked') + .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address), + ).to.be.equal(false) + }) + it('should revoke role from if operator is approved by both grantor and grantee', async () => { + await RolesRegistry.connect(grantor).approveRole(AddressZero, tokenId, operator.address, true) + await RolesRegistry.connect(userOne).approveRole(AddressZero, tokenId, operator.address, true) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address), + ).to.be.equal(true) + await expect( + RolesRegistry.connect(operator).revokeRoleFrom( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userOne.address, + ), + ) + .to.emit(RolesRegistry, 'RoleRevoked') + .withArgs(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address) + expect( + await RolesRegistry.hasRole(PROPERTY_MANAGER, AddressZero, tokenId, grantor.address, userOne.address), + ).to.be.equal(false) + }) + it('should NOT revoke role from if operator is only approved by grantor', async () => { + await RolesRegistry.connect(grantor).approveRole(AddressZero, tokenId, operator.address, true) + await expect( + RolesRegistry.connect(operator).revokeRoleFrom( + PROPERTY_MANAGER, + AddressZero, + tokenId, + grantor.address, + userOne.address, + ), + ).to.be.revertedWith(`RolesRegistry: Role is not revocable or caller is not the grantee`) + }) }) }) }) diff --git a/test/contants.ts b/test/contants.ts index 0ef1333..267eb5b 100644 --- a/test/contants.ts +++ b/test/contants.ts @@ -1 +1 @@ -export const ERC7432InterfaceId = '0xd7e151ef' +export const ERC7432InterfaceId = '0x25be10b2' From be4293f6fb19dbca8fb804aa5646e9ffe359c620 Mon Sep 17 00:00:00 2001 From: Daniel Lima Date: Mon, 25 Sep 2023 12:09:07 -0300 Subject: [PATCH 2/2] ON-483: Update comment --- contracts/RolesRegistry/RolesRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/RolesRegistry/RolesRegistry.sol b/contracts/RolesRegistry/RolesRegistry.sol index fa6bf0c..284cf54 100644 --- a/contracts/RolesRegistry/RolesRegistry.sol +++ b/contracts/RolesRegistry/RolesRegistry.sol @@ -15,7 +15,7 @@ contract RolesRegistry 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) {