Skip to content

Commit d7162ec

Browse files
authored
ERC721 Mint By Quantity V2 (#265)
Changes: Made a copy of ImmutableERC721 contract and dependant contracts and called them V2 contracts. The only major differences are in the ERC721PSIV2 and ERC721PSIBurnable contracts. Added interfaces for ERC721 contracts, so common features for contracts can sit behind an interface. This has allowed for the same tests to be used across implementations. Migrated ERC721 HardHat tests to Forge. Added many additional tests to improve test coverage. Added perfTest directory, that includes test that check the gas efficiency of ERC721 contract implementations. Added fuzz testing
1 parent 3632287 commit d7162ec

File tree

58 files changed

+4955
-1395
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+4955
-1395
lines changed

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ typechain
66
typechain-types
77
node.json
88
.idea/
9+
.vscode/
10+
package-lock.json
911

1012
# Hardhat files
1113
cache
@@ -16,6 +18,16 @@ dist/
1618

1719
# Forge files
1820
foundry-out/
21+
broadcast/
1922

2023
# Apple Mac files
2124
.DS_Store
25+
26+
# Fuzz
27+
crytic-export
28+
echidna-corpus
29+
medusa-corpus
30+
med-logs
31+
slither.json
32+
slither.sarif
33+
solc-bin

BUILD.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ To check the test coverage based on Foundry tests use:
4949
forge coverage
5050
```
5151

52+
## Performance Tests
53+
54+
To run tests that check the gas usage:
55+
56+
```
57+
forge test -C perfTest --match-path "./perfTest/**" -vvv --block-gas-limit 1000000000000
58+
```
59+
60+
## Fuzz Tests
61+
62+
For ERC721 tests see: [./test/token/erc721/fuzz/README.md](./test/token/erc721/fuzz/README.md)
63+
5264
## Deploy
5365

5466
To deploy the contract with foundry use the following command:
Binary file not shown.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright Immutable Pty Ltd 2018 - 2023
2+
// SPDX-License-Identifier: Apache 2.0
3+
pragma solidity >=0.8.19 <0.8.29;
4+
5+
import {IAccessControlEnumerable} from "@openzeppelin/contracts/access/IAccessControlEnumerable.sol";
6+
7+
interface IMintingAccessControl is IAccessControlEnumerable {
8+
/**
9+
* @notice Role to mint tokens
10+
*/
11+
function MINTER_ROLE() external returns (bytes32);
12+
13+
/**
14+
* @notice Allows admin grant `user` `MINTER` role
15+
* @param user The address to grant the `MINTER` role to
16+
*/
17+
function grantMinterRole(address user) external;
18+
19+
/**
20+
* @notice Allows admin to revoke `MINTER_ROLE` role from `user`
21+
* @param user The address to revoke the `MINTER` role from
22+
*/
23+
function revokeMinterRole(address user) external;
24+
25+
/**
26+
* @notice Returns the addresses which have DEFAULT_ADMIN_ROLE
27+
*/
28+
function getAdmins() external view returns (address[] memory);
29+
}

contracts/deployer/create3/OwnableCreate3.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ contract OwnableCreate3 is OwnableCreate3Address, IDeploy {
2727
* @param deploySalt A salt to influence the contract address
2828
* @return deployed The address of the deployed contract
2929
*/
30-
// Slither 0.10.4 is mistakenly seeing this as dead code. It is called
30+
// Slither 0.10.4 is mistakenly seeing this as dead code. It is called
3131
// from OwnableCreate3Deployer.deploy and could be called from other contracts.
3232
// slither-disable-next-line dead-code
33-
function _create3(bytes memory bytecode, bytes32 deploySalt) internal returns (address deployed) {
33+
function _create3(bytes memory bytecode, bytes32 deploySalt) internal returns (address deployed) {
3434
deployed = _create3Address(deploySalt);
3535

3636
if (bytecode.length == 0) revert EmptyBytecode();

contracts/mocks/MockEIP1271Wallet.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity >=0.8.19 <0.8.29;
33

44
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
55
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
6+
import {IERC721Receiver} from "@openzeppelin/contracts/interfaces/IERC721Receiver.sol";
67

78
contract MockEIP1271Wallet is IERC1271 {
89
address public immutable owner;
@@ -20,4 +21,13 @@ contract MockEIP1271Wallet is IERC1271 {
2021
return 0;
2122
}
2223
}
24+
25+
function onERC721Received(
26+
address /* operator */,
27+
address /* from */,
28+
uint256 /* tokenId */,
29+
bytes calldata /* data */
30+
) external pure returns (bytes4) {
31+
return IERC721Receiver.onERC721Received.selector;
32+
}
2333
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# ERC 721 Mermaid Diagram Source
2+
3+
This file contains source code to be used with mermaid.live's online diagramming tool.
4+
5+
## ImmutableERC721V2
6+
7+
Source code for `preset/ImmutableERC721V2.sol`.
8+
9+
```
10+
classDiagram
11+
class IERC721 {
12+
<<Interface>>
13+
}
14+
15+
class IERC721Metadata {
16+
<<Interface>>
17+
}
18+
19+
class ERC721 {
20+
getApproved(uint256)
21+
name()
22+
setApprovalForAll(address, bool)
23+
symbol()
24+
tokenURI(uint256)
25+
}
26+
27+
class EIP712 {
28+
eip712Domain()
29+
}
30+
31+
class ERC2981 {
32+
royaltyInfo(uint256, uint256)
33+
}
34+
35+
class ERC721HybridV2 {
36+
approve(address, uint256)
37+
balanceOf(address)
38+
burn(uint256)
39+
burnBatch(uint256[])
40+
exists(uint256)
41+
getApproved(uint256)
42+
isApprovedForAll(address, address)
43+
ownerOf(uint256 tokenId)
44+
safeBurn(address, uint256)
45+
safeTransferFrom(address, address, uint256)
46+
safeTransferFrom(address, address, uint256, bytes)
47+
totalSupply()
48+
transferFrom(address, address, uint256)
49+
}
50+
51+
class ERC721PSIV2 {
52+
mintBatchByQuantityThreshold()
53+
mintBatchByQuantityNextTokenId()
54+
}
55+
56+
class ERC721PSIBurnableV2 {
57+
}
58+
59+
class ERC721HybridPermitV2 {
60+
permit(address, uint256, uint256, uint8, bytes32, bytes32)
61+
nonces(uint256)
62+
DOMAIN_SEPARATOR()
63+
}
64+
65+
class ImmutableERC721HybridBaseV2 {
66+
contractURI()
67+
baseURI()
68+
setApprovalForAll(address, bool)
69+
setBaseURI(string)
70+
setContractURI(string)
71+
setDefaultRoyaltyReceiver(address, uint96)
72+
setNFTRoyaltyReceiver(uint256, address, uint96)
73+
setNFTRoyaltyReceiverBatch(uint256[], address, uint96)
74+
supportsInterface(bytes4)
75+
}
76+
77+
class ImmutableERC721V2 {
78+
mint(address, uint256)
79+
mintBatch(IDMint[])
80+
mintBatchByQuantity(Mint[])
81+
mintByQuantity(address, uint256)
82+
safeBurnBatch(IDBurn[])
83+
safeMint(address, uint256)
84+
safeMintBatch(IDMint[])
85+
safeMintBatchByQuantity(Mint[])
86+
safeMintByQuantity(address, uint256)
87+
safeTransferFromBatch(TransferRequest)
88+
}
89+
90+
class OperatorAllowlistEnforced {
91+
operatorAllowlist()
92+
}
93+
94+
class AccessControl {
95+
getRoleAdmin(bytes32)
96+
grantRole(bytes32, address)
97+
hasRole(bytes32, address)
98+
renounceRole(bytes32, address)
99+
revokeRole(bytes32, address)
100+
DEFAULT_ADMIN_ROLE()
101+
}
102+
103+
104+
class AccessControlEnumerable {
105+
getRoleMember(bytes32, uint256)
106+
getRoleMemberCount(bytes32)
107+
}
108+
109+
110+
class MintingAccessControl {
111+
getAdmins()
112+
grantMinterRole(address)
113+
revokeMinterRole(address)
114+
MINTER_ROLE()
115+
}
116+
117+
IERC721 <|-- ERC721
118+
IERC721Metadata <|-- ERC721
119+
120+
IERC721 <|-- ERC721PSIV2
121+
IERC721Metadata <|-- ERC721PSIV2
122+
123+
ERC721PSIV2 <|-- ERC721PSIBurnableV2
124+
125+
ERC721PSIBurnableV2 <|-- ERC721HybridV2
126+
ERC721 <|-- ERC721HybridV2
127+
IImmutableERC721Structs <|-- ERC721HybridV2
128+
IImmutableERC721Errors <|-- ERC721HybridV2
129+
130+
ERC721HybridV2 <|-- ERC721HybridPermitV2
131+
IERC4494 <|-- ERC721HybridPermitV2
132+
EIP712 <|-- ERC721HybridPermitV2
133+
134+
AccessControl <|-- AccessControlEnumerable
135+
136+
AccessControlEnumerable <|-- MintingAccessControl
137+
138+
OperatorAllowlistEnforcementErrors <|-- OperatorAllowlistEnforced
139+
140+
141+
ERC721HybridPermitV2 <|-- ImmutableERC721HybridBaseV2
142+
MintingAccessControl <|-- ImmutableERC721HybridBaseV2
143+
OperatorAllowlistEnforced <|-- ImmutableERC721HybridBaseV2
144+
ERC2981 <|-- ImmutableERC721HybridBaseV2
145+
146+
ImmutableERC721HybridBaseV2 <|-- ImmutableERC721V2
147+
```

contracts/token/erc721/README.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# ERC 721 Tokens
2+
3+
This directory contains ERC 721 token contracts that game studios could choose to use
4+
directly or extend. The main contracts are shown below. A detailed description of the
5+
all the contracts is contained at the end of this document.
6+
7+
| Contract | Description |
8+
|--------------------------------------- |-----------------------------------------------|
9+
| preset/ImmutableERC721 | ERC721 contract that provides mint by id and mint by quantity. |
10+
| preset/ImmutableERC721V2 | ImmutableERC721 with improved overall performance. |
11+
| preset/ImmutableERC721MintByID | ERC721 that allow mint by id across the entire token range. |
12+
13+
## Security
14+
15+
These contracts 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).
16+
17+
18+
# Status
19+
20+
Contract threat models and audits:
21+
22+
| Description | Date |Version Audited | Link to Report |
23+
|---------------------------|------------------|-----------------|----------------|
24+
| Threat model | October 2023 |[4ff8003d](https://github.com/immutable/contracts/tree/4ff8003da7f1fd9a6e505646cc519cffe07e4994) | [202309-threat-model-preset-erc721.md](../../../audits/token/202309-threat-model-preset-erc721.md) |
25+
| Internal audit | November 2023, revised February 2024 | [8ae72094](https://github.com/immutable/contracts/tree/8ae72094ab335c6a88ebabde852040e85cb77880) | [202402-internal-audit-preset-erc721.pdf](../../../audits/token/202402-internal-audit-preset-erc721.pdf)
26+
| Internal audit | February 2025 | [2606a379](https://github.com/immutable/contracts/tree/2606a379573b892428254b83660b4bc91ed6e173) | [202502-internal-audit-preset-erc721v2.pdf](../../../audits/token/202502-internal-audit-preset-erc721v2.pdf)
27+
28+
29+
# Contracts
30+
31+
## Presets
32+
33+
Presets are contracts that game studios could choose to deploy.
34+
35+
### ImmutableERC721 and ImmutableERC721V2
36+
37+
These contracts have the following features:
38+
39+
* Mint by ID for token IDs less than `2^128`.
40+
* Mint by quantity for token IDs greater than `2^128`.
41+
* Permits.
42+
43+
Note: The threshold between mint by ID and mint by quantity can be changed by extending the contracts and
44+
implementing `mintBatchByQuantityThreshold`.
45+
46+
### ImmutableERC721MintByID
47+
48+
The contract has the following features:
49+
50+
* Mint by ID for any token ID
51+
* Permits.
52+
53+
## Interfaces
54+
55+
The original presets, ImmutableERC721 and ImmutableERC721MintByID did not implement interfaces. To reduce
56+
the number of code differences between ImmutableERC721 and ImmutableERC721V2, ImmutableERC721V2 also does not
57+
implement interfaces. However, the preset contracts implement the following interfaces:
58+
59+
* ImmutableERC721: IImmutableERC721ByQuantity.sol
60+
* ImmutableERC721V2: IImmutableERC721ByQuantityV2.sol
61+
* ImmutableERC721MintByID: IImmutableERC721.sol
62+
63+
## Abstract and PSI
64+
65+
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.
66+
67+
```
68+
ImmutableERC721
69+
|- ImmutableERC721HybridBase
70+
|- OperatorAllowlistEnforced
71+
|- MintingAccessControl
72+
|- ERC721HybridPermit
73+
|- ERC721Hybrid
74+
|- ERC721PsiBurnable
75+
| |- ERC721Psi
76+
|- Open Zeppelin's ERC721
77+
78+
ImmutableERC721V2
79+
|- ImmutableERC721HybridBaseV2
80+
|- OperatorAllowlistEnforced
81+
|- MintingAccessControl
82+
|- ERC721HybridPermitV2
83+
|- ERC721HybridV2
84+
|- ERC721PsiBurnableV2
85+
| |- ERC721PsiV2
86+
|- Open Zeppelin's ERC721
87+
88+
ImmutableERC721MintByID
89+
|- ImmutableERC721Base
90+
|- OperatorAllowlistEnforced
91+
|- MintingAccessControl
92+
|- ERC721Permit
93+
|- Open Zeppelin's ERC721Burnable
94+
```

0 commit comments

Comments
 (0)