Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
33c4d65
Initial forge tests
drinkcoffee Jan 10, 2025
35ba4ac
Added initial test
drinkcoffee Jan 10, 2025
84bad32
Create initial performance tests
drinkcoffee Jan 13, 2025
5c9f3a9
Mint at 15K per block to max out the blocks
drinkcoffee Jan 13, 2025
80bce5d
Added more tests
drinkcoffee Jan 14, 2025
0f14037
Add more tests
drinkcoffee Jan 14, 2025
bf0837f
Revise import order
drinkcoffee Jan 15, 2025
f70f480
Initial compiling version of PsiV2
drinkcoffee Jan 16, 2025
011ccb4
Resolved issues with PsiV2
drinkcoffee Jan 17, 2025
47c0079
Fixed issue with determing existance of an NFT
drinkcoffee Jan 17, 2025
f72216a
Improve documentation. Prefill by 160K
drinkcoffee Jan 20, 2025
dedbc6b
Migrate ERC721 tests to forge
drinkcoffee Jan 29, 2025
5861530
Improve ERC 721 test coverage
drinkcoffee Jan 29, 2025
e48d49c
Added more tests
drinkcoffee Jan 31, 2025
a74fa1b
Add more tests and remove dead code
drinkcoffee Feb 3, 2025
f5c0f86
Added documentation for ERC721PsiV2
drinkcoffee Feb 4, 2025
3f56c1b
Added more tests
drinkcoffee Feb 5, 2025
540e221
Change solidity version
drinkcoffee Feb 6, 2025
c53427a
Merge branch 'main' into peter-solidity-version
drinkcoffee Feb 6, 2025
bea8ee7
Merge branch 'peter-solidity-version' into peter-erc721-perf
drinkcoffee Feb 6, 2025
ce033e3
Add more tests
drinkcoffee Feb 6, 2025
36c41e7
Added transferFrom tests
drinkcoffee Feb 6, 2025
59b6c1a
Added more tests
drinkcoffee Feb 7, 2025
6912c29
Rename files
drinkcoffee Feb 7, 2025
4045cac
Merge branch 'main' into peter-erc721-perf
drinkcoffee Feb 7, 2025
0f31fa1
Add documentation for ERC 721 contracts
drinkcoffee Feb 7, 2025
2be5039
Fix prettier issues
drinkcoffee Feb 9, 2025
c5f3ec5
Fix solidity version and remove dead code
drinkcoffee Feb 10, 2025
0cc0e1b
Add solhint support for PsiV2
drinkcoffee Feb 10, 2025
e7e359c
Fixed last Slither warning
drinkcoffee Feb 10, 2025
df57fa4
Remove temporary file
drinkcoffee Feb 10, 2025
a6f485c
Remove dead code
drinkcoffee Feb 10, 2025
78bf98b
Improve test coverage
drinkcoffee Feb 10, 2025
af2bca0
Resolve solhint issues
drinkcoffee Feb 10, 2025
45ea3cd
Merge from peter-erc721-perf
drinkcoffee Feb 25, 2025
b7684d7
Remove mention of invariant tests
drinkcoffee Feb 25, 2025
2606a37
Fixed copyright notices
drinkcoffee Feb 27, 2025
34965c8
Added Mermaid diagram source code
drinkcoffee Feb 28, 2025
3bcd1a1
Fixed up incorrect documentation
drinkcoffee Feb 28, 2025
e321e7a
Add audit report
drinkcoffee Mar 3, 2025
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
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ typechain
typechain-types
node.json
.idea/
.vscode/
package-lock.json

# Hardhat files
cache
Expand All @@ -16,6 +18,16 @@ dist/

# Forge files
foundry-out/
broadcast/

# Apple Mac files
.DS_Store

# Fuzz
crytic-export
echidna-corpus
medusa-corpus
med-logs
slither.json
slither.sarif
solc-bin
12 changes: 12 additions & 0 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ To check the test coverage based on Foundry tests use:
forge coverage
```

## Performance Tests

To run tests that check the gas usage:

```
forge test -C perfTest --match-path "./perfTest/**" -vvv --block-gas-limit 1000000000000
```

## Fuzz Tests

For ERC721 tests see: [./test/token/erc721/fuzz/README.md](./test/token/erc721/fuzz/README.md)

## Deploy

To deploy the contract with foundry use the following command:
Expand Down
Binary file not shown.
29 changes: 29 additions & 0 deletions contracts/access/IMintingAccessControl.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright Immutable Pty Ltd 2018 - 2023
// SPDX-License-Identifier: Apache 2.0
pragma solidity >=0.8.19 <0.8.29;

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

interface IMintingAccessControl 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);
}
4 changes: 2 additions & 2 deletions contracts/deployer/create3/OwnableCreate3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ contract OwnableCreate3 is OwnableCreate3Address, IDeploy {
* @param deploySalt A salt to influence the contract address
* @return deployed The address of the deployed contract
*/
// Slither 0.10.4 is mistakenly seeing this as dead code. It is called
// Slither 0.10.4 is mistakenly seeing this as dead code. It is called
// from OwnableCreate3Deployer.deploy and could be called from other contracts.
// slither-disable-next-line dead-code
function _create3(bytes memory bytecode, bytes32 deploySalt) internal returns (address deployed) {
function _create3(bytes memory bytecode, bytes32 deploySalt) internal returns (address deployed) {
deployed = _create3Address(deploySalt);

if (bytecode.length == 0) revert EmptyBytecode();
Expand Down
10 changes: 10 additions & 0 deletions contracts/mocks/MockEIP1271Wallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity >=0.8.19 <0.8.29;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/interfaces/IERC721Receiver.sol";

contract MockEIP1271Wallet is IERC1271 {
address public immutable owner;
Expand All @@ -20,4 +21,13 @@ contract MockEIP1271Wallet is IERC1271 {
return 0;
}
}

function onERC721Received(
address /* operator */,
address /* from */,
uint256 /* tokenId */,
bytes calldata /* data */
) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
}
147 changes: 147 additions & 0 deletions contracts/token/erc721/MermaidDiagramSource.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# ERC 721 Mermaid Diagram Source

This file contains source code to be used with mermaid.live's online diagramming tool.

## ImmutableERC721V2

Source code for `preset/ImmutableERC721V2.sol`.

```
classDiagram
class IERC721 {
<<Interface>>
}

class IERC721Metadata {
<<Interface>>
}

class ERC721 {
getApproved(uint256)
name()
setApprovalForAll(address, bool)
symbol()
tokenURI(uint256)
}

class EIP712 {
eip712Domain()
}

class ERC2981 {
royaltyInfo(uint256, uint256)
}

class ERC721HybridV2 {
approve(address, uint256)
balanceOf(address)
burn(uint256)
burnBatch(uint256[])
exists(uint256)
getApproved(uint256)
isApprovedForAll(address, address)
ownerOf(uint256 tokenId)
safeBurn(address, uint256)
safeTransferFrom(address, address, uint256)
safeTransferFrom(address, address, uint256, bytes)
totalSupply()
transferFrom(address, address, uint256)
}

class ERC721PSIV2 {
mintBatchByQuantityThreshold()
mintBatchByQuantityNextTokenId()
}

class ERC721PSIBurnableV2 {
}

class ERC721HybridPermitV2 {
permit(address, uint256, uint256, uint8, bytes32, bytes32)
nonces(uint256)
DOMAIN_SEPARATOR()
}

class ImmutableERC721HybridBaseV2 {
contractURI()
baseURI()
setApprovalForAll(address, bool)
setBaseURI(string)
setContractURI(string)
setDefaultRoyaltyReceiver(address, uint96)
setNFTRoyaltyReceiver(uint256, address, uint96)
setNFTRoyaltyReceiverBatch(uint256[], address, uint96)
supportsInterface(bytes4)
}

class ImmutableERC721V2 {
mint(address, uint256)
mintBatch(IDMint[])
mintBatchByQuantity(Mint[])
mintByQuantity(address, uint256)
safeBurnBatch(IDBurn[])
safeMint(address, uint256)
safeMintBatch(IDMint[])
safeMintBatchByQuantity(Mint[])
safeMintByQuantity(address, uint256)
safeTransferFromBatch(TransferRequest)
}

class OperatorAllowlistEnforced {
operatorAllowlist()
}

class AccessControl {
getRoleAdmin(bytes32)
grantRole(bytes32, address)
hasRole(bytes32, address)
renounceRole(bytes32, address)
revokeRole(bytes32, address)
DEFAULT_ADMIN_ROLE()
}


class AccessControlEnumerable {
getRoleMember(bytes32, uint256)
getRoleMemberCount(bytes32)
}


class MintingAccessControl {
getAdmins()
grantMinterRole(address)
revokeMinterRole(address)
MINTER_ROLE()
}

IERC721 <|-- ERC721
IERC721Metadata <|-- ERC721

IERC721 <|-- ERC721PSIV2
IERC721Metadata <|-- ERC721PSIV2

ERC721PSIV2 <|-- ERC721PSIBurnableV2

ERC721PSIBurnableV2 <|-- ERC721HybridV2
ERC721 <|-- ERC721HybridV2
IImmutableERC721Structs <|-- ERC721HybridV2
IImmutableERC721Errors <|-- ERC721HybridV2

ERC721HybridV2 <|-- ERC721HybridPermitV2
IERC4494 <|-- ERC721HybridPermitV2
EIP712 <|-- ERC721HybridPermitV2

AccessControl <|-- AccessControlEnumerable

AccessControlEnumerable <|-- MintingAccessControl

OperatorAllowlistEnforcementErrors <|-- OperatorAllowlistEnforced


ERC721HybridPermitV2 <|-- ImmutableERC721HybridBaseV2
MintingAccessControl <|-- ImmutableERC721HybridBaseV2
OperatorAllowlistEnforced <|-- ImmutableERC721HybridBaseV2
ERC2981 <|-- ImmutableERC721HybridBaseV2

ImmutableERC721HybridBaseV2 <|-- ImmutableERC721V2
```
94 changes: 94 additions & 0 deletions contracts/token/erc721/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# ERC 721 Tokens

This directory contains ERC 721 token contracts that game studios could choose to use
directly or extend. The main contracts are shown below. A detailed description of the
all the contracts is contained at the end of this document.

| Contract | Description |
|--------------------------------------- |-----------------------------------------------|
| preset/ImmutableERC721 | ERC721 contract that provides mint by id and mint by quantity. |
| preset/ImmutableERC721V2 | ImmutableERC721 with improved overall performance. |
| preset/ImmutableERC721MintByID | ERC721 that allow mint by id across the entire token range. |

## Security

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).


# Status

Contract threat models and audits:

| Description | Date |Version Audited | Link to Report |
|---------------------------|------------------|-----------------|----------------|
| 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) |
| 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)
| 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)


# Contracts

## Presets

Presets are contracts that game studios could choose to deploy.

### ImmutableERC721 and ImmutableERC721V2

These contracts have the following features:

* Mint by ID for token IDs less than `2^128`.
* Mint by quantity for token IDs greater than `2^128`.
* Permits.

Note: The threshold between mint by ID and mint by quantity can be changed by extending the contracts and
implementing `mintBatchByQuantityThreshold`.

### ImmutableERC721MintByID

The contract has the following features:

* Mint by ID for any token ID
* Permits.

## Interfaces

The original presets, ImmutableERC721 and ImmutableERC721MintByID did not implement interfaces. To reduce
the number of code differences between ImmutableERC721 and ImmutableERC721V2, ImmutableERC721V2 also does not
implement interfaces. However, the preset contracts implement the following interfaces:

* ImmutableERC721: IImmutableERC721ByQuantity.sol
* ImmutableERC721V2: IImmutableERC721ByQuantityV2.sol
* ImmutableERC721MintByID: IImmutableERC721.sol

## Abstract and PSI

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
|- MintingAccessControl
|- ERC721HybridPermitV2
|- ERC721HybridV2
|- ERC721PsiBurnableV2
| |- ERC721PsiV2
|- Open Zeppelin's ERC721

ImmutableERC721MintByID
|- ImmutableERC721Base
|- OperatorAllowlistEnforced
|- MintingAccessControl
|- ERC721Permit
|- Open Zeppelin's ERC721Burnable
```
Loading