Skip to content

Commit aa6c1d4

Browse files
authored
[gms-1627] erc20 improvements (#204)
* [gms-1627] erc20 improvements * comments
1 parent 6d919c8 commit aa6c1d4

File tree

14 files changed

+266
-187
lines changed

14 files changed

+266
-187
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Immutable Contracts is a library of smart contracts targeted at developers who w
99
- [ImmutableERC721](./contracts/token/erc721/preset/ImmutableERC721.sol)
1010
- [ImmutableERC721MintByID](./contracts/token/erc721/preset/ImmutableERC721MintByID.sol)
1111
- [ImmutableERC1155](./contracts/token/erc1155/preset/ImmutableERC1155.sol)
12-
- [ImmutableERC20](./contracts/token/erc20/preset/ImmutableERC20.sol)
12+
- [ImmutableERC20MinterBurnerPermit](./contracts/token/erc20/preset/ImmutableERC20MinterBurnerPermit.sol)
1313
- [ImmutableERC20FixedSupplyNoBurn](./contracts/token/erc20/preset/ImmutableERC20FixedSupplyNoBurn.sol)
1414

1515
- Bridging contracts

audits/token/202404-threat-model-preset-immutable-erc20.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
This document is a thread model for ImmutableERC20 token contracts built by Immutable.
33

44
Contracts covered under this model include:
5-
[ImmutableERC20](../../contracts/token/erc20/preset/ImmutableERC20.sol)
5+
[ImmutableERC20MinterBurnerPermit](../../contracts/token/erc20/preset/ImmutableERC20MinterBurnerPermit.sol)
66

77

88
## Context
@@ -21,13 +21,12 @@ The ERC20 token built by Immutable is meant to be used by game studios to releas
2121

2222
### ImmutableERC20
2323

24-
ImmutableERC20 is a simple wrapper around the [ERC20 implementation from openzeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol). It also extends [ERC20Permit](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Permit.sol) allowing token holders to offload the approval costs to a third party who wish to operate on behalf of the holder.
24+
ImmutableERC20 is a simple wrapper around the [ERC20 implementation from openzeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol). It also extends [ERC20Permit](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Permit.sol), [ERC20Capped](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Capped.sol) and [ERC20Burnable](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Burnable.sol) allowing token holders to offload the approval costs to a third party who wish to operate on behalf of the holder.
2525

2626
The contract defines an immutable `_maxSupply` value to track the maximum number of tokens allowed to be in circulation. This helps the token to derive value using scarcity.
2727

28-
The `renounceOwnership()` method from the `Ownable` extension has been overridden to prevent the contract from being ownerless. This is to help Immutable Hub to link tokens based of their owners.
28+
The `renonceRole()` method from the `AccessControl` extension has been overridden to prevent the contract from being ownerless. This is to help Immutable Hub to link tokens based of their owners.
2929

30-
`Owner` and `DEFAULT_ADMIN_ROLE` will share the same key at depolyment, but the `Ownership` can be transferred.
3130

3231

3332
## Attack Surfaces
@@ -48,7 +47,6 @@ Functions that *change* state:
4847
| ------------------------------------------------------------- | ----------------- | --------------------- |
4948
| mint(address,uint256) | 0x40c10f19 | MINTER_ROLE |
5049
| burn(address,uint256) | 0x9dc29fac | None - permissionless |
51-
| renounceOwnership() | 0x715018a6 | OnlyOwner |
5250
| transfer(address,uint256) | 0xa9059cbb | None - permissionless |
5351
| approve(address,uint256) | 0x095ea7b3 | None - permissionless |
5452
| increaseAllowance(address,uint256) | 0x39509351 | None - permissionless |
@@ -60,7 +58,7 @@ Functions that *change* state:
6058
| renounceRole(bytes32,address) | 0x36568abe | None - permissionless |
6159
| revokeMinterRole(address) | 0x69e2f0fb | DEFAULT_ADMIN_ROLE |
6260
| revokeRole(bytes32,address) | 0xd547741f | DEFAULT_ADMIN_ROLE |
63-
| transferOwnership(address) | 0xf2fde38b | OnlyOwner |
61+
| burnFrom(address,uint256) | 0x79cc6790 | None - permissionless |
6462

6563
Functions that *do not change* state:
6664
| Name | Function Selector | Access Control |
@@ -84,9 +82,10 @@ Functions that *do not change* state:
8482
| supportsInterface(bytes4) | 0x01ffc9a7 | None - permissionless |
8583
| symbol() | 0x95d89b41 | None - permissionless |
8684
| totalSupply() | 0x18160ddd | None - permissionless |
85+
| cap() | 0x355274ea | None - permissionless |
8786

8887
## Tests
8988
`forge test` will run all the related tests for the above mentioned repos. The test plan and cases are written in the test files describing the scenario is it testing for. The test plan can be seen [here](../../test/token/erc20/preset/README.md)
9089

9190
## Diagram
92-
![](./202404-threat-model-preset-immutable-erc20/ImmutableERC20.png)
91+
![](./202404-threat-model-preset-immutable-erc20/ImmutableERC20MinterBurnerPermit.png)
Binary file not shown.

contracts/access/MintingAccessControl.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// SPDX-License-Identifier: Apache 2.0
33
pragma solidity 0.8.19;
44

5-
import {AccessControlEnumerable} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
5+
// solhint-disable no-unused-import
6+
import {AccessControlEnumerable, AccessControl, IAccessControl} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
67

78
abstract contract MintingAccessControl is AccessControlEnumerable {
89
/// @notice Role to mint tokens

contracts/token/erc20/README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
This directory contains ERC 20 token contracts that game studios could choose to use
44
directly or extend.
55

6-
| Contract | Description |
7-
|---------------------------------|-----------------------------------------------|
8-
| ImmutableERC20 | Provides basic ERC 20 capability. Designed to be extended. |
6+
| Contract | Description |
7+
|--------------------------------------- |-----------------------------------------------|
8+
| preset/ImmutableERC20MinterBurnerPermit| Provides basic ERC 20 Permit, Capped token supply and burn capability. |
99
| preset/ImmutableERC20FixedSupplyNoBurn | ERC 20 contract with a fixed supply defined at deployment. |
1010

11+
## ImmutableERC20MinterBurnerPermit
12+
13+
This contract contains Permit methods, allowing the token owner to give a third party operator a Permit which is a signed message that can be used by the third party to give approval to themselves to operate on the tokens owned by the original owner. Users take care when signing messages. If they inadvertantly sign a malicious permit, then the attacker could use use it to gain access to the user's tokens. Read more on the EIP here: [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612).
1114
# Status
1215

1316
Contract threat models and audits:

contracts/errors/ERC20Errors.sol renamed to contracts/token/erc20/preset/Errors.sol

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,4 @@ pragma solidity 0.8.19;
33

44
interface IImmutableERC20Errors {
55
error RenounceOwnershipNotAllowed();
6-
7-
error MaxSupplyExceeded(uint256 maxSupply);
8-
9-
error InvalidMaxSupply();
106
}

contracts/token/erc20/preset/ImmutableERC20.sol

Lines changed: 0 additions & 73 deletions
This file was deleted.

contracts/token/erc20/preset/ImmutableERC20FixedSupplyNoBurn.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pragma solidity 0.8.19;
44

55
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
66
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
7-
import {IImmutableERC20Errors} from "../../../errors/ERC20Errors.sol";
7+
import {IImmutableERC20Errors} from "./Errors.sol";
88

99

1010
/**
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Immutable Pty Ltd 2018 - 2024
2+
// SPDX-License-Identifier: MIT
3+
pragma solidity 0.8.19;
4+
5+
import {ERC20Permit, ERC20} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
6+
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
7+
import {ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
8+
import {MintingAccessControl, AccessControl, IAccessControl} from "../../../access/MintingAccessControl.sol";
9+
import {IImmutableERC20Errors} from "./Errors.sol";
10+
11+
/**
12+
* @notice ERC 20 contract that wraps Open Zeppelin's ERC 20 contract.
13+
* This contract has the concept of a hubOwner, called _hubOwner in the constructor.
14+
* This account has no rights to execute any administrative actions within the contract,
15+
* with the exception of renouncing their ownership.
16+
* The Immutable Hub uses this function to help associate the ERC 20 contract
17+
* with a specific Immutable Hub account.
18+
*/
19+
contract ImmutableERC20MinterBurnerPermit is ERC20Capped, ERC20Burnable, ERC20Permit, MintingAccessControl {
20+
/// @notice Role to mint tokens
21+
bytes32 public constant HUB_OWNER_ROLE = bytes32("HUB_OWNER_ROLE");
22+
23+
/**
24+
* @dev Delegate to Open Zeppelin's contract.
25+
* @param _roleAdmin The account that has the DEFAULT_ADMIN_ROLE.
26+
* @param _minterAdmin The account that has the MINTER_ROLE.
27+
* @param _hubOwner The account that owns the contract and is associated with Immutable Hub.
28+
* @param _name Name of the token.
29+
* @param _symbol Token symbol.
30+
* @param _maxTokenSupply The maximum supply of the token.
31+
32+
*/
33+
constructor(address _roleAdmin, address _minterAdmin, address _hubOwner, string memory _name, string memory _symbol, uint256 _maxTokenSupply) ERC20(_name, _symbol) ERC20Permit(_name) ERC20Capped(_maxTokenSupply) {
34+
_grantRole(DEFAULT_ADMIN_ROLE, _roleAdmin);
35+
_grantRole(HUB_OWNER_ROLE, _hubOwner);
36+
_grantRole(MINTER_ROLE, _minterAdmin);
37+
}
38+
39+
40+
/**
41+
* @dev Mints `amount` number of token and transfers them to the `to` address.
42+
* @param to the address to mint the tokens to.
43+
* @param amount The amount of tokens to mint.
44+
*/
45+
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
46+
_mint(to, amount);
47+
}
48+
49+
50+
/**
51+
* @dev Renounces the role `role` from the calling account. Prevents the last hub owner and admin from
52+
* renouncing their role.
53+
* @param role The role to renounce.
54+
* @param account The account to renounce the role from.
55+
*/
56+
function renounceRole(bytes32 role, address account) public override(AccessControl, IAccessControl) {
57+
if (getRoleMemberCount(role) == 1 && (role == HUB_OWNER_ROLE || role == DEFAULT_ADMIN_ROLE)) {
58+
revert IImmutableERC20Errors.RenounceOwnershipNotAllowed();
59+
}
60+
super.renounceRole(role, account);
61+
}
62+
63+
64+
/**
65+
* @dev Delegate to Open Zeppelin's ERC20Capped contract.
66+
*/
67+
function _mint(address account, uint256 amount) internal override(ERC20, ERC20Capped) {
68+
ERC20Capped._mint(account, amount);
69+
}
70+
71+
}

test/token/erc20/preset/ImmutableERC20.t.sol

Lines changed: 0 additions & 93 deletions
This file was deleted.

test/token/erc20/preset/ImmutableERC20FixedSupplyNoBurn.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pragma solidity 0.8.19;
44
import "forge-std/Test.sol";
55

66
import {ImmutableERC20FixedSupplyNoBurn} from "contracts/token/erc20/preset/ImmutableERC20FixedSupplyNoBurn.sol";
7-
import {IImmutableERC20Errors} from "contracts/errors/ERC20Errors.sol";
7+
import {IImmutableERC20Errors} from "contracts/token/erc20/preset/Errors.sol";
88

99

1010
contract ImmutableERC20FixedSupplyNoBurnTest is Test {

0 commit comments

Comments
 (0)