Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ON-811: Polishing ERC-7432 #29

Merged
merged 2 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading