diff --git a/contracts/ERC7432/ERC7432WrapperForERC4907.sol b/contracts/ERC7432/ERC7432WrapperForERC4907.sol index 37d72c4..dc5da39 100644 --- a/contracts/ERC7432/ERC7432WrapperForERC4907.sol +++ b/contracts/ERC7432/ERC7432WrapperForERC4907.sol @@ -7,13 +7,12 @@ import { IERC721Receiver } from '@openzeppelin/contracts/token/ERC721/IERC721Rec import { ERC721Holder } from '@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol'; import { IERC7432 } from '../interfaces/IERC7432.sol'; import { IERC4907 } from '../interfaces/IERC4907.sol'; -import { IERC7432VaultExtension } from '../interfaces/IERC7432VaultExtension.sol'; import { IOriumWrapperManager } from '../interfaces/IOriumWrapperManager.sol'; import { IWrapNFT } from '../interfaces/DoubleProtocol/IWrapNFT.sol'; /// @title ERC-7432 Wrapper for ERC-4907 /// @dev This contract introduces a ERC-7432 interface to manage the role of ERC-4907 NFTs. -contract ERC7432WrapperForERC4907 is IERC7432, IERC7432VaultExtension, ERC721Holder { +contract ERC7432WrapperForERC4907 is IERC7432, ERC721Holder { bytes32 public constant USER_ROLE = keccak256('User()'); address public oriumWrapperManager; @@ -34,7 +33,7 @@ contract ERC7432WrapperForERC4907 is IERC7432, IERC7432VaultExtension, ERC721Hol _; } - /** ERC-7432 External Functions **/ + /** External Functions **/ constructor(address _oriumWrapperManagerAddress) { oriumWrapperManager = _oriumWrapperManagerAddress; @@ -46,7 +45,8 @@ contract ERC7432WrapperForERC4907 is IERC7432, IERC7432VaultExtension, ERC721Hol require( _role.expirationDate > block.timestamp && - _role.expirationDate < block.timestamp + IOriumWrapperManager(oriumWrapperManager).getMaxDurationOf(_role.tokenAddress), + _role.expirationDate < + block.timestamp + IOriumWrapperManager(oriumWrapperManager).getMaxDurationOf(_role.tokenAddress), 'ERC7432WrapperForERC4907: invalid expiration date' ); @@ -101,12 +101,39 @@ contract ERC7432WrapperForERC4907 is IERC7432, IERC7432VaultExtension, ERC721Hol emit RoleRevoked(_tokenAddress, _tokenId, _roleId); } + function unlockToken(address _tokenAddress, uint256 _tokenId) external override { + address _wrappedTokenAddress = IOriumWrapperManager(oriumWrapperManager).getWrappedTokenOf(_tokenAddress); + require(_wrappedTokenAddress != address(0), 'ERC7432WrapperForERC4907: token not supported'); + + address originalOwner = originalOwners[_tokenAddress][_tokenId]; + require( + originalOwner == msg.sender || isRoleApprovedForAll(_tokenAddress, originalOwner, msg.sender), + 'ERC7432WrapperForERC4907: sender must be owner or approved' + ); + + require( + isRevocableRole[_tokenAddress][_tokenId] || + IERC4907(_wrappedTokenAddress).userExpires(_tokenId) < block.timestamp, + 'ERC7432WrapperForERC4907: token has a non-revocable role active' + ); + + delete originalOwners[_tokenAddress][_tokenId]; + delete isRevocableRole[_tokenAddress][_tokenId]; + IWrapNFT(_wrappedTokenAddress).redeem(_tokenId); + IERC721(_tokenAddress).transferFrom(address(this), originalOwner, _tokenId); + emit TokenUnlocked(originalOwner, _tokenAddress, _tokenId); + } + function setRoleApprovalForAll(address _tokenAddress, address _operator, bool _approved) external override { tokenApprovals[msg.sender][_tokenAddress][_operator] = _approved; emit RoleApprovalForAll(_tokenAddress, _operator, _approved); } - /** ERC-7432 View Functions **/ + /** View Functions **/ + + function ownerOf(address _tokenAddress, uint256 _tokenId) external view returns (address owner_) { + return originalOwners[_tokenAddress][_tokenId]; + } function recipientOf( address _tokenAddress, @@ -153,42 +180,10 @@ contract ERC7432WrapperForERC4907 is IERC7432, IERC7432VaultExtension, ERC721Hol tokenApprovals[_owner][_tokenAddress][_operator]; } - /** ERC-7432 Vault Extension Functions **/ - - function withdraw(address _tokenAddress, uint256 _tokenId) external override { - address _wrappedTokenAddress = IOriumWrapperManager(oriumWrapperManager).getWrappedTokenOf(_tokenAddress); - require(_wrappedTokenAddress != address(0), 'ERC7432WrapperForERC4907: token not supported'); - - address originalOwner = originalOwners[_tokenAddress][_tokenId]; - require( - originalOwner == msg.sender || isRoleApprovedForAll(_tokenAddress, originalOwner, msg.sender), - 'ERC7432WrapperForERC4907: sender must be owner or approved' - ); - - require( - isRevocableRole[_tokenAddress][_tokenId] || - IERC4907(_wrappedTokenAddress).userExpires(_tokenId) < block.timestamp, - 'ERC7432WrapperForERC4907: token is not withdrawable' - ); - - delete originalOwners[_tokenAddress][_tokenId]; - delete isRevocableRole[_tokenAddress][_tokenId]; - IWrapNFT(_wrappedTokenAddress).redeem(_tokenId); - IERC721(_tokenAddress).transferFrom(address(this), originalOwner, _tokenId); - emit Withdraw(originalOwner, _tokenAddress, _tokenId); - } - - function ownerOf(address _tokenAddress, uint256 _tokenId) external view returns (address owner_) { - return originalOwners[_tokenAddress][_tokenId]; - } - /** ERC-165 Functions **/ function supportsInterface(bytes4 interfaceId) external view virtual override returns (bool) { - return - interfaceId == type(IERC7432).interfaceId || - interfaceId == type(IERC7432VaultExtension).interfaceId || - interfaceId == type(IERC721Receiver).interfaceId; + return interfaceId == type(IERC7432).interfaceId || interfaceId == type(IERC721Receiver).interfaceId; } /** Internal Functions **/ @@ -228,6 +223,7 @@ contract ERC7432WrapperForERC4907 is IERC7432, IERC7432VaultExtension, ERC721Hol IWrapNFT(_wrappedTokenAddress).stake(_tokenId); originalOwners[_tokenAddress][_tokenId] = _ownerOfOriginalToken; originalOwner_ = _ownerOfOriginalToken; + emit TokenLocked(_ownerOfOriginalToken, _tokenAddress, _tokenId); } } diff --git a/contracts/ERC7432/NftRolesRegistryVault.sol b/contracts/ERC7432/NftRolesRegistryVault.sol index 63fa748..9a97b3f 100644 --- a/contracts/ERC7432/NftRolesRegistryVault.sol +++ b/contracts/ERC7432/NftRolesRegistryVault.sol @@ -3,10 +3,9 @@ pragma solidity 0.8.9; import { IERC7432 } from '../interfaces/IERC7432.sol'; -import { IERC7432VaultExtension } from '../interfaces/IERC7432VaultExtension.sol'; import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol'; -contract NftRolesRegistryVault is IERC7432, IERC7432VaultExtension { +contract NftRolesRegistryVault is IERC7432 { struct RoleData { address recipient; uint64 expirationDate; @@ -23,7 +22,7 @@ contract NftRolesRegistryVault is IERC7432, IERC7432VaultExtension { // owner => tokenAddress => operator => isApproved mapping(address => mapping(address => mapping(address => bool))) public tokenApprovals; - /** ERC-7432 External Functions **/ + /** External Functions **/ function grantRole(IERC7432.Role calldata _role) external override { require(_role.expirationDate > block.timestamp, 'NftRolesRegistryVault: expiration date must be in the future'); @@ -76,6 +75,21 @@ contract NftRolesRegistryVault is IERC7432, IERC7432VaultExtension { emit RoleRevoked(_tokenAddress, _tokenId, _roleId); } + function unlockToken(address _tokenAddress, uint256 _tokenId) external override { + address originalOwner = originalOwners[_tokenAddress][_tokenId]; + + require(_isLocked(_tokenAddress, _tokenId), 'NftRolesRegistryVault: NFT is locked'); + + require( + originalOwner == msg.sender || isRoleApprovedForAll(_tokenAddress, originalOwner, msg.sender), + 'NftRolesRegistryVault: sender must be owner or approved' + ); + + delete originalOwners[_tokenAddress][_tokenId]; + IERC721(_tokenAddress).transferFrom(address(this), originalOwner, _tokenId); + emit TokenUnlocked(originalOwner, _tokenAddress, _tokenId); + } + function setRoleApprovalForAll(address _tokenAddress, address _operator, bool _approved) external override { tokenApprovals[msg.sender][_tokenAddress][_operator] = _approved; emit RoleApprovalForAll(_tokenAddress, _operator, _approved); @@ -83,6 +97,10 @@ contract NftRolesRegistryVault is IERC7432, IERC7432VaultExtension { /** ERC-7432 View Functions **/ + function ownerOf(address _tokenAddress, uint256 _tokenId) external view returns (address owner_) { + return originalOwners[_tokenAddress][_tokenId]; + } + function recipientOf( address _tokenAddress, uint256 _tokenId, @@ -134,31 +152,10 @@ contract NftRolesRegistryVault is IERC7432, IERC7432VaultExtension { return tokenApprovals[_owner][_tokenAddress][_operator]; } - /** ERC-7432 Vault Extension Functions **/ - - function withdraw(address _tokenAddress, uint256 _tokenId) external override { - address originalOwner = originalOwners[_tokenAddress][_tokenId]; - - require(_isWithdrawable(_tokenAddress, _tokenId), 'NftRolesRegistryVault: NFT is not withdrawable'); - - require( - originalOwner == msg.sender || isRoleApprovedForAll(_tokenAddress, originalOwner, msg.sender), - 'NftRolesRegistryVault: sender must be owner or approved' - ); - - delete originalOwners[_tokenAddress][_tokenId]; - IERC721(_tokenAddress).transferFrom(address(this), originalOwner, _tokenId); - emit Withdraw(originalOwner, _tokenAddress, _tokenId); - } - - function ownerOf(address _tokenAddress, uint256 _tokenId) external view returns (address owner_) { - return originalOwners[_tokenAddress][_tokenId]; - } - /** ERC-165 Functions **/ function supportsInterface(bytes4 interfaceId) external view virtual override returns (bool) { - return interfaceId == type(IERC7432).interfaceId || interfaceId == type(IERC7432VaultExtension).interfaceId; + return interfaceId == type(IERC7432).interfaceId; } /** Internal Functions **/ @@ -186,6 +183,7 @@ contract NftRolesRegistryVault is IERC7432, IERC7432VaultExtension { IERC721(_tokenAddress).transferFrom(_currentOwner, address(this), _tokenId); originalOwners[_tokenAddress][_tokenId] = _currentOwner; originalOwner_ = _currentOwner; + emit TokenLocked(_currentOwner, _tokenAddress, _tokenId); } } @@ -209,12 +207,12 @@ contract NftRolesRegistryVault is IERC7432, IERC7432VaultExtension { revert('NftRolesRegistryVault: role does not exist or sender is not approved'); } - /// @notice Check if an NFT is withdrawable. + /// @notice Checks if an NFT is locked. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. - /// @return True if the NFT is withdrawable. - function _isWithdrawable(address _tokenAddress, uint256 _tokenId) internal view returns (bool) { - // todo needs to implement a way to track expiration dates to make sure NFTs are withdrawable + /// @return True if the NFT is locked. + function _isLocked(address _tokenAddress, uint256 _tokenId) internal view returns (bool) { + // todo needs to implement a way to track expiration dates to make sure NFTs are not locked // mocked result return _isTokenDeposited(_tokenAddress, _tokenId); } diff --git a/contracts/interfaces/IERC7432.sol b/contracts/interfaces/IERC7432.sol index d0f103d..beba086 100644 --- a/contracts/interfaces/IERC7432.sol +++ b/contracts/interfaces/IERC7432.sol @@ -6,7 +6,7 @@ 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 0xfecc9ed3. +/// Note: the ERC-165 identifier for this interface is 0xd00ca5cf. interface IERC7432 is IERC165 { struct Role { bytes32 roleId; @@ -20,6 +20,12 @@ interface IERC7432 is IERC165 { /** Events **/ + /// @notice Emitted when an NFT is locked (deposited or frozen). + /// @param _owner The owner of the NFT. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + event TokenLocked(address indexed _owner, address indexed _tokenAddress, uint256 _tokenId); + /// @notice Emitted when a role is granted. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. @@ -46,6 +52,12 @@ interface IERC7432 is IERC165 { /// @param _roleId The role identifier. event RoleRevoked(address indexed _tokenAddress, uint256 indexed _tokenId, bytes32 indexed _roleId); + /// @notice Emitted when an NFT is unlocked (withdrawn or unfrozen). + /// @param _owner The original owner of the NFT. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + event TokenUnlocked(address indexed _owner, address indexed _tokenAddress, uint256 indexed _tokenId); + /// @notice Emitted when a user is approved to manage roles on behalf of another user. /// @param _tokenAddress The token address. /// @param _operator The user approved to grant and revoke roles. @@ -64,6 +76,12 @@ interface IERC7432 is IERC165 { /// @param _roleId The role identifier. function revokeRole(address _tokenAddress, uint256 _tokenId, bytes32 _roleId) external; + /// @notice Unlocks NFT (transfer back to original owner or unfreeze it). + /// @dev Reverts if sender is not approved or the original owner. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + function unlockToken(address _tokenAddress, uint256 _tokenId) external; + /// @notice Approves operator to grant and revoke roles on behalf of another user. /// @param _tokenAddress The token address. /// @param _operator The user approved to grant and revoke roles. @@ -72,6 +90,12 @@ interface IERC7432 is IERC165 { /** View Functions **/ + /// @notice Retrieves the owner of NFT. + /// @param _tokenAddress The token address. + /// @param _tokenId The token identifier. + /// @return owner_ The owner of the token. + function ownerOf(address _tokenAddress, uint256 _tokenId) external view returns (address owner_); + /// @notice Retrieves the recipient of an NFT role. /// @param _tokenAddress The token address. /// @param _tokenId The token identifier. diff --git a/contracts/interfaces/IERC7432VaultExtension.sol b/contracts/interfaces/IERC7432VaultExtension.sol deleted file mode 100644 index 1e56014..0000000 --- a/contracts/interfaces/IERC7432VaultExtension.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.9; - -/// @title ERC-7432 Vault Extension -/// @dev See https://eips.ethereum.org/EIPS/eip-7432 -/// Note: the ERC-165 identifier for this interface is 0xecd7217f. -interface IERC7432VaultExtension { - /** Events **/ - - /// @notice Emitted when an NFT is withdrawn. - /// @param _owner The original owner of the NFT. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - event Withdraw(address indexed _owner, address indexed _tokenAddress, uint256 indexed _tokenId); - - /** External Functions **/ - - /// @notice Withdraw NFT back to original owner. - /// @dev Reverts if sender is not approved or the original owner. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - function withdraw(address _tokenAddress, uint256 _tokenId) external; - - /** View Functions **/ - - /// @notice Retrieves the owner of a deposited NFT. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @return owner_ The owner of the token. - function ownerOf(address _tokenAddress, uint256 _tokenId) external view returns (address owner_); -} diff --git a/test/ERC7432/ERC7432WrapperForERC4907.spec.ts b/test/ERC7432/ERC7432WrapperForERC4907.spec.ts index 5efecdd..54523c1 100644 --- a/test/ERC7432/ERC7432WrapperForERC4907.spec.ts +++ b/test/ERC7432/ERC7432WrapperForERC4907.spec.ts @@ -7,7 +7,7 @@ import { buildRole, getExpiredDate } from './mockData' import { expect } from 'chai' import { generateErc165InterfaceId, ROLE, THREE_MONTHS } from '../helpers' import { beforeEach } from 'mocha' -import { IERC7432__factory, IERC7432VaultExtension__factory, IERC721Receiver__factory } from '../../typechain-types' +import { IERC7432__factory, IERC721Receiver__factory } from '../../typechain-types' const UserRole = 'User()' const NovaCreedTokenAddress = '0x8a514a40ed06fc44b6e0c9875cdd58e20063d10e' @@ -89,6 +89,8 @@ describe('ERC7432WrapperForERC4907', async () => { role.revocable, role.data, ) + .to.emit(ERC7432WrapperForERC4907, 'TokenLocked') + .withArgs(owner.address, role.tokenAddress, role.tokenId) .to.emit(WrappedErc721Token, 'UpdateUser') .withArgs(role.tokenId, recipient, role.expirationDate) } @@ -182,6 +184,8 @@ describe('ERC7432WrapperForERC4907', async () => { role.revocable, role.data, ) + .to.emit(ERC7432WrapperForERC4907, 'TokenLocked') + .withArgs(owner.address, role.tokenAddress, role.tokenId) .to.emit(WrappedErc721Token, 'UpdateUser') .withArgs(role.tokenId, role.recipient, role.expirationDate) }) @@ -215,6 +219,7 @@ describe('ERC7432WrapperForERC4907', async () => { .withArgs(role.tokenId, role.recipient, role.expirationDate) .to.not.emit(Erc721Token, 'Transfer') .to.not.emit(WrappedErc721Token, 'Transfer') + .to.not.emit(ERC7432WrapperForERC4907, 'TokenLocked') }) it('should grant role when sender is approved', async () => { @@ -239,6 +244,7 @@ describe('ERC7432WrapperForERC4907', async () => { .withArgs(role.tokenId, role.recipient, role.expirationDate) .to.not.emit(Erc721Token, 'Transfer') .to.not.emit(WrappedErc721Token, 'Transfer') + .to.not.emit(ERC7432WrapperForERC4907, 'TokenLocked') }) it('should revert when there is a non-expired and non-revocable role', async () => { @@ -326,6 +332,7 @@ describe('ERC7432WrapperForERC4907', async () => { role.data, ) .to.not.emit(Erc721Token, 'Transfer') + .to.not.emit(ERC7432WrapperForERC4907, 'TokenLocked') await time.increase(THREE_MONTHS) await expect(ERC7432WrapperForERC4907.connect(owner).revokeRole(role.tokenAddress, role.tokenId, role.roleId)) .to.emit(ERC7432WrapperForERC4907, 'RoleRevoked') @@ -377,39 +384,39 @@ describe('ERC7432WrapperForERC4907', async () => { }) }) - describe('withdraw', async () => { + describe('unlockToken', async () => { beforeEach(async () => { await depositNftAndGrantRole({ recipient: recipient.address }) }) it('should revert when token is not supported', async () => { - await expect(ERC7432WrapperForERC4907.connect(owner).withdraw(AddressZero, role.roleId)).to.be.revertedWith( + await expect(ERC7432WrapperForERC4907.connect(owner).unlockToken(AddressZero, role.roleId)).to.be.revertedWith( 'ERC7432WrapperForERC4907: token not supported', ) }) it('should revert if token is not deposited', async () => { await expect( - ERC7432WrapperForERC4907.connect(owner).withdraw(role.tokenAddress, role.tokenId + 1), + ERC7432WrapperForERC4907.connect(owner).unlockToken(role.tokenAddress, role.tokenId + 1), ).to.be.revertedWith('ERC7432WrapperForERC4907: sender must be owner or approved') }) it('should revert if sender is not original owner or approved', async () => { await expect( - ERC7432WrapperForERC4907.connect(anotherUser).withdraw(role.tokenAddress, role.tokenId), + ERC7432WrapperForERC4907.connect(anotherUser).unlockToken(role.tokenAddress, role.tokenId), ).to.be.revertedWith('ERC7432WrapperForERC4907: sender must be owner or approved') }) it('should revert if role is not revocable and not expired', async () => { await ERC7432WrapperForERC4907.connect(owner).grantRole({ ...role, revocable: false }) await expect( - ERC7432WrapperForERC4907.connect(owner).withdraw(role.tokenAddress, role.tokenId), - ).to.be.revertedWith('ERC7432WrapperForERC4907: token is not withdrawable') + ERC7432WrapperForERC4907.connect(owner).unlockToken(role.tokenAddress, role.tokenId), + ).to.be.revertedWith('ERC7432WrapperForERC4907: token has a non-revocable role active') }) - it('should withdraw if sender is owner and NFT is withdrawable', async () => { - await expect(ERC7432WrapperForERC4907.connect(owner).withdraw(role.tokenAddress, role.tokenId)) - .to.emit(ERC7432WrapperForERC4907, 'Withdraw') + it('should unlock token if sender is owner and NFT is not locked', async () => { + await expect(ERC7432WrapperForERC4907.connect(owner).unlockToken(role.tokenAddress, role.tokenId)) + .to.emit(ERC7432WrapperForERC4907, 'TokenUnlocked') .withArgs(owner.address, role.tokenAddress, role.tokenId) .to.emit(WrappedErc721Token, 'Redeem') .withArgs(ERC7432WrapperForERC4907.address, role.tokenAddress, role.tokenId) @@ -420,8 +427,8 @@ describe('ERC7432WrapperForERC4907', async () => { it('should revert if role is not revocable, but is expired', async () => { await ERC7432WrapperForERC4907.connect(owner).grantRole({ ...role, revocable: false }) await time.increase(THREE_MONTHS) - await expect(ERC7432WrapperForERC4907.connect(owner).withdraw(role.tokenAddress, role.tokenId)) - .to.emit(ERC7432WrapperForERC4907, 'Withdraw') + await expect(ERC7432WrapperForERC4907.connect(owner).unlockToken(role.tokenAddress, role.tokenId)) + .to.emit(ERC7432WrapperForERC4907, 'TokenUnlocked') .withArgs(owner.address, role.tokenAddress, role.tokenId) .to.emit(WrappedErc721Token, 'Redeem') .withArgs(ERC7432WrapperForERC4907.address, role.tokenAddress, role.tokenId) @@ -429,10 +436,10 @@ describe('ERC7432WrapperForERC4907', async () => { .withArgs(ERC7432WrapperForERC4907.address, owner.address, role.tokenId) }) - it('should withdraw if sender is approved and NFT is withdrawable', async () => { + it('should unlock token if sender is approved and NFT is not locked', async () => { await ERC7432WrapperForERC4907.connect(owner).setRoleApprovalForAll(role.tokenAddress, anotherUser.address, true) - await expect(ERC7432WrapperForERC4907.connect(anotherUser).withdraw(role.tokenAddress, role.tokenId)) - .to.emit(ERC7432WrapperForERC4907, 'Withdraw') + await expect(ERC7432WrapperForERC4907.connect(anotherUser).unlockToken(role.tokenAddress, role.tokenId)) + .to.emit(ERC7432WrapperForERC4907, 'TokenUnlocked') .withArgs(owner.address, role.tokenAddress, role.tokenId) .to.emit(WrappedErc721Token, 'Redeem') .withArgs(ERC7432WrapperForERC4907.address, role.tokenAddress, role.tokenId) @@ -542,12 +549,6 @@ describe('ERC7432WrapperForERC4907', async () => { expect(await ERC7432WrapperForERC4907.supportsInterface(ifaceId)).to.be.true }) - it('should return true when IERC7432VaultExtension identifier is provided', async () => { - const iface = IERC7432VaultExtension__factory.createInterface() - const ifaceId = generateErc165InterfaceId(iface) - expect(await ERC7432WrapperForERC4907.supportsInterface(ifaceId)).to.be.true - }) - it('should return true when IERC721Receiver identifier is provided', async () => { const iface = IERC721Receiver__factory.createInterface() const ifaceId = generateErc165InterfaceId(iface) diff --git a/test/ERC7432/NftRolesRegistryVault.ts b/test/ERC7432/NftRolesRegistryVault.ts index 6a30b6d..3015561 100644 --- a/test/ERC7432/NftRolesRegistryVault.ts +++ b/test/ERC7432/NftRolesRegistryVault.ts @@ -6,7 +6,7 @@ import { loadFixture, time } from '@nomicfoundation/hardhat-network-helpers' import { Role } from '../types' import { buildRole, getExpiredDate } from './mockData' import { expect } from 'chai' -import { IERC7432__factory, IERC7432VaultExtension__factory } from '../../typechain-types' +import { IERC7432__factory } from '../../typechain-types' import { generateErc165InterfaceId, ONE_DAY } from '../helpers' const { AddressZero, HashZero } = ethers.constants @@ -46,6 +46,8 @@ describe('NftRolesRegistryVault', () => { ) .to.emit(MockErc721Token, 'Transfer') .withArgs(owner.address, NftRolesRegistryVault.address, role.tokenId) + .to.emit(NftRolesRegistryVault, 'TokenLocked') + .withArgs(owner.address, role.tokenAddress, role.tokenId) } beforeEach(async () => { @@ -111,6 +113,8 @@ describe('NftRolesRegistryVault', () => { ) .to.emit(MockErc721Token, 'Transfer') .withArgs(owner.address, NftRolesRegistryVault.address, role.tokenId) + .to.emit(NftRolesRegistryVault, 'TokenLocked') + .withArgs(owner.address, role.tokenAddress, role.tokenId) }) }) @@ -139,6 +143,7 @@ describe('NftRolesRegistryVault', () => { role.data, ) .to.not.emit(MockErc721Token, 'Transfer') + .to.not.emit(NftRolesRegistryVault, 'TokenLocked') }) it('should grant role when sender is approved', async () => { @@ -156,6 +161,7 @@ describe('NftRolesRegistryVault', () => { role.data, ) .to.not.emit(MockErc721Token, 'Transfer') + .to.not.emit(NftRolesRegistryVault, 'TokenLocked') }) it('should revert when there is a non-expired and non-revocable role', async () => { @@ -219,6 +225,8 @@ describe('NftRolesRegistryVault', () => { role.data, ) .to.not.emit(MockErc721Token, 'Transfer') + .to.not.emit(NftRolesRegistryVault, 'TokenLocked') + await time.increase(ONE_DAY) await expect(NftRolesRegistryVault.connect(owner).revokeRole(role.tokenAddress, role.tokenId, role.roleId)) .to.emit(NftRolesRegistryVault, 'RoleRevoked') @@ -256,35 +264,35 @@ describe('NftRolesRegistryVault', () => { }) }) - describe('withdraw', () => { + describe('unlockToken', () => { beforeEach(async () => { await depositNftAndGrantRole({ recipient: recipient.address }) }) it('should revert if token is not deposited', async () => { await expect( - NftRolesRegistryVault.connect(owner).withdraw(role.tokenAddress, role.tokenId + 1), - ).to.be.revertedWith('NftRolesRegistryVault: NFT is not withdrawable') + NftRolesRegistryVault.connect(owner).unlockToken(role.tokenAddress, role.tokenId + 1), + ).to.be.revertedWith('NftRolesRegistryVault: NFT is locked') }) it('should revert if sender is not original owner or approved', async () => { await expect( - NftRolesRegistryVault.connect(anotherUser).withdraw(role.tokenAddress, role.tokenId), + NftRolesRegistryVault.connect(anotherUser).unlockToken(role.tokenAddress, role.tokenId), ).to.be.revertedWith('NftRolesRegistryVault: sender must be owner or approved') }) - it('should withdraw if sender is owner and NFT is withdrawable', async () => { - await expect(NftRolesRegistryVault.connect(owner).withdraw(role.tokenAddress, role.tokenId)) - .to.emit(NftRolesRegistryVault, 'Withdraw') + it('should unlock token if sender is owner and NFT is not locked', async () => { + await expect(NftRolesRegistryVault.connect(owner).unlockToken(role.tokenAddress, role.tokenId)) + .to.emit(NftRolesRegistryVault, 'TokenUnlocked') .withArgs(owner.address, role.tokenAddress, role.tokenId) .to.emit(MockErc721Token, 'Transfer') .withArgs(NftRolesRegistryVault.address, owner.address, role.tokenId) }) - it('should withdraw if sender is approved and NFT is withdrawable', async () => { + it('should unlock token if sender is approved and NFT is not locked', async () => { await NftRolesRegistryVault.connect(owner).setRoleApprovalForAll(role.tokenAddress, anotherUser.address, true) - await expect(NftRolesRegistryVault.connect(anotherUser).withdraw(role.tokenAddress, role.tokenId)) - .to.emit(NftRolesRegistryVault, 'Withdraw') + await expect(NftRolesRegistryVault.connect(anotherUser).unlockToken(role.tokenAddress, role.tokenId)) + .to.emit(NftRolesRegistryVault, 'TokenUnlocked') .withArgs(owner.address, role.tokenAddress, role.tokenId) .to.emit(MockErc721Token, 'Transfer') .withArgs(NftRolesRegistryVault.address, owner.address, role.tokenId) @@ -364,11 +372,5 @@ describe('NftRolesRegistryVault', () => { const ifaceId = generateErc165InterfaceId(iface) expect(await NftRolesRegistryVault.supportsInterface(ifaceId)).to.be.true }) - - it('should return true when IERC7432VaultExtension identifier is provided', async () => { - const iface = IERC7432VaultExtension__factory.createInterface() - const ifaceId = generateErc165InterfaceId(iface) - expect(await NftRolesRegistryVault.supportsInterface(ifaceId)).to.be.true - }) }) })