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-876: Review ERC-7589 interface #33

Merged
merged 5 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
309 changes: 309 additions & 0 deletions contracts/ERC7589/ERC7589RolesRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity 0.8.9;

import { IERC165 } from '@openzeppelin/contracts/utils/introspection/IERC165.sol';
import { IERC1155Receiver } from '@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol';
import { ERC1155Holder, ERC1155Receiver } from '@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol';
import { IERC7589 } from '../interfaces/IERC7589.sol';
import { IERC1155 } from '@openzeppelin/contracts/token/ERC1155/IERC1155.sol';
import { IERC7589LockTokensAndGrantRoleExtension } from '../interfaces/IERC7589LockTokensAndGrantRoleExtension.sol';
import { Uint64SortedLinkedListLibrary } from '../libraries/Uint64SortedLinkedListLibrary.sol';
import { IOriumWrapperManager } from '../interfaces/IOriumWrapperManager.sol';

contract ERC7589RolesRegistry is IERC7589, ERC1155Holder, IERC7589LockTokensAndGrantRoleExtension {
using Uint64SortedLinkedListLibrary for Uint64SortedLinkedListLibrary.List;

struct TokenLock {
address owner;
address tokenAddress;
uint256 tokenId;
uint256 tokenAmount;
}

struct Role {
address recipient;
uint64 expirationDate;
bool revocable;
bytes data;
}

address public managerAddress;

address public marketplaceAddress;

uint256 public lockIdCount;

// tokenAddress => isAllowed
mapping(address => bool) public isTokenAddressAllowed;

// lockId => TokenLock
mapping(uint256 => TokenLock) public tokenLocks;

// lockId => roleId => Role
mapping(uint256 => mapping(bytes32 => Role)) public roles;

// tokenAddress => tokenId => List
mapping(uint256 => Uint64SortedLinkedListLibrary.List) public tokenLockExpirationDates;

// ownerAddress => tokenAddress => operator => isApproved
mapping(address => mapping(address => mapping(address => bool))) public roleApprovals;

// supportedInterfaces => bool
mapping(bytes4 => bool) private supportedInterfaces;

modifier onlyManager() {
require(msg.sender == managerAddress, 'ERC7589RolesRegistry: sender is not manager');
_;
}

modifier onlyAllowedTokenAddress(address _tokenAddress) {
require(isTokenAddressAllowed[_tokenAddress], 'ERC7589RolesRegistry: tokenAddress is not allowed');
_;
}

modifier onlyOwnerOrApproved(address _account, address _tokenAddress) {
require(
msg.sender == _account || isRoleApprovedForAll(_tokenAddress, _account, msg.sender),
'ERC7589RolesRegistry: sender is not owner or approved'
);
_;
}

modifier onlyTokenLockOwnerOrApproved(uint256 _lockId) {
TokenLock storage _tokenLock = tokenLocks[_lockId];
require(
msg.sender == _tokenLock.owner ||
isRoleApprovedForAll(_tokenLock.tokenAddress, _tokenLock.owner, msg.sender),
'ERC7589RolesRegistry: sender is not owner or approved'
);
_;
}

constructor(address _marketplaceAddress) {
managerAddress = msg.sender;
marketplaceAddress = _marketplaceAddress;
supportedInterfaces[type(IERC7589).interfaceId] = true;
supportedInterfaces[type(IERC1155Receiver).interfaceId] = true;
supportedInterfaces[type(IERC7589LockTokensAndGrantRoleExtension).interfaceId] = true;
}

/** ERC-7589 External Functions **/

function lockTokens(
address _owner,
address _tokenAddress,
uint256 _tokenId,
uint256 _tokenAmount
) external returns (uint256 lockId_) {
lockId_ = _lockTokens(_owner, _tokenAddress, _tokenId, _tokenAmount);
}

function grantRole(
uint256 _lockId,
bytes32 _roleId,
address _recipient,
uint64 _expirationDate,
bool _revocable,
bytes calldata _data
) external {
_grantERC7589Role(_lockId, _roleId, _recipient, _expirationDate, _revocable, _data);
}

function revokeRole(uint256 _lockId, bytes32 _roleId, address _recipient) external {
TokenLock storage _tokenLock = tokenLocks[_lockId];
Role storage _role = roles[_lockId][_roleId];
require(_role.expirationDate > block.timestamp, 'ERC7589RolesRegistry: role does not exist');

// ensure caller is approved
address caller = _findCaller(_tokenLock.owner, _role.recipient, _tokenLock.tokenAddress);
if (_role.expirationDate > block.timestamp && !_role.revocable) {
// if role is not expired and is not revocable, only the grantee can revoke it
require(
caller == _role.recipient,
'ERC7589RolesRegistry: role is not revocable or caller is not the approved'
);
}

if (!_role.revocable) {
tokenLockExpirationDates[_lockId].remove(_role.expirationDate);
}

delete roles[_lockId][_roleId];
emit RoleRevoked(_lockId, _roleId, _recipient);
}

function unlockTokens(uint256 _lockId) external onlyTokenLockOwnerOrApproved(_lockId) {
uint64 _headExpirationDate = tokenLockExpirationDates[_lockId].head;
require(_headExpirationDate < block.timestamp, 'ERC7589RolesRegistry: NFT is locked');

address _owner = tokenLocks[_lockId].owner;
address _tokenAddress = tokenLocks[_lockId].tokenAddress;
uint256 _tokenId = tokenLocks[_lockId].tokenId;
uint256 _tokenAmount = tokenLocks[_lockId].tokenAmount;
delete tokenLocks[_lockId];
emit TokensUnlocked(_lockId);

_transferFrom(address(this), _owner, _tokenAddress, _tokenId, _tokenAmount);
ernanirst marked this conversation as resolved.
Show resolved Hide resolved
}

function setRoleApprovalForAll(address _tokenAddress, address _operator, bool _approved) external {
roleApprovals[msg.sender][_tokenAddress][_operator] = _approved;
}

/** ERC-7589 Lock Tokens and Grant Role Extension External Functions **/

function lockTokensAndGrantRole(
address _owner,
address _tokenAddress,
uint256 _tokenId,
uint256 _tokenAmount,
bytes32 _roleId,
address _recipient,
uint64 _expirationDate,
bool _revocable,
bytes calldata _data
) external returns (uint256 lockId_) {
lockId_ = _lockTokens(_owner, _tokenAddress, _tokenId, _tokenAmount);
_grantERC7589Role(lockId_, _roleId, _recipient, _expirationDate, _revocable, _data);
}

/** Manager External Functions **/

function setMarketplaceAddress(address _marketplaceAddress) external onlyManager {
marketplaceAddress = _marketplaceAddress;
}

function setTokenAddressAllowed(address _tokenAddress, bool _isAllowed) external onlyManager {
isTokenAddressAllowed[_tokenAddress] = _isAllowed;
}

function setManagerAddress(address _managerAddress) external onlyManager {
managerAddress = _managerAddress;
}

/** View Functions **/

function ownerOf(uint256 _lockId) external view returns (address owner_) {
return tokenLocks[_lockId].owner;
}

function tokenAddressOf(uint256 _lockId) external view returns (address tokenAddress_) {
return tokenLocks[_lockId].tokenAddress;
}

function tokenIdOf(uint256 _lockId) external view returns (uint256 tokenId_) {
return tokenLocks[_lockId].tokenId;
}

function tokenAmountOf(uint256 _lockId) external view returns (uint256 tokenAmount_) {
return tokenLocks[_lockId].tokenAmount;
}

function roleData(uint256 _lockId, bytes32 _roleId) external view returns (bytes memory data_) {
if (roles[_lockId][_roleId].expirationDate > block.timestamp) {
return roles[_lockId][_roleId].data;
}
return '';
}

function roleExpirationDate(uint256 _lockId, bytes32 _roleId) external view returns (uint64 expirationDate_) {
if (roles[_lockId][_roleId].expirationDate > block.timestamp) {
return roles[_lockId][_roleId].expirationDate;
}
return 0;
}

function isRoleRevocable(uint256 _lockId, bytes32 _roleId) external view returns (bool revocable_) {
if (roles[_lockId][_roleId].expirationDate > block.timestamp) {
return roles[_lockId][_roleId].revocable;
}
return false;
}

function isRoleApprovedForAll(
address _tokenAddress,
address _owner,
address _operator
) public view returns (bool isApproved_) {
return _operator == marketplaceAddress || roleApprovals[_owner][_tokenAddress][_operator];
}

/** ERC-165 View Functions **/

function supportsInterface(
bytes4 interfaceId
) public view virtual override(IERC165, ERC1155Receiver) returns (bool) {
return supportedInterfaces[interfaceId];
}

/** Helper Functions **/

function _lockTokens(
address _owner,
address _tokenAddress,
uint256 _tokenId,
uint256 _tokenAmount
)
private
onlyAllowedTokenAddress(_tokenAddress)
onlyOwnerOrApproved(_owner, _tokenAddress)
returns (uint256 lockId_)
{
require(_tokenAmount > 0, 'ERC7589RolesRegistry: tokenAmount must be greater than zero');
lockId_ = ++lockIdCount;
tokenLocks[lockId_] = TokenLock(_owner, _tokenAddress, _tokenId, _tokenAmount);
emit TokensLocked(_owner, lockId_, _tokenAddress, _tokenId, _tokenAmount);
_transferFrom(_owner, address(this), _tokenAddress, _tokenId, _tokenAmount);
ernanirst marked this conversation as resolved.
Show resolved Hide resolved
}

function _grantERC7589Role(
uint256 _lockId,
bytes32 _roleId,
address _recipient,
uint64 _expirationDate,
bool _revocable,
bytes calldata _data
) private onlyTokenLockOwnerOrApproved(_lockId) {
require(_expirationDate > block.timestamp, 'ERC7589RolesRegistry: expirationDate must be in the future');

// only grant new role if previous role is expired or revocable
Role storage _currentRole = roles[_lockId][_roleId];
require(
_currentRole.expirationDate < block.timestamp || _currentRole.revocable,
'ERC7589RolesRegistry: role is not expired nor revocable'
);

// if role is not revocable
if (!_revocable) {
// add expiration date to lock list
tokenLockExpirationDates[_lockId].insert(_expirationDate);
}

roles[_lockId][_roleId] = Role(_recipient, _expirationDate, _revocable, _data);
emit RoleGranted(_lockId, _roleId, _recipient, _expirationDate, _revocable, _data);
}

function _transferFrom(
address _from,
address _to,
address _tokenAddress,
uint256 _tokenId,
uint256 _tokenAmount
) internal {
IERC1155(_tokenAddress).safeTransferFrom(_from, _to, _tokenId, _tokenAmount, '');
}

// careful with the following edge case:
// if both owner and recipient approve the sender, the recipient should be returned
// if owner is returned instead, the recipient won't be able to revoke roles
function _findCaller(address _owner, address _recipient, address _tokenAddress) internal view returns (address) {
if (_recipient == msg.sender || isRoleApprovedForAll(_tokenAddress, _recipient, msg.sender)) {
return _recipient;
}
if (_owner == msg.sender || isRoleApprovedForAll(_tokenAddress, _owner, msg.sender)) {
return _owner;
}
revert('ERC7589RolesRegistry: sender is not approved');
}
}
Loading
Loading