Skip to content

Commit 8ae7209

Browse files
authored
[gms-959][chore] audit, threat model, comments (#85)
1 parent a9c3748 commit 8ae7209

File tree

7 files changed

+73
-7
lines changed

7 files changed

+73
-7
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Contract Factory Threat Model
2+
3+
## Introduction
4+
This document is a thread model for two preset erc721 token contracts built by Immutable.
5+
6+
This document encompasses information for all contracts under the [token](../contracts/token/) directory as well as the [allowlist](../contracts/allowlist/) directory.
7+
8+
## Context
9+
10+
The ERC721 presets built by immutable were done with the requirements of cheaper onchain minting and flexible project management for games. Namely:
11+
12+
- Studios should be able to mint multiple tokens efficiently to multiple addresses.
13+
14+
- Studios should be able to to mint by token id out of order for metadata association.
15+
16+
- Minting should be restricted to addresses that were granted the `minter` role.
17+
18+
- Only allow operators should be able to modify and assign roles to addresses for administering the collection on chain.
19+
20+
- Contracts should not be upgradeable to prevent external developers from getting around royalty requirements.
21+
22+
23+
## Design and Implementation
24+
25+
### ImmutableERC721
26+
27+
The ImmutableERC721 contract is a hybrid of Openzepplin implementation of [ERC721Burnable](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/extensions/ERC721Burnable.sol) and the [ERC721Psi](https://github.com/estarriolvetch/ERC721Psi/blob/main/contracts/ERC721Psi.sol) implementation. This is to give the studios flexibility on their minting strategies depending on their use cases.
28+
29+
The contract interface allows users to call methods to bulk mint multiple tokens either by ID or by quantity to multiple addresses.
30+
31+
The tokens are split into two different sections by a specific number `2^128`. This number was chosen arbitrarily. Studios have the option to override and replace it with their own threshold. Individual methods of the ERC721 and ERC721Psi implementations have all been overriden to call the accepted `tokenID`'s method implemented in their respective half of the contract. i.e any `tokenID` < `bulkMintThreadhold` will be calling the ERC721 methods and any `tokenID` >= `bulkMintThreadhold` will be calling the ERC721Psi implementations. The exceptions to this are methods involving supply balance which will sum the two halves together, as well as collection metadata information which remains consistent across all of the tokens. This also means the starting tokenId for the ERC721Psi collection is now 2^128 instead of 0.
32+
33+
[EIP4494](https://eips.ethereum.org/EIPS/eip-4494) Permit is added to allow for Gasless transactions from the token owners.
34+
35+
### ImmutableERC721MintByID
36+
37+
The ImmutableERC721MintByID contract is a subset of the ImmutableERC721 contract without the ERC721Psi features but retains features to allow bulk minting with in one transaction.
38+
39+
#### Modifications From Base Implementation
40+
41+
- Added a Bitmap to the Openzepplin ERC721 half to keep track of burns to prevent re-minting of burned tokens
42+
- Added a new burning method to allow the contract to validate if the token being burned belongs to the address that is passed in
43+
- Modified ERC721Psi `_safeMint` and `safeMint` methods to not call the overridden `_mint` methods but to call its own internally defined `_mint`
44+
- Added a `_idMintTotalSupply` to help keep track of how many tokens have been minted and belong to a non-zero address for the `totalSupply()` method.
45+
- Added Modifiers to `transfer` and `approve` related methods to enforce correct operator permissions.
46+
- Added various bulk minting methods to allow the minting of multiple tokens to multiple addresses. These methods come with new structs.
47+
- Added support for EIP4494 Permits. This feature comes with an additional nonce mapping that is needed to help keep track of the validity of permits. We decided to remove support for allowing `approved` contract addresses to validate and use permits as it does not fit any of the uses cases in Immutable's ecosystem, and there is no reliable method of getting all of the approved operators of a token.
48+
49+
50+
## Attack Surfaces
51+
52+
The contract has no access to any funds. The risks will come from compromised keys that are responsible for managing the admin roles that control the collection. As well as permits and approves if an user was tricked into creating a permit that can be validated by a malicious eip1271 wallet giving them permissions to the user's token.
53+
54+
Potential Attacks:
55+
- Compromised Admin Keys:
56+
- The compromised keys are able to assign the `MINTER_ROLE` to malicious parties and allow them to mint tokens to themselves without restriction
57+
- The compromised keys are able to update the `OperatorAllowList` to white list malicious contracts to be approved to operate on tokens within the collection
58+
- Compromised Offchain auth:
59+
- Since EIP4494 combined with EIP1271 relies on off chain signatures that are not standard to the ethereum signature scheme, user auth info can be compromised and be used to create valid EIP1271 signatures.
60+
61+
## Tests
62+
`npx hardhat 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.
63+
64+
## Diagram
65+
![](./immutableERC721.png)

audits/immutableERC721.png

264 KB
Loading

contracts/token/erc721/abstract/ERC721Hybrid.sol

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ abstract contract ERC721Hybrid is ERC721PsiBurnable, ERC721, IImmutableERC721Err
2424
/// @dev A mapping of tokens ids before the threshold that have been burned to prevent re-minting
2525
BitMaps.BitMap private _burnedTokens;
2626

27-
/// @dev A singular batch transfer request
27+
/** @dev A singular batch transfer request. The length of the tos and tokenIds must be matching
28+
* batch transfers will transfer the specified ids to their matching address via index.
29+
**/
2830
struct TransferRequest {
2931
address from;
3032
address[] tos;
@@ -189,7 +191,6 @@ abstract contract ERC721Hybrid is ERC721PsiBurnable, ERC721, IImmutableERC721Err
189191
* if the token id in the param is below the threshold the erc721 method is invoked. Else
190192
* the erc721psi method is invoked. They then behave like their specified ancestors methods.
191193
**/
192-
193194
function _exists(uint256 tokenId) internal view virtual override(ERC721, ERC721PsiBurnable) returns (bool) {
194195
if (tokenId < bulkMintThreshold()) {
195196
return ERC721._ownerOf(tokenId) != address(0) && (!_burnedTokens.get(tokenId));
@@ -295,7 +296,7 @@ abstract contract ERC721Hybrid is ERC721PsiBurnable, ERC721, IImmutableERC721Err
295296
}
296297

297298
/** @dev methods below are overwritten to always invoke the erc721 equivalent due to linearisation
298-
they do not get invoked by any minting methods in this contract and are only overwritten to satisfy
299+
they do not get invoked explicitly by any external minting methods in this contract and are only overwritten to satisfy
299300
the compiler
300301
*/
301302

contracts/token/erc721/abstract/IERC4494.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pragma solidity ^0.8.0;
33
import "@openzeppelin/contracts/interfaces/IERC165.sol";
44

55
///
6-
/// @dev Interface for token permits for ERC-721
6+
/// @dev Interface for token permits for ERC721
77
///
88
interface IERC4494 is IERC165 {
99
/// ERC165 bytes to add to interface array - set in parent contract

contracts/token/erc721/abstract/ImmutableERC721Base.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
pragma solidity ^0.8.0;
33

44
// Token
5-
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
6-
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
75
import "./ERC721Permit.sol";
86

97
// Allowlist

contracts/token/erc721/abstract/ImmutableERC721HybridBase.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
pragma solidity ^0.8.0;
33

44
import { AccessControlEnumerable, MintingAccessControl } from "./MintingAccessControl.sol";
5-
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
65
import { ERC2981 } from "@openzeppelin/contracts/token/common/ERC2981.sol";
76
import { OperatorAllowlistEnforced } from "../../../allowlist/OperatorAllowlistEnforced.sol";
87
import { ERC721HybridPermit } from "./ERC721HybridPermit.sol";
@@ -47,6 +46,7 @@ abstract contract ImmutableERC721HybridBase is OperatorAllowlistEnforced, Mintin
4746
return super.supportsInterface(interfaceId);
4847
}
4948

49+
/// @dev Returns the baseURI
5050
function _baseURI() internal view virtual override returns (string memory) {
5151
return baseURI;
5252
}

contracts/token/erc721/abstract/MintingAccessControl.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ pragma solidity ^0.8.0;
44
import {AccessControlEnumerable} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
55

66
abstract contract MintingAccessControl is AccessControlEnumerable {
7+
8+
/// @dev Role to mint tokens
79
bytes32 public constant MINTER_ROLE = bytes32("MINTER_ROLE");
810

911
/// @dev Returns the addresses which have DEFAULT_ADMIN_ROLE

0 commit comments

Comments
 (0)