-
Notifications
You must be signed in to change notification settings - Fork 40
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
ERC 20 contracts V2 #222
Closed
+597
−180
Closed
ERC 20 contracts V2 #222
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
b21b21b
Work in progress on creating ERC 20 v2
drinkcoffee 67548ef
Merge branch 'main' into peter/erc20d
drinkcoffee a771acd
Improved test coverage
drinkcoffee a2df8d7
Merged from main
drinkcoffee 85de404
Add check for when there are no hub owners
drinkcoffee 2dc1035
Fix prettier issues
drinkcoffee 0650261
Revised test plans
drinkcoffee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// Copyright (c) Immutable Pty Ltd 2018 - 2024 | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.19; | ||
|
||
import {AccessControlEnumerable, AccessControl, IAccessControl} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; | ||
|
||
/** | ||
* @notice Adds the concept of a hub owner. | ||
* @dev This contract has the concept of a hubOwner, called _hubOwner in the constructor. | ||
* This account has no rights to execute any administrative actions within the contract, | ||
* with the exception of renouncing their ownership. | ||
* The Immutable Hub uses this function to help associate the ERC 20 contract | ||
* with a specific Immutable Hub account. | ||
*/ | ||
abstract contract HubOwner is AccessControlEnumerable { | ||
// Report an error if renounceRole is called for the last DEFAULT_ADMIN_ROLE or | ||
// OWNER_ROLE. | ||
error RenounceLastNotAllowed(); | ||
|
||
/// @notice Role to indicate owner for Immutable Hub and other applications. | ||
bytes32 public constant HUB_OWNER_ROLE = bytes32("HUB_OWNER_ROLE"); | ||
|
||
/** | ||
* @param _roleAdmin The account that administers other roles and other | ||
* accounts with DEFAULT_ADMIN_ROLE. | ||
* @param _hubOwner The account associated with Immutable Hub and other applications that need an "owner". | ||
*/ | ||
constructor(address _roleAdmin, address _hubOwner) { | ||
_grantRole(DEFAULT_ADMIN_ROLE, _roleAdmin); | ||
_grantRole(HUB_OWNER_ROLE, _hubOwner); | ||
} | ||
|
||
/** | ||
* @dev Renounces the role `role` from the calling account. Prevents the last hub owner and admin from | ||
* renouncing their role. | ||
* @param role The role to renounce. | ||
* @param account The account to renounce the role from. | ||
*/ | ||
function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { | ||
if ((role == HUB_OWNER_ROLE || role == DEFAULT_ADMIN_ROLE) && (getRoleMemberCount(role) == 1)) { | ||
revert RenounceLastNotAllowed(); | ||
} | ||
super.renounceRole(role, account); | ||
} | ||
|
||
/** | ||
* @notice Returns the addresses which have a certain role. | ||
* @dev In the unlikely event that there are many accounts with a certain role, | ||
* this function might cause out of memory issues, and fail. | ||
* @param _role Role to return array of admins for. | ||
* @return admins The array of admins with the requested role. | ||
*/ | ||
function getAdmins(bytes32 _role) public view returns (address[] memory admins) { | ||
uint256 adminCount = getRoleMemberCount(_role); | ||
admins = new address[](adminCount); | ||
for (uint256 i; i < adminCount; i++) { | ||
admins[i] = getRoleMember(_role, i); | ||
} | ||
return admins; | ||
} | ||
|
||
/** | ||
* @notice Return the first account that has OWNER_ROLE. | ||
* @dev Some applications assume there is only one owner and it is returned by the owner function. | ||
* @return address The "owner" of the contract. | ||
*/ | ||
function owner() external view returns (address) { | ||
bytes32 role = HUB_OWNER_ROLE; | ||
if (getRoleMemberCount(role) == 0) { | ||
// The only way that there could be no owners is if an account with DEFAULT_ADMIN_ROLE | ||
// revoked all of the accounts with OWNER_ROLE. | ||
return address(0); | ||
} | ||
return getRoleMember(role, 0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Copyright Immutable Pty Ltd 2018 - 2023 | ||
// SPDX-License-Identifier: Apache 2.0 | ||
pragma solidity 0.8.19; | ||
|
||
// solhint-disable no-unused-import | ||
import {HubOwner, AccessControlEnumerable, AccessControl, IAccessControl} from "./HubOwner.sol"; | ||
|
||
abstract contract MintingHubOwner is HubOwner { | ||
/// @notice Role to mint tokens | ||
bytes32 public constant MINTER_ROLE = bytes32("MINTER_ROLE"); | ||
|
||
/** | ||
* @param _roleAdmin The account that administers other roles and other | ||
* accounts with DEFAULT_ADMIN_ROLE. | ||
* @param _hubOwner The account associated with Immutable Hub. | ||
* @param _minterAdmin An account with minter role. | ||
*/ | ||
constructor(address _roleAdmin, address _hubOwner, address _minterAdmin) HubOwner(_roleAdmin, _hubOwner) { | ||
_grantRole(MINTER_ROLE, _minterAdmin); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
contracts/token/erc20/preset/ImmutableERC20FixedSupplyNoBurnV2.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Copyright (c) Immutable Pty Ltd 2018 - 2024 | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.19; | ||
|
||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import {HubOwner} from "../../common/HubOwner.sol"; | ||
|
||
/** | ||
* @notice ERC 20 contract that mints a fixed total supply of tokens when the contract | ||
* is deployed. | ||
* @dev This contract has the concept of a hubOwner, called _hubOwner in the constructor. | ||
* This account has no rights to execute any administrative actions within the contract, | ||
* with the exception of renouncing their ownership. | ||
* The Immutable Hub uses this function to help associate the ERC 20 contract | ||
* with a specific Immutable Hub account. | ||
*/ | ||
|
||
contract ImmutableERC20FixedSupplyNoBurnV2 is HubOwner, ERC20 { | ||
/** | ||
* @dev Mints `_totalSupply` number of token and transfers them to `_hubOwner`. | ||
* @param _roleAdmin The account that has the DEFAULT_ADMIN_ROLE. | ||
* @param _treasurer Initial owner of entire supply of all tokens. | ||
* @param _hubOwner The account associated with Immutable Hub. | ||
* @param _name Name of the token. | ||
* @param _symbol Token symbol. | ||
* @param _totalSupply The fixed supply to be minted. | ||
*/ | ||
constructor( | ||
address _roleAdmin, | ||
address _treasurer, | ||
address _hubOwner, | ||
string memory _name, | ||
string memory _symbol, | ||
uint256 _totalSupply | ||
) HubOwner(_roleAdmin, _hubOwner) ERC20(_name, _symbol) { | ||
_mint(_treasurer, _totalSupply); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
contracts/token/erc20/preset/ImmutableERC20MinterBurnerPermitV2.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright (c) Immutable Pty Ltd 2018 - 2024 | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.19; | ||
|
||
import {ERC20Permit, ERC20} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; | ||
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; | ||
import {ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol"; | ||
import {MintingHubOwner} from "../../common/MintingHubOwner.sol"; | ||
|
||
/** | ||
* @notice ERC 20 contract that wraps Open Zeppelin's ERC 20 Permit contract. | ||
* @dev This contract has the concept of a hubOwner, called _hubOwner in the constructor. | ||
* This account has no rights to execute any administrative actions within the contract, | ||
* with the exception of renouncing their ownership. | ||
* The Immutable Hub uses this function to help associate the ERC 20 contract | ||
* with a specific Immutable Hub account. | ||
*/ | ||
contract ImmutableERC20MinterBurnerPermitV2 is MintingHubOwner, ERC20Capped, ERC20Burnable, ERC20Permit { | ||
/** | ||
* @dev Delegate to Open Zeppelin's contract. | ||
* @param _roleAdmin The account that has the DEFAULT_ADMIN_ROLE. | ||
* @param _minterAdmin The account that has the MINTER_ROLE. | ||
* @param _hubOwner The account that owns the contract and is associated with Immutable Hub. | ||
* @param _name Name of the token. | ||
* @param _symbol Token symbol. | ||
* @param _maxTokenSupply The maximum supply of the token. | ||
*/ | ||
constructor( | ||
address _roleAdmin, | ||
address _minterAdmin, | ||
address _hubOwner, | ||
string memory _name, | ||
string memory _symbol, | ||
uint256 _maxTokenSupply | ||
) | ||
MintingHubOwner(_roleAdmin, _hubOwner, _minterAdmin) | ||
ERC20(_name, _symbol) | ||
ERC20Permit(_name) | ||
ERC20Capped(_maxTokenSupply) | ||
{} | ||
|
||
/** | ||
* @dev Mints `amount` number of token and transfers them to the `to` address. | ||
* @param to the address to mint the tokens to. | ||
* @param amount The amount of tokens to mint. | ||
*/ | ||
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { | ||
_mint(to, amount); | ||
} | ||
|
||
/** | ||
* @dev Delegate to Open Zeppelin's ERC20Capped contract. | ||
*/ | ||
function _mint(address account, uint256 amount) internal override(ERC20, ERC20Capped) { | ||
ERC20Capped._mint(account, amount); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.19; | ||
|
||
import "forge-std/Test.sol"; | ||
|
||
import {HubOwner} from "contracts/token/common/HubOwner.sol"; | ||
|
||
|
||
// Contract that isn't abstract, and hence allows the contract to be instantiated | ||
contract HubOwnerImpl is HubOwner { | ||
constructor(address _roleAdmin, address _hubOwner) HubOwner(_roleAdmin, _hubOwner) { | ||
} | ||
} | ||
|
||
contract HubOwnerTest is Test { | ||
HubOwner public tokenContract; | ||
|
||
address public hubOwner; | ||
address public admin; | ||
|
||
function setUp() public virtual { | ||
admin = makeAddr("admin"); | ||
hubOwner = makeAddr("hubOwner"); | ||
|
||
tokenContract = new HubOwnerImpl(admin, hubOwner); | ||
} | ||
|
||
function testInit() public { | ||
assertEq(tokenContract.owner(), hubOwner, "owner"); | ||
assertTrue(tokenContract.hasRole(tokenContract.DEFAULT_ADMIN_ROLE(), admin)); | ||
assertEq(tokenContract.getRoleMemberCount(tokenContract.DEFAULT_ADMIN_ROLE()), 1, "one admin"); | ||
assertTrue(tokenContract.hasRole(tokenContract.HUB_OWNER_ROLE(), hubOwner), "hub owner"); | ||
assertEq(tokenContract.getRoleMemberCount(tokenContract.HUB_OWNER_ROLE()), 1, "one hub owner"); | ||
|
||
address[] memory admins = tokenContract.getAdmins(tokenContract.DEFAULT_ADMIN_ROLE()); | ||
assertEq(admins.length, 1, "admins length"); | ||
assertEq(admins[0], admin, "admins[0]"); | ||
|
||
address[] memory hubOwners = tokenContract.getAdmins(tokenContract.HUB_OWNER_ROLE()); | ||
assertEq(hubOwners.length, 1, "hub owners length"); | ||
assertEq(hubOwners[0], hubOwner, "hub owners[0]"); | ||
} | ||
|
||
function testRenounceAdmin() public { | ||
address secondAdmin = makeAddr("secondAdmin"); | ||
vm.startPrank(admin); | ||
tokenContract.grantRole(tokenContract.DEFAULT_ADMIN_ROLE(), secondAdmin); | ||
assertTrue(tokenContract.hasRole(tokenContract.DEFAULT_ADMIN_ROLE(), secondAdmin)); | ||
|
||
tokenContract.renounceRole(tokenContract.DEFAULT_ADMIN_ROLE(), admin); | ||
assertFalse(tokenContract.hasRole(tokenContract.DEFAULT_ADMIN_ROLE(), admin)); | ||
vm.stopPrank(); | ||
} | ||
|
||
function testRenounceLastAdminBlocked() public { | ||
bytes32 defaultAdminRole = tokenContract.DEFAULT_ADMIN_ROLE(); | ||
vm.prank(admin); | ||
vm.expectRevert(abi.encodeWithSelector(HubOwner.RenounceLastNotAllowed.selector)); | ||
tokenContract.renounceRole(defaultAdminRole, admin); | ||
} | ||
|
||
function testRenounceHubOwner() public { | ||
address secondHubOwner = makeAddr("secondHubOwner"); | ||
vm.startPrank(admin); | ||
tokenContract.grantRole(tokenContract.HUB_OWNER_ROLE(), secondHubOwner); | ||
assertTrue(tokenContract.hasRole(tokenContract.HUB_OWNER_ROLE(), secondHubOwner)); | ||
vm.stopPrank(); | ||
|
||
vm.startPrank(hubOwner); | ||
tokenContract.renounceRole(tokenContract.HUB_OWNER_ROLE(), hubOwner); | ||
assertFalse(tokenContract.hasRole(tokenContract.HUB_OWNER_ROLE(), hubOwner)); | ||
vm.stopPrank(); | ||
} | ||
|
||
function testRenounceLastHubOwnerBlocked() public { | ||
bytes32 hubOwnerRole = tokenContract.HUB_OWNER_ROLE(); | ||
vm.prank(hubOwner); | ||
vm.expectRevert(abi.encodeWithSelector(HubOwner.RenounceLastNotAllowed.selector)); | ||
tokenContract.renounceRole(hubOwnerRole, hubOwner); | ||
} | ||
|
||
// Check what happens when owner() is called when there is no hub owner. | ||
function testOwnerWhenNoHubOwner() public { | ||
bytes32 hubOwnerRole = tokenContract.HUB_OWNER_ROLE(); | ||
vm.prank(admin); | ||
tokenContract.revokeRole(hubOwnerRole, hubOwner); | ||
|
||
// Check the revoke worked. | ||
assertEq(tokenContract.getRoleMemberCount(hubOwnerRole), 0, "no hub owner"); | ||
|
||
// Check getAdmins worked in this situation too. | ||
address[] memory hubOwners = tokenContract.getAdmins(hubOwnerRole); | ||
assertEq(hubOwners.length, 0, "hub owners length"); | ||
|
||
address theOwner = tokenContract.owner(); | ||
assertEq(theOwner, address(0), "owner when there are now owners"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Test Plan for Common Token contracts | ||
|
||
## HubOwner.sol | ||
This section defines tests for contracts/token/common/HubOwner.sol. | ||
All of these tests are in test/token/common/HubOwner.t.sol. | ||
|
||
| Test name |Description | Happy Case | Implemented | | ||
|---------------------------------| --------------------------------------------------|------------|-------------| | ||
| testInit | Check that deployment work. | Yes | Yes | | ||
| testRenounceAdmin | Check that default admins can call renounce. | Yes | Yes | | ||
| testRenounceLastAdminBlocked | Check that the last admin can not call renounce. | No | Yes | | ||
| testRenounceHubOwner | Check that hub owners can call renounce. | Yes | Yes | | ||
| testRenounceLastHubOwnerBlocked | Check that the last hub owner can not call renounce. | No | Yes | | ||
| testOwnerWhenNoHubOwner | Check operation when there are no hub owners. | No | Yes | |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which application/feature will use
MintingHubOwner
role?