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-543: Single-role ERC-1155 Role Delegation Registry #20

Merged
merged 31 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4ff0954
wip
ernanirst Oct 26, 2023
9c5f5b8
using nonce
ernanirst Oct 31, 2023
03154fc
implemented SftRolesRegistry with array limit
ernanirst Nov 7, 2023
073bf91
removed bitmap
ernanirst Nov 8, 2023
8e9dcaa
removed eip721
ernanirst Nov 8, 2023
36a3c6f
replaced recordId with nonce
ernanirst Nov 8, 2023
f21a6d0
implemented linked lists to store role assignments
ernanirst Nov 8, 2023
8b2e852
fixed bug on linked list insert and add tests
ernanirst Nov 9, 2023
a08f2b5
renamed header to head
ernanirst Nov 10, 2023
985cbdc
fixed bug when comparing expiration date and added more tests
ernanirst Nov 10, 2023
b5d656a
fixed bug when removing the head of a non-empty list, and included tests
ernanirst Nov 10, 2023
56cb636
preventing user to try to manipulate items of different lists, finish…
ernanirst Nov 10, 2023
6206935
renamed contract in the logs
ernanirst Nov 11, 2023
dd37dcf
wip tests 1155 roles registry
ernanirst Nov 11, 2023
3502b89
included more tests for grantRoleFrom function
ernanirst Nov 11, 2023
b57bc3e
fixed revokeRoleFrom and included tests
ernanirst Nov 12, 2023
191e4ad
finish tests
ernanirst Nov 12, 2023
23d54b8
fixed tests
ernanirst Nov 12, 2023
65c5672
wip validate erc165 supportsInterface
ernanirst Nov 13, 2023
e2aa663
fixed lint
ernanirst Nov 13, 2023
68ecd7b
one role registry
Nov 29, 2023
b18f39f
improve test coverage
Dec 4, 2023
daa61c8
100% coverage
Dec 5, 2023
7cfb2ae
ON-543: refactor
ernanirst Dec 11, 2023
6885f5d
ON-543: fixed file
ernanirst Dec 11, 2023
13e7821
ON-543: 100% coverage
Dec 11, 2023
7961281
Update contracts/RolesRegistry/interfaces/IERCXXXX.sol
karacurt Dec 11, 2023
1fcd8ba
ON-543: pr fixes
Dec 11, 2023
f65438d
ON-543: pr fixes
Dec 12, 2023
26b81d3
ON-543: add role and grantee validation and tests
Dec 12, 2023
f4c83d9
ON-543: PR fixes
Dec 12, 2023
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
249 changes: 249 additions & 0 deletions contracts/RolesRegistry/SftRolesRegistrySingleRole.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity 0.8.9;

import { IERCXXXX } from './interfaces/IERCXXXX.sol';
import { IERC165 } from '@openzeppelin/contracts/utils/introspection/IERC165.sol';
import { IERC1155 } from '@openzeppelin/contracts/token/ERC1155/IERC1155.sol';
import { IERC1155Receiver } from '@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol';
import { ERC1155Holder, ERC1155Receiver } from '@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol';
import { ERC165Checker } from '@openzeppelin/contracts/utils/introspection/ERC165Checker.sol';

// Semi-fungible token (SFT) registry with only one role (UNIQUE_ROLE)
contract SftRolesRegistrySingleRole is IERCXXXX, ERC1155Holder {
bytes32 public constant UNIQUE_ROLE = keccak256('UNIQUE_ROLE');

// grantor => tokenAddress => operator => isApproved
mapping(address => mapping(address => mapping(address => bool))) public tokenApprovals;

// nonce => DepositInfo
mapping(uint256 => DepositInfo) public deposits;

// nonce => RoleAssignment
mapping(uint256 => RoleData) internal roleAssignments;

modifier validGrantRoleData(
uint256 _nonce,
uint64 _expirationDate,
uint256 _tokenAmount,
bytes32 _role
) {
require(_nonce > 0, 'SftRolesRegistry: nonce must be greater than zero');
require(_expirationDate > block.timestamp, 'SftRolesRegistry: expiration date must be in the future');
require(_tokenAmount > 0, 'SftRolesRegistry: tokenAmount must be greater than zero');
require(_role == UNIQUE_ROLE, 'SftRolesRegistry: invalid role');
_;
}

modifier onlyOwnerOrApproved(address _account, address _tokenAddress) {
require(
_account == msg.sender || isRoleApprovedForAll(_tokenAddress, _account, msg.sender),
'SftRolesRegistry: account not approved'
);
_;
}

/** External Functions **/

function grantRoleFrom(
RoleAssignment calldata _grantRoleData
)
external
override
validGrantRoleData(_grantRoleData.nonce, _grantRoleData.expirationDate, _grantRoleData.tokenAmount, _grantRoleData.role)
onlyOwnerOrApproved(_grantRoleData.grantor, _grantRoleData.tokenAddress)
{
if (deposits[_grantRoleData.nonce].grantor == address(0)) {
// transfer tokens
_deposit(_grantRoleData);
} else {
// nonce exists
require(
deposits[_grantRoleData.nonce].grantor == _grantRoleData.grantor,
'SftRolesRegistry: grantor mismatch'
);
require(
deposits[_grantRoleData.nonce].tokenAddress == _grantRoleData.tokenAddress,
'SftRolesRegistry: tokenAddress mismatch'
);
require(
deposits[_grantRoleData.nonce].tokenId == _grantRoleData.tokenId,
'SftRolesRegistry: tokenId mismatch'
);
require(
deposits[_grantRoleData.nonce].tokenAmount == _grantRoleData.tokenAmount,
'SftRolesRegistry: tokenAmount mismatch'
);

RoleData storage _roleData = roleAssignments[_grantRoleData.nonce];
require(
_roleData.expirationDate < block.timestamp || _roleData.revocable,
'SftRolesRegistry: nonce is not expired or is not revocable'
);
}
_grantOrUpdateRole(_grantRoleData);
}

function _grantOrUpdateRole(RoleAssignment calldata _grantRoleData) internal {
roleAssignments[_grantRoleData.nonce] = RoleData(
_grantRoleData.grantee,
_grantRoleData.expirationDate,
_grantRoleData.revocable,
_grantRoleData.data
);

emit RoleGranted(
_grantRoleData.nonce,
UNIQUE_ROLE,
_grantRoleData.tokenAddress,
_grantRoleData.tokenId,
_grantRoleData.tokenAmount,
_grantRoleData.grantor,
_grantRoleData.grantee,
_grantRoleData.expirationDate,
_grantRoleData.revocable,
_grantRoleData.data
);
}

function _deposit(RoleAssignment calldata _grantRoleData) internal {
deposits[_grantRoleData.nonce] = DepositInfo(
_grantRoleData.grantor,
_grantRoleData.tokenAddress,
_grantRoleData.tokenId,
_grantRoleData.tokenAmount
);

_transferFrom(
_grantRoleData.grantor,
address(this),
_grantRoleData.tokenAddress,
_grantRoleData.tokenId,
_grantRoleData.tokenAmount
);
}
karacurt marked this conversation as resolved.
Show resolved Hide resolved

function revokeRoleFrom(uint256 _nonce, bytes32 _role, address _grantee) external override {
require(_role == UNIQUE_ROLE, 'SftRolesRegistry: invalid role');

RoleData memory _roleData = roleAssignments[_nonce];
require(_grantee == roleAssignments[_nonce].grantee, 'SftRolesRegistry: invalid grantee or nonce not used');
DepositInfo memory _depositInfo = deposits[_nonce];

address caller = _findCaller(_roleData, _depositInfo);
if (_roleData.expirationDate > block.timestamp && !_roleData.revocable) {
// if role is not expired and is not revocable, only the grantee can revoke it
require(caller == _roleData.grantee, 'SftRolesRegistry: nonce is not expired or is not revocable');
}

delete roleAssignments[_nonce];

emit RoleRevoked(
_nonce,
UNIQUE_ROLE,
_depositInfo.tokenAddress,
_depositInfo.tokenId,
_depositInfo.tokenAmount,
_depositInfo.grantor,
_roleData.grantee
);
}

function withdraw(
uint256 _nonce
) public onlyOwnerOrApproved(deposits[_nonce].grantor, deposits[_nonce].tokenAddress) {
DepositInfo memory _depositInfo = deposits[_nonce];
require(
roleAssignments[_nonce].grantee == address(0) ||
roleAssignments[_nonce].expirationDate < block.timestamp ||
roleAssignments[_nonce].revocable,
'SftRolesRegistry: token has an active role'
);

delete deposits[_nonce];
delete roleAssignments[_nonce];

_transferFrom(
address(this),
_depositInfo.grantor,
_depositInfo.tokenAddress,
_depositInfo.tokenId,
_depositInfo.tokenAmount
);

emit Withdrew(
_nonce,
_depositInfo.grantor,
_depositInfo.tokenAddress,
_depositInfo.tokenId,
_depositInfo.tokenAmount
);
}

function setRoleApprovalForAll(address _tokenAddress, address _operator, bool _isApproved) external override {
tokenApprovals[msg.sender][_tokenAddress][_operator] = _isApproved;
emit RoleApprovalForAll(_tokenAddress, _operator, _isApproved);
}

/** View Functions **/

function roleData(uint256 _nonce, bytes32 _role, address _grantee) external view returns (RoleData memory) {
require(_role == UNIQUE_ROLE, 'SftRolesRegistry: invalid role');
require(_grantee == roleAssignments[_nonce].grantee, 'SftRolesRegistry: invalid grantee or nonce not used');
karacurt marked this conversation as resolved.
Show resolved Hide resolved
return roleAssignments[_nonce];
}

function roleExpirationDate(
uint256 _nonce,
bytes32 _role,
address _grantee
) external view returns (uint64 expirationDate_) {
require(_role == UNIQUE_ROLE, 'SftRolesRegistry: invalid role');
require(_grantee == roleAssignments[_nonce].grantee, 'SftRolesRegistry: invalid grantee or nonce not used');
return roleAssignments[_nonce].expirationDate;
}

function isRoleApprovedForAll(
address _tokenAddress,
address _grantor,
address _operator
) public view override returns (bool) {
return tokenApprovals[_grantor][_tokenAddress][_operator];
}

function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC1155Receiver, IERC165) returns (bool) {
return interfaceId == type(IERCXXXX).interfaceId || interfaceId == type(IERC1155Receiver).interfaceId;
}

/** Helper Functions **/

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

function _findCaller(RoleData memory _roleData, DepositInfo memory _depositInfo) internal view returns (address) {
if (
_depositInfo.grantor == msg.sender ||
isRoleApprovedForAll(_depositInfo.tokenAddress, _depositInfo.grantor, msg.sender)
) {
return _depositInfo.grantor;
}

if (
_roleData.grantee == msg.sender ||
isRoleApprovedForAll(_depositInfo.tokenAddress, _roleData.grantee, msg.sender)
) {
return _roleData.grantee;
}

revert('SftRolesRegistry: sender must be approved');
}
}
133 changes: 133 additions & 0 deletions contracts/RolesRegistry/interfaces/IERCXXXX.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity 0.8.9;

import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/// @title ERC-XXXX Semi-Fungible Token Roles
/// @dev See https://eips.ethereum.org/EIPS/eip-XXXX
/// Note: the ERC-165 identifier for this interface is 0xTBD
interface IERCXXXX is IERC165 {
struct RoleData {
address grantee;
uint64 expirationDate;
bool revocable;
bytes data;
}

struct DepositInfo {
address grantor;
address tokenAddress;
uint256 tokenId;
uint256 tokenAmount;
}

struct RoleAssignment {
uint256 nonce;
bytes32 role;
address tokenAddress;
uint256 tokenId;
uint256 tokenAmount;
address grantor;
address grantee;
uint64 expirationDate;
bool revocable;
bytes data;
}

/** Events **/

/// @notice Emitted when a role is granted.
/// @param _nonce The identifier of the role assignment.
/// @param _role The role identifier.
/// @param _tokenAddress The token address.
/// @param _tokenId The token identifier.
/// @param _tokenAmount The token amount.
/// @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(
uint256 indexed _nonce,
bytes32 indexed _role,
address _tokenAddress,
uint256 _tokenId,
uint256 _tokenAmount,
address _grantor,
address _grantee,
uint64 _expirationDate,
bool _revocable,
bytes _data
);

/// @notice Emitted when a role is revoked.
/// @param _nonce The identifier of the role assignment.
/// @param _role The role identifier.
/// @param _tokenAddress The token address.
/// @param _tokenId The token identifier.
/// @param _tokenAmount The token amount.
/// @param _revoker The user revoking the role.
/// @param _grantee The user that receives the role revocation.
event RoleRevoked(
uint256 indexed _nonce,
bytes32 indexed _role,
address _tokenAddress,
uint256 _tokenId,
uint256 _tokenAmount,
address _revoker,
address _grantee
);

/// @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.
/// @param _isApproved The approval status.
event RoleApprovalForAll(address indexed _tokenAddress, address indexed _operator, bool _isApproved);

/// @notice Emitted when a user withdraws tokens from a role assignment.
/// @param _nonce The identifier of the role assignment.
/// @param _grantor The user withdrawing the tokens.
/// @param _tokenAddress The token address.
/// @param _tokenId The token identifier.
/// @param _tokenAmount The token amount withdrawn.
event Withdrew(uint256 indexed _nonce, address indexed _grantor, address _tokenAddress, uint256 _tokenId, uint256 _tokenAmount);

/** External Functions **/

/// @notice Grants a role on behalf of a user.
/// @param _roleAssignment The role assignment data.
function grantRoleFrom(RoleAssignment calldata _roleAssignment) external;

/// @notice Revokes a role on behalf of a user.
/// @param _nonce The identifier of the role assignment.
/// @param _role The role identifier.
/// @param _grantee The user that gets their role revoked.
function revokeRoleFrom(uint256 _nonce, bytes32 _role, address _grantee) external;

/// @notice Approves operator to grant and revoke any roles on behalf of another user.
/// @param _tokenAddress The token address.
/// @param _operator The user approved to grant and revoke roles.
/// @param _approved The approval status.
function setRoleApprovalForAll(address _tokenAddress, address _operator, bool _approved) external;

/** View Functions **/

/// @notice Returns the custom data of a role assignment.
/// @param _nonce The identifier of the role assignment.
/// @param _role The role identifier.
/// @param _grantee The user that gets their role revoked.
function roleData(uint256 _nonce, bytes32 _role, address _grantee) external view returns (RoleData memory data_);

/// @notice Returns the expiration date of a role assignment.
/// @param _nonce The identifier of the role assignment.
/// @param _role The role identifier.
/// @param _grantee The user that gets their role revoked.
function roleExpirationDate(uint256 _nonce, bytes32 _role, address _grantee) external view returns (uint64 expirationDate_);

/// @notice Checks if the grantor approved the operator for all NFTs.
/// @param _tokenAddress The token address.
/// @param _grantor The user that approved the operator.
/// @param _operator The user that can grant and revoke roles.
function isRoleApprovedForAll(address _tokenAddress, address _grantor, address _operator) external view returns (bool);
}
Loading
Loading