Skip to content
Open
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
1 change: 1 addition & 0 deletions contracts/multicall/GuardedMulticaller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ contract GuardedMulticaller is AccessControl, ReentrancyGuard, EIP712 {
mapping(bytes32 => bool) private replayProtection;

/// @dev Only those with MULTICALL_SIGNER_ROLE can generate valid signatures for execute function.
// forge-lint: disable-next-line(unsafe-typecast)
bytes32 public constant MULTICALL_SIGNER_ROLE = bytes32("MULTICALL_SIGNER_ROLE");

/// @dev EIP712 typehash for execute function
Expand Down
4 changes: 2 additions & 2 deletions contracts/token/erc1155/abstract/ImmutableERC1155Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {ERC1155Permit, ERC1155} from "./ERC1155Permit.sol";
import {ERC2981} from "openzeppelin-contracts-4/token/common/ERC2981.sol";
import {OperatorAllowlistEnforced} from "../../../allowlist/OperatorAllowlistEnforced.sol";

import {AccessControlEnumerable, MintingAccessControl} from "../../../access/MintingAccessControl.sol";
import {AccessControlEnumerable, MintingAccessControlOz4} from "../../utils/MintingAccessControlOz4.sol";

abstract contract ImmutableERC1155Base is OperatorAllowlistEnforced, ERC1155Permit, ERC2981, MintingAccessControl {
abstract contract ImmutableERC1155Base is OperatorAllowlistEnforced, ERC1155Permit, ERC2981, MintingAccessControlOz4 {
/// @dev Contract level metadata
string public contractURI;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19 <=0.8.27;

import {Ownable} from "openzeppelin-contracts-4/access/Ownable.sol";
import {ERC20} from "openzeppelin-contracts-4/token/ERC20/ERC20.sol";
import {Ownable} from "openzeppelin-contracts-5/access/Ownable.sol";
import {ERC20} from "openzeppelin-contracts-5/token/ERC20/ERC20.sol";
import {IImmutableERC20Errors} from "./IImmutableERC20Errors.sol";

/**
Expand All @@ -26,10 +26,9 @@ contract ImmutableERC20FixedSupplyNoBurn is Ownable, ERC20 {
* @param _hubOwner The account associated with Immutable Hub.
*/
constructor(string memory _name, string memory _symbol, uint256 _totalSupply, address _treasurer, address _hubOwner)
ERC20(_name, _symbol)
ERC20(_name, _symbol) Ownable(_hubOwner)
{
_mint(_treasurer, _totalSupply);
_transferOwnership(_hubOwner);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19 <=0.8.27;

import {ERC20Permit, ERC20} from "openzeppelin-contracts-4/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20Burnable} from "openzeppelin-contracts-4/token/ERC20/extensions/ERC20Burnable.sol";
import {ERC20Capped} from "openzeppelin-contracts-4/token/ERC20/extensions/ERC20Capped.sol";
import {AccessControl, IAccessControl} from "openzeppelin-contracts-4/access/AccessControl.sol";
import {MintingAccessControl} from "../../../access/MintingAccessControl.sol";
import {ERC20Permit, ERC20} from "openzeppelin-contracts-5/token/ERC20/extensions/ERC20Permit.sol";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that these presets are on OZ5: the audit / threat-model status table in contracts/token/erc20/README.md (which references the earlier OZ4 commits b7adf0d7 / aa6c1d4) is stale for this contract. That README is not part of this PR -- consider updating it to record the OZ5 migration, or to note the migrated code is not yet audited against OZ5.

import {ERC20Burnable} from "openzeppelin-contracts-5/token/ERC20/extensions/ERC20Burnable.sol";
import {ERC20Capped} from "openzeppelin-contracts-5/token/ERC20/extensions/ERC20Capped.sol";
import {AccessControl, IAccessControl} from "openzeppelin-contracts-5/access/AccessControl.sol";
import {MintingAccessControl} from "../../utils/MintingAccessControl.sol";
import {IImmutableERC20Errors} from "./IImmutableERC20Errors.sol";

/**
Expand Down Expand Up @@ -66,10 +66,8 @@ contract ImmutableERC20MinterBurnerPermit is ERC20Capped, ERC20Burnable, ERC20Pe
super.renounceRole(role, account);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking doc nit from the OZ5 migration: the NatSpec for this renounceRole override (@param account -- "The account to renounce the role from") is now slightly misleading. In OZ5, AccessControl.renounceRole renames the second parameter to callerConfirmation and enforces callerConfirmation == msg.sender (reverting with AccessControlBadConfirmation). Behaviour is unchanged vs OZ4, but consider renaming the param / updating the doc to make the self-only requirement explicit.

}

/**
* @dev Delegate to Open Zeppelin's ERC20Capped contract.
*/
function _mint(address account, uint256 amount) internal override(ERC20, ERC20Capped) {
ERC20Capped._mint(account, amount);
}
/// @inheritdoc ERC20

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: @inheritdoc ERC20 here, while the body delegates to ERC20Capped._update. This matches OZ's own convention (OZ tags ERC20Capped._update with @inheritdoc ERC20), so it is acceptable, but @inheritdoc ERC20Capped would more precisely describe the delegation target. Non-blocking.

function _update(address from, address to, uint256 value) internal virtual override (ERC20Capped, ERC20) {
ERC20Capped._update(from, to, value);
}
}
10 changes: 0 additions & 10 deletions contracts/token/erc721/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,6 @@ implement interfaces. However, the preset contracts implement the following inte
The contract hierarchy for the preset contracts is shown below. The _Base_ layer combines the ERC 721 capabilities with the operator allow list and access control. The _Permit_ layer adds in the Permit capability. The _Hybrid_ contracts combine mint by ID and mint by quantity capabilities. The _PSI_ contracts provide mint by quantity capability.

```
ImmutableERC721
|- ImmutableERC721HybridBase
|- OperatorAllowlistEnforced
|- MintingAccessControl
|- ERC721HybridPermit
|- ERC721Hybrid
|- ERC721PsiBurnable
| |- ERC721Psi
|- Open Zeppelin's ERC721

ImmutableERC721V2
|- ImmutableERC721HybridBaseV2
|- OperatorAllowlistEnforced
Expand Down
4 changes: 2 additions & 2 deletions contracts/token/erc721/abstract/ImmutableERC721Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {OperatorAllowlistEnforced} from "../../../allowlist/OperatorAllowlistEnf

// Utils
import {BitMaps} from "openzeppelin-contracts-4/utils/structs/BitMaps.sol";
import {AccessControlEnumerable, MintingAccessControl} from "../../../access/MintingAccessControl.sol";
import {AccessControlEnumerable, MintingAccessControlOz4} from "../../utils/MintingAccessControlOz4.sol";

// forge-lint: disable-start(pascal-case-struct)

Expand All @@ -21,7 +21,7 @@ import {AccessControlEnumerable, MintingAccessControl} from "../../../access/Min
own minting functionality to meet the needs of the inheriting contract.
*/

abstract contract ImmutableERC721Base is OperatorAllowlistEnforced, MintingAccessControl, ERC721Permit, ERC2981 {
abstract contract ImmutableERC721Base is OperatorAllowlistEnforced, MintingAccessControlOz4, ERC721Permit, ERC2981 {
using BitMaps for BitMaps.BitMap;
/// ===== State Variables =====

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
pragma solidity >=0.8.19 <=0.8.27;

import {ERC721, IERC721} from "openzeppelin-contracts-4/token/ERC721/ERC721.sol";
import {AccessControlEnumerable, MintingAccessControl} from "../../../access/MintingAccessControl.sol";
import {AccessControlEnumerable, MintingAccessControlOz4} from "../../utils/MintingAccessControlOz4.sol";
import {ERC2981} from "openzeppelin-contracts-4/token/common/ERC2981.sol";
import {OperatorAllowlistEnforced} from "../../../allowlist/OperatorAllowlistEnforced.sol";
import {ERC721HybridPermitV2} from "./ERC721HybridPermitV2.sol";
import {ERC721HybridV2} from "./ERC721HybridV2.sol";

abstract contract ImmutableERC721HybridBaseV2 is
ERC721HybridPermitV2,
MintingAccessControl,
MintingAccessControlOz4,
OperatorAllowlistEnforced,
ERC2981
{
Expand Down
4 changes: 2 additions & 2 deletions contracts/token/erc721/interfaces/IImmutableERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import {IERC721Metadata} from "openzeppelin-contracts-4/interfaces/IERC721Metada
import {IERC2981} from "openzeppelin-contracts-4/interfaces/IERC2981.sol";
import {IERC5267} from "openzeppelin-contracts-4/interfaces/IERC5267.sol";
import {IERC4494} from "../abstract/IERC4494.sol";
import {IMintingAccessControl} from "../../../access/IMintingAccessControl.sol";
import {IMintingAccessControlOz4} from "../../utils/IMintingAccessControlOz4.sol";

import {IImmutableERC721Structs} from "./IImmutableERC721Structs.sol";
import {IImmutableERC721Errors} from "./IImmutableERC721Errors.sol";

interface IImmutableERC721 is
IMintingAccessControl,
IMintingAccessControlOz4,
IERC2981,
IERC721Metadata,
IImmutableERC721Structs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache 2.0
pragma solidity >=0.8.19 <=0.8.27;

import {IAccessControlEnumerable} from "openzeppelin-contracts-4/access/IAccessControlEnumerable.sol";
import {IAccessControlEnumerable} from "openzeppelin-contracts-5/access/extensions/IAccessControlEnumerable.sol";

interface IMintingAccessControl is IAccessControlEnumerable {
/**
Expand Down
29 changes: 29 additions & 0 deletions contracts/token/utils/IMintingAccessControlOz4.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright Immutable Pty Ltd 2018 - 2026
// SPDX-License-Identifier: Apache 2.0
pragma solidity >=0.8.19 <=0.8.27;

import {IAccessControlEnumerable} from "openzeppelin-contracts-4/access/IAccessControlEnumerable.sol";

interface IMintingAccessControlOz4 is IAccessControlEnumerable {
/**
* @notice Role to mint tokens
*/
function MINTER_ROLE() external returns (bytes32);

/**
* @notice Allows admin grant `user` `MINTER` role
* @param user The address to grant the `MINTER` role to
*/
function grantMinterRole(address user) external;

/**
* @notice Allows admin to revoke `MINTER_ROLE` role from `user`
* @param user The address to revoke the `MINTER` role from
*/
function revokeMinterRole(address user) external;

/**
* @notice Returns the addresses which have DEFAULT_ADMIN_ROLE
*/
function getAdmins() external view returns (address[] memory);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache 2.0
pragma solidity >=0.8.19 <=0.8.27;

import {AccessControlEnumerable} from "openzeppelin-contracts-4/access/AccessControlEnumerable.sol";
import {AccessControlEnumerable} from "openzeppelin-contracts-5/access/extensions/AccessControlEnumerable.sol";

abstract contract MintingAccessControl is AccessControlEnumerable {
/// @notice Role to mint tokens
Expand Down
39 changes: 39 additions & 0 deletions contracts/token/utils/MintingAccessControlOz4.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright Immutable Pty Ltd 2018 - 2026
// SPDX-License-Identifier: Apache 2.0
pragma solidity >=0.8.19 <=0.8.27;

import {AccessControlEnumerable} from "openzeppelin-contracts-4/access/AccessControlEnumerable.sol";

abstract contract MintingAccessControlOz4 is AccessControlEnumerable {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new contracts/token/utils/ directory has no README.md. The repo convention (e.g. contracts/multicall/, contracts/token/erc721/), and prior review feedback on past PRs, is to add a directory README describing the contents, threat model, and audit references. Consider adding one for token/utils/.

/// @notice Role to mint tokens
// forge-lint: disable-next-line(unsafe-typecast)
bytes32 public constant MINTER_ROLE = bytes32("MINTER_ROLE");

/**
* @notice Allows admin grant `user` `MINTER` role
* @param user The address to grant the `MINTER` role to
*/
function grantMinterRole(address user) public onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(MINTER_ROLE, user);
}

/**
* @notice Allows admin to revoke `MINTER_ROLE` role from `user`
* @param user The address to revoke the `MINTER` role from
*/
function revokeMinterRole(address user) public onlyRole(DEFAULT_ADMIN_ROLE) {
revokeRole(MINTER_ROLE, user);
}

/**
* @notice Returns the addresses which have DEFAULT_ADMIN_ROLE
*/
function getAdmins() public view returns (address[] memory) {
uint256 adminCount = getRoleMemberCount(DEFAULT_ADMIN_ROLE);
address[] memory admins = new address[](adminCount);
for (uint256 i; i < adminCount; i++) {
admins[i] = getRoleMember(DEFAULT_ADMIN_ROLE, i);
}
return admins;
}
}
6 changes: 3 additions & 3 deletions test/multicall/GuardedMulticaller.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import {Test} from "forge-std/Test.sol";
import {GuardedMulticaller} from "../../contracts/multicall/GuardedMulticaller.sol";
import {MockFunctions} from "./MockFunctions.sol";
import {SigUtils} from "./SigUtils.t.sol";
Expand Down Expand Up @@ -327,13 +327,13 @@ contract GuardedMulticallerTest is Test {
assertFalse(gmc.hasBeenExecuted(invalidRef));
}

function testIsFunctionPermitted() public {
function testIsFunctionPermitted() public view {
assertTrue(gmc.isFunctionPermitted(address(mock), MockFunctions.succeed.selector));
assertTrue(gmc.isFunctionPermitted(address(mock), MockFunctions.revertWithNoReason.selector));
assertFalse(gmc.isFunctionPermitted(address(mock), MockFunctions.notPermitted.selector));
}

function testHashBytesArray() public {
function testHashBytesArray() public view {
bytes[] memory data = new bytes[](2);
data[0] = abi.encodeWithSignature("succeed()");
data[1] = abi.encodeWithSignature("notSucceed()");
Expand Down
19 changes: 11 additions & 8 deletions test/token/erc20/preset/ImmutableERC20MinterBurnerPermit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {Test} from "forge-std/Test.sol";

import {ImmutableERC20MinterBurnerPermit} from "contracts/token/erc20/preset/ImmutableERC20MinterBurnerPermit.sol";
import {IImmutableERC20Errors} from "contracts/token/erc20/preset/IImmutableERC20Errors.sol";
import {ERC20Capped} from "openzeppelin-contracts-5/token/ERC20/extensions/ERC20Capped.sol";
import {IAccessControl} from "openzeppelin-contracts-5/access/IAccessControl.sol";

contract ImmutableERC20MinterBurnerPermitTest is Test {
ImmutableERC20MinterBurnerPermit public erc20;
Expand Down Expand Up @@ -83,10 +85,9 @@ contract ImmutableERC20MinterBurnerPermitTest is Test {
function testOnlyMinterCanMint() public {
address to = makeAddr("to");
uint256 amount = 100;
bytes32 minterRole = erc20.MINTER_ROLE();
vm.prank(hubOwner);
vm.expectRevert(
"AccessControl: account 0xa268ae5516b47694c3f15805a560258dbcdefd08 is missing role 0x4d494e5445525f524f4c45000000000000000000000000000000000000000000"
);
vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, hubOwner, minterRole));
erc20.mint(to, amount);
}

Expand All @@ -110,12 +111,14 @@ contract ImmutableERC20MinterBurnerPermitTest is Test {

function testCanOnlyMintUpToMaxSupply() public {
address to = makeAddr("to");
uint256 amount = 1000;
uint256 amount = maxSupply;
vm.startPrank(minter);
erc20.mint(to, amount);
assertEq(erc20.balanceOf(to), amount);
vm.expectRevert("ERC20Capped: cap exceeded");
erc20.mint(to, 1);
assertEq(erc20.balanceOf(to), amount, "Balance ");
assertEq(erc20.totalSupply(), amount, "Total supply");
uint256 extra = 1;
vm.expectRevert(abi.encodeWithSelector(ERC20Capped.ERC20ExceededCap.selector, (amount + extra), maxSupply));
erc20.mint(to, extra);
vm.stopPrank();
}

Expand All @@ -126,7 +129,7 @@ contract ImmutableERC20MinterBurnerPermitTest is Test {
erc20.mint(tokenReceiver, amount);
assertEq(erc20.balanceOf(tokenReceiver), 100);
vm.prank(tokenReceiver);
erc20.increaseAllowance(operator, amount);
erc20.approve(operator, amount);
vm.prank(operator);
erc20.burnFrom(tokenReceiver, amount);
assertEq(erc20.balanceOf(tokenReceiver), 0);
Expand Down