Skip to content

Commit

Permalink
ON-811: Polishing ERC-7432 (#29)
Browse files Browse the repository at this point in the history
* ON-811: included event TokensCommitted on ERC-7432

* ON-811: refactor ERC-7432
  • Loading branch information
ernanirst authored Apr 24, 2024
1 parent 694bbc3 commit 4ac82c7
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 138 deletions.
72 changes: 34 additions & 38 deletions contracts/ERC7432/ERC7432WrapperForERC4907.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,7 +33,7 @@ contract ERC7432WrapperForERC4907 is IERC7432, IERC7432VaultExtension, ERC721Hol
_;
}

/** ERC-7432 External Functions **/
/** External Functions **/

constructor(address _oriumWrapperManagerAddress) {
oriumWrapperManager = _oriumWrapperManagerAddress;
Expand All @@ -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'
);

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 **/
Expand Down Expand Up @@ -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);
}
}

Expand Down
56 changes: 27 additions & 29 deletions contracts/ERC7432/NftRolesRegistryVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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');
Expand Down Expand Up @@ -76,13 +75,32 @@ 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);
}

/** 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,
Expand Down Expand Up @@ -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 **/
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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);
}
Expand Down
26 changes: 25 additions & 1 deletion contracts/interfaces/IERC7432.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down
32 changes: 0 additions & 32 deletions contracts/interfaces/IERC7432VaultExtension.sol

This file was deleted.

Loading

0 comments on commit 4ac82c7

Please sign in to comment.