├── .eslintrc
├── .github
└── workflows
│ ├── deploy-npm.yml
│ ├── run-tests.yml
│ └── transpile-merge.yml
├── .gitignore
├── .prettierrc
├── CODEOWNERS
├── LICENSE.txt
├── README.md
├── contracts
├── ERC721AStorage.sol
├── ERC721AUpgradeable.sol
├── ERC721A__Initializable.sol
├── ERC721A__InitializableStorage.sol
├── IERC721AUpgradeable.sol
├── extensions
│ ├── ERC4907AStorage.sol
│ ├── ERC4907AUpgradeable.sol
│ ├── ERC721ABurnableUpgradeable.sol
│ ├── ERC721AQueryableUpgradeable.sol
│ ├── IERC4907AUpgradeable.sol
│ ├── IERC721ABurnableUpgradeable.sol
│ └── IERC721AQueryableUpgradeable.sol
├── interfaces
│ ├── IERC4907AUpgradeable.sol
│ ├── IERC721ABurnableUpgradeable.sol
│ ├── IERC721AQueryableUpgradeable.sol
│ └── IERC721AUpgradeable.sol
└── mocks
│ ├── DirectBurnBitSetterHelperUpgradeable.sol
│ ├── ERC4907AMockUpgradeable.sol
│ ├── ERC721ABurnableMockUpgradeable.sol
│ ├── ERC721ABurnableStartTokenIdMockUpgradeable.sol
│ ├── ERC721AGasReporterMockUpgradeable.sol
│ ├── ERC721AMockUpgradeable.sol
│ ├── ERC721AQueryableMockUpgradeable.sol
│ ├── ERC721AQueryableStartTokenIdMockUpgradeable.sol
│ ├── ERC721ASpotMockUpgradeable.sol
│ ├── ERC721AStartTokenIdMockUpgradeable.sol
│ ├── ERC721ATransferCounterMockUpgradeable.sol
│ ├── ERC721AWithERC2309MockUpgradeable.sol
│ ├── ERC721ReceiverMockStorage.sol
│ ├── ERC721ReceiverMockUpgradeable.sol
│ ├── SequentialUpToHelperUpgradeable.sol
│ ├── StartTokenIdHelperUpgradeable.sol
│ └── WithInit.sol
├── hardhat.config.js
├── package-lock.json
├── package.json
├── scripts
├── replace-imports.js
└── transpile-merge.sh
└── test
├── ERC721A.test.js
├── GasUsage.test.js
├── extensions
├── ERC4907A.test.js
├── ERC721ABurnable.test.js
├── ERC721AQueryable.test.js
├── ERC721ASpot.test.js
└── ERC721ATransferCounter.test.js
└── helpers.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "commonjs": true,
5 | "es2021": true,
6 | "mocha": true
7 | },
8 | "extends": "eslint:recommended",
9 | "parserOptions": {
10 | "ecmaVersion": "latest"
11 | },
12 | "globals": {
13 | "ethers": "readonly"
14 | },
15 | "rules": {
16 | "max-len": [
17 | "error",
18 | {
19 | "code": 120
20 | }
21 | ]
22 | }
23 | }
--------------------------------------------------------------------------------
/.github/workflows/deploy-npm.yml:
--------------------------------------------------------------------------------
1 | name: Publish Package to npmjs
2 | on:
3 | release:
4 | types: [published]
5 | jobs:
6 | build:
7 | environment: production
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v2
12 | # Setup .npmrc file to publish to npm
13 | - uses: actions/setup-node@v2
14 | with:
15 | node-version: '16.x'
16 | registry-url: 'https://registry.npmjs.org'
17 | - run: npm ci
18 | - run: npm publish
19 | env:
20 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
21 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: ERC721A Upgradeable CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | paths-ignore:
7 | - 'docs/**'
8 | - '**.md'
9 | pull_request:
10 | branches: [main]
11 | paths-ignore:
12 | - 'docs/**'
13 | - '**.md'
14 |
15 | jobs:
16 | run-tests:
17 | runs-on: ubuntu-latest
18 |
19 | strategy:
20 | matrix:
21 | node-version: [14.x,16.x,17.x,18.x]
22 |
23 | steps:
24 | - uses: actions/checkout@v2
25 | - name: Use Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v2
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 | - name: Install dependencies
30 | run: npm ci
31 | - name: Run linter
32 | run: npm run lint
33 | - name: Run tests
34 | run: npm test
35 |
--------------------------------------------------------------------------------
/.github/workflows/transpile-merge.yml:
--------------------------------------------------------------------------------
1 | name: Transpile Merge
2 |
3 | on:
4 | push:
5 | branches: [patches]
6 | workflow_dispatch: {}
7 | repository_dispatch:
8 | types: [Update]
9 |
10 | concurrency:
11 | group: transpile-${{ github.ref_name }}
12 | cancel-in-progress: true
13 |
14 | jobs:
15 | transpile:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Use Node.js 16.x
20 | uses: actions/setup-node@v2
21 | with:
22 | node-version: 16.x
23 | - name: Install dependencies
24 | run: npm ci
25 | - name: Transpile Merge
26 | run: ./scripts/transpile-merge.sh
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .env
3 | coverage
4 | coverage.json
5 | typechain
6 |
7 | #Hardhat files
8 | cache
9 | artifacts
10 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 120,
4 | "overrides": [
5 | {
6 | "files": "*.sol",
7 | "options": {
8 | "printWidth": 120,
9 | "explicitTypes": "always"
10 | }
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | .github/workflows/ @chiru-labs
2 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Chiru Labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![Docs][docs-shield]][docs-url]
2 | [![NPM][npm-shield]][npm-url]
3 | [![CI][ci-shield]][ci-url]
4 | [![Issues][issues-shield]][issues-url]
5 | [![MIT License][license-shield]][license-url]
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | > **📢 Version 4.x introduces several breaking changes. [Please refer to the documentation for more details.](https://chiru-labs.github.io/ERC721A/#/migration)**
15 |
16 | _We highly recommend reading the migration guide_, **especially** _the part on [`supportsInterface`](https://chiru-labs.github.io/ERC721A/#/migration?id=supportsinterface) if you are using with OpenZeppelin extensions_ (e.g. ERC2981).
17 |
18 |
19 |
20 | ## About The Project
21 |
22 | This repository hosts the Upgradeable variant of [ERC721A](https://github.com/chiru-labs/ERC721A), meant for use in upgradeable contracts. This variant is available as separate package called `erc721a-upgradeable`.
23 |
24 | This version uses the [diamond storage layout pattern](https://eips.ethereum.org/EIPS/eip-2535).
25 |
26 | It follows all of the rules for [Writing Upgradeable Contracts]: constructors are replaced by initializer functions, state variables are initialized in initializer functions, and we additionally check for storage incompatibilities across minor versions.
27 |
28 | [writing upgradeable contracts]: https://docs.openzeppelin.com/upgrades-plugins/writing-upgradeable
29 |
30 | > **Warning**
31 | >
32 | > There will be storage incompatibilities across major versions of this package, which makes it unsafe to upgrade a deployed contract from one major version to another, for example from 3.4.0 to 4.0.0.
33 | >
34 | > **It is strongly encouraged to use these contracts together with a tool that can simplify the deployment of upgradeable contracts, such as [OpenZeppelin Upgrades Plugins](https://github.com/OpenZeppelin/openzeppelin-upgrades).**
35 |
36 | This repository is generated by a transpiler.
37 |
38 | **Chiru Labs is not liable for any outcomes as a result of using ERC721A and ERC721A-Upgradeable.** DYOR.
39 |
40 |
41 |
42 | ## Docs
43 |
44 | https://chiru-labs.github.io/ERC721A/
45 |
46 |
47 |
48 | ## Installation
49 |
50 | ```sh
51 |
52 | npm install --save-dev erc721a-upgradeable
53 |
54 | ```
55 |
56 |
57 |
58 | ## Usage
59 |
60 | Once installed, you can use the contracts in the library by importing them:
61 |
62 | ```solidity
63 | pragma solidity ^0.8.4;
64 |
65 | import 'erc721a-upgradeable/contracts/ERC721AUpgradeable.sol';
66 | import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
67 |
68 | contract Something is ERC721AUpgradeable, OwnableUpgradeable {
69 | // Take note of the initializer modifiers.
70 | // - `initializerERC721A` for `ERC721AUpgradeable`.
71 | // - `initializer` for OpenZeppelin's `OwnableUpgradeable`.
72 | function initialize() initializerERC721A initializer public {
73 | __ERC721A_init('Something', 'SMTH');
74 | __Ownable_init();
75 | }
76 |
77 | function mint(uint256 quantity) external payable {
78 | // `_mint`'s second argument now takes in a `quantity`, not a `tokenId`.
79 | _mint(msg.sender, quantity);
80 | }
81 |
82 | function adminMint(uint256 quantity) external payable onlyOwner {
83 | _mint(msg.sender, quantity);
84 | }
85 | }
86 |
87 | ```
88 |
89 |
90 |
91 | ## Contributing
92 |
93 | This repository is automatically transpiled from the main [ERC721A](https://github.com/chiru-labs/ERC721A) repository by a workflow.
94 |
95 | Any changes to the `contracts` and `test` directories will be overwritten.
96 |
97 | If you want to make a contribution to the transpiler workflow:
98 |
99 | 1. Fork the Project
100 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
101 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
102 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
103 | 5. Open a Pull Request
104 |
105 | ### Running tests locally
106 |
107 | 1. `npm install`
108 | 2. `npm run test`
109 |
110 |
111 |
112 | ## License
113 |
114 | Distributed under the MIT License. See `LICENSE.txt` for more information.
115 |
116 |
117 |
118 | ## Contact
119 |
120 | - 2pm.flow (owner) - [@2pmflow](https://twitter.com/2pmflow)
121 | - location tba (owner) - [@locationtba](https://twitter.com/locationtba)
122 | - cygaar (maintainer) - [@cygaar_dev](https://twitter.com/cygaar_dev)
123 | - vectorized.eth (maintainer) - [@optimizoor](https://twitter.com/optimizoor)
124 |
125 | Project Link: [https://github.com/chiru-labs/ERC721A-Upgradeable](https://github.com/chiru-labs/ERC721A-Upgradeable)
126 |
127 |
128 |
129 |
130 |
131 | [docs-shield]: https://img.shields.io/badge/docs-%F0%9F%93%84-blue?style=for-the-badge
132 | [docs-url]: https://chiru-labs.github.io/ERC721A/#/upgradeable
133 | [npm-shield]: https://img.shields.io/npm/v/erc721a-upgradeable.svg?style=for-the-badge
134 | [npm-url]: https://www.npmjs.com/package/erc721a-upgradeable
135 | [ci-shield]: https://img.shields.io/github/actions/workflow/status/chiru-labs/ERC721A-Upgradeable/run-tests.yml?label=build&style=for-the-badge&branch=main
136 | [ci-url]: https://github.com/chiru-labs/ERC721A-Upgradeable/actions/workflows/run-tests.yml
137 | [issues-shield]: https://img.shields.io/github/issues/chiru-labs/ERC721A-Upgradeable.svg?style=for-the-badge
138 | [issues-url]: https://github.com/chiru-labs/ERC721A-Upgradeable/issues
139 | [license-shield]: https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge
140 | [license-url]: https://github.com/chiru-labs/ERC721A-Upgradeable/blob/main/LICENSE.txt
141 |
--------------------------------------------------------------------------------
/contracts/ERC721AStorage.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.0;
4 |
5 | library ERC721AStorage {
6 | // Bypass for a `--via-ir` bug (https://github.com/chiru-labs/ERC721A/pull/364).
7 | struct TokenApprovalRef {
8 | address value;
9 | }
10 |
11 | struct Layout {
12 | // =============================================================
13 | // STORAGE
14 | // =============================================================
15 |
16 | // The next token ID to be minted.
17 | uint256 _currentIndex;
18 | // The number of tokens burned.
19 | uint256 _burnCounter;
20 | // Token name
21 | string _name;
22 | // Token symbol
23 | string _symbol;
24 | // Mapping from token ID to ownership details
25 | // An empty struct value does not necessarily mean the token is unowned.
26 | // See {_packedOwnershipOf} implementation for details.
27 | //
28 | // Bits Layout:
29 | // - [0..159] `addr`
30 | // - [160..223] `startTimestamp`
31 | // - [224] `burned`
32 | // - [225] `nextInitialized`
33 | // - [232..255] `extraData`
34 | mapping(uint256 => uint256) _packedOwnerships;
35 | // Mapping owner address to address data.
36 | //
37 | // Bits Layout:
38 | // - [0..63] `balance`
39 | // - [64..127] `numberMinted`
40 | // - [128..191] `numberBurned`
41 | // - [192..255] `aux`
42 | mapping(address => uint256) _packedAddressData;
43 | // Mapping from token ID to approved address.
44 | mapping(uint256 => ERC721AStorage.TokenApprovalRef) _tokenApprovals;
45 | // Mapping from owner to operator approvals
46 | mapping(address => mapping(address => bool)) _operatorApprovals;
47 | // The amount of tokens minted above `_sequentialUpTo()`.
48 | // We call these spot mints (i.e. non-sequential mints).
49 | uint256 _spotMinted;
50 | }
51 |
52 | bytes32 internal constant STORAGE_SLOT = keccak256('ERC721A.contracts.storage.ERC721A');
53 |
54 | function layout() internal pure returns (Layout storage l) {
55 | bytes32 slot = STORAGE_SLOT;
56 | assembly {
57 | l.slot := slot
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/contracts/ERC721A__Initializable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | /**
5 | * @dev This is a base contract to aid in writing upgradeable diamond facet contracts, or any kind of contract that will be deployed
6 | * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
7 | * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
8 | * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
9 | *
10 | * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
11 | * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
12 | *
13 | * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
14 | * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
15 | */
16 |
17 | import {ERC721A__InitializableStorage} from './ERC721A__InitializableStorage.sol';
18 |
19 | abstract contract ERC721A__Initializable {
20 | using ERC721A__InitializableStorage for ERC721A__InitializableStorage.Layout;
21 |
22 | /**
23 | * @dev Modifier to protect an initializer function from being invoked twice.
24 | */
25 | modifier initializerERC721A() {
26 | // If the contract is initializing we ignore whether _initialized is set in order to support multiple
27 | // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
28 | // contract may have been reentered.
29 | require(
30 | ERC721A__InitializableStorage.layout()._initializing
31 | ? _isConstructor()
32 | : !ERC721A__InitializableStorage.layout()._initialized,
33 | 'ERC721A__Initializable: contract is already initialized'
34 | );
35 |
36 | bool isTopLevelCall = !ERC721A__InitializableStorage.layout()._initializing;
37 | if (isTopLevelCall) {
38 | ERC721A__InitializableStorage.layout()._initializing = true;
39 | ERC721A__InitializableStorage.layout()._initialized = true;
40 | }
41 |
42 | _;
43 |
44 | if (isTopLevelCall) {
45 | ERC721A__InitializableStorage.layout()._initializing = false;
46 | }
47 | }
48 |
49 | /**
50 | * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
51 | * {initializer} modifier, directly or indirectly.
52 | */
53 | modifier onlyInitializingERC721A() {
54 | require(
55 | ERC721A__InitializableStorage.layout()._initializing,
56 | 'ERC721A__Initializable: contract is not initializing'
57 | );
58 | _;
59 | }
60 |
61 | /// @dev Returns true if and only if the function is running in the constructor
62 | function _isConstructor() private view returns (bool) {
63 | // extcodesize checks the size of the code stored in an address, and
64 | // address returns the current address. Since the code is still not
65 | // deployed when running a constructor, any checks on its code size will
66 | // yield zero, making it an effective way to detect if a contract is
67 | // under construction or not.
68 | address self = address(this);
69 | uint256 cs;
70 | assembly {
71 | cs := extcodesize(self)
72 | }
73 | return cs == 0;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/contracts/ERC721A__InitializableStorage.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.0;
4 |
5 | /**
6 | * @dev This is a base storage for the initialization function for upgradeable diamond facet contracts
7 | **/
8 |
9 | library ERC721A__InitializableStorage {
10 | struct Layout {
11 | /*
12 | * Indicates that the contract has been initialized.
13 | */
14 | bool _initialized;
15 | /*
16 | * Indicates that the contract is in the process of being initialized.
17 | */
18 | bool _initializing;
19 | }
20 |
21 | bytes32 internal constant STORAGE_SLOT = keccak256('ERC721A.contracts.storage.initializable.facet');
22 |
23 | function layout() internal pure returns (Layout storage l) {
24 | bytes32 slot = STORAGE_SLOT;
25 | assembly {
26 | l.slot := slot
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/contracts/IERC721AUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | /**
8 | * @dev Interface of ERC721A.
9 | */
10 | interface IERC721AUpgradeable {
11 | /**
12 | * The caller must own the token or be an approved operator.
13 | */
14 | error ApprovalCallerNotOwnerNorApproved();
15 |
16 | /**
17 | * The token does not exist.
18 | */
19 | error ApprovalQueryForNonexistentToken();
20 |
21 | /**
22 | * Cannot query the balance for the zero address.
23 | */
24 | error BalanceQueryForZeroAddress();
25 |
26 | /**
27 | * Cannot mint to the zero address.
28 | */
29 | error MintToZeroAddress();
30 |
31 | /**
32 | * The quantity of tokens minted must be more than zero.
33 | */
34 | error MintZeroQuantity();
35 |
36 | /**
37 | * The token does not exist.
38 | */
39 | error OwnerQueryForNonexistentToken();
40 |
41 | /**
42 | * The caller must own the token or be an approved operator.
43 | */
44 | error TransferCallerNotOwnerNorApproved();
45 |
46 | /**
47 | * The token must be owned by `from`.
48 | */
49 | error TransferFromIncorrectOwner();
50 |
51 | /**
52 | * Cannot safely transfer to a contract that does not implement the
53 | * ERC721Receiver interface.
54 | */
55 | error TransferToNonERC721ReceiverImplementer();
56 |
57 | /**
58 | * Cannot transfer to the zero address.
59 | */
60 | error TransferToZeroAddress();
61 |
62 | /**
63 | * The token does not exist.
64 | */
65 | error URIQueryForNonexistentToken();
66 |
67 | /**
68 | * The `quantity` minted with ERC2309 exceeds the safety limit.
69 | */
70 | error MintERC2309QuantityExceedsLimit();
71 |
72 | /**
73 | * The `extraData` cannot be set on an unintialized ownership slot.
74 | */
75 | error OwnershipNotInitializedForExtraData();
76 |
77 | /**
78 | * `_sequentialUpTo()` must be greater than `_startTokenId()`.
79 | */
80 | error SequentialUpToTooSmall();
81 |
82 | /**
83 | * The `tokenId` of a sequential mint exceeds `_sequentialUpTo()`.
84 | */
85 | error SequentialMintExceedsLimit();
86 |
87 | /**
88 | * Spot minting requires a `tokenId` greater than `_sequentialUpTo()`.
89 | */
90 | error SpotMintTokenIdTooSmall();
91 |
92 | /**
93 | * Cannot mint over a token that already exists.
94 | */
95 | error TokenAlreadyExists();
96 |
97 | /**
98 | * The feature is not compatible with spot mints.
99 | */
100 | error NotCompatibleWithSpotMints();
101 |
102 | // =============================================================
103 | // STRUCTS
104 | // =============================================================
105 |
106 | struct TokenOwnership {
107 | // The address of the owner.
108 | address addr;
109 | // Stores the start time of ownership with minimal overhead for tokenomics.
110 | uint64 startTimestamp;
111 | // Whether the token has been burned.
112 | bool burned;
113 | // Arbitrary data similar to `startTimestamp` that can be set via {_extraData}.
114 | uint24 extraData;
115 | }
116 |
117 | // =============================================================
118 | // TOKEN COUNTERS
119 | // =============================================================
120 |
121 | /**
122 | * @dev Returns the total number of tokens in existence.
123 | * Burned tokens will reduce the count.
124 | * To get the total number of tokens minted, please see {_totalMinted}.
125 | */
126 | function totalSupply() external view returns (uint256);
127 |
128 | // =============================================================
129 | // IERC165
130 | // =============================================================
131 |
132 | /**
133 | * @dev Returns true if this contract implements the interface defined by
134 | * `interfaceId`. See the corresponding
135 | * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
136 | * to learn more about how these ids are created.
137 | *
138 | * This function call must use less than 30000 gas.
139 | */
140 | function supportsInterface(bytes4 interfaceId) external view returns (bool);
141 |
142 | // =============================================================
143 | // IERC721
144 | // =============================================================
145 |
146 | /**
147 | * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
148 | */
149 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
150 |
151 | /**
152 | * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
153 | */
154 | event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
155 |
156 | /**
157 | * @dev Emitted when `owner` enables or disables
158 | * (`approved`) `operator` to manage all of its assets.
159 | */
160 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
161 |
162 | /**
163 | * @dev Returns the number of tokens in `owner`'s account.
164 | */
165 | function balanceOf(address owner) external view returns (uint256 balance);
166 |
167 | /**
168 | * @dev Returns the owner of the `tokenId` token.
169 | *
170 | * Requirements:
171 | *
172 | * - `tokenId` must exist.
173 | */
174 | function ownerOf(uint256 tokenId) external view returns (address owner);
175 |
176 | /**
177 | * @dev Safely transfers `tokenId` token from `from` to `to`,
178 | * checking first that contract recipients are aware of the ERC721 protocol
179 | * to prevent tokens from being forever locked.
180 | *
181 | * Requirements:
182 | *
183 | * - `from` cannot be the zero address.
184 | * - `to` cannot be the zero address.
185 | * - `tokenId` token must exist and be owned by `from`.
186 | * - If the caller is not `from`, it must be have been allowed to move
187 | * this token by either {approve} or {setApprovalForAll}.
188 | * - If `to` refers to a smart contract, it must implement
189 | * {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
190 | *
191 | * Emits a {Transfer} event.
192 | */
193 | function safeTransferFrom(
194 | address from,
195 | address to,
196 | uint256 tokenId,
197 | bytes calldata data
198 | ) external payable;
199 |
200 | /**
201 | * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
202 | */
203 | function safeTransferFrom(
204 | address from,
205 | address to,
206 | uint256 tokenId
207 | ) external payable;
208 |
209 | /**
210 | * @dev Transfers `tokenId` from `from` to `to`.
211 | *
212 | * WARNING: Usage of this method is discouraged, use {safeTransferFrom}
213 | * whenever possible.
214 | *
215 | * Requirements:
216 | *
217 | * - `from` cannot be the zero address.
218 | * - `to` cannot be the zero address.
219 | * - `tokenId` token must be owned by `from`.
220 | * - If the caller is not `from`, it must be approved to move this token
221 | * by either {approve} or {setApprovalForAll}.
222 | *
223 | * Emits a {Transfer} event.
224 | */
225 | function transferFrom(
226 | address from,
227 | address to,
228 | uint256 tokenId
229 | ) external payable;
230 |
231 | /**
232 | * @dev Gives permission to `to` to transfer `tokenId` token to another account.
233 | * The approval is cleared when the token is transferred.
234 | *
235 | * Only a single account can be approved at a time, so approving the
236 | * zero address clears previous approvals.
237 | *
238 | * Requirements:
239 | *
240 | * - The caller must own the token or be an approved operator.
241 | * - `tokenId` must exist.
242 | *
243 | * Emits an {Approval} event.
244 | */
245 | function approve(address to, uint256 tokenId) external payable;
246 |
247 | /**
248 | * @dev Approve or remove `operator` as an operator for the caller.
249 | * Operators can call {transferFrom} or {safeTransferFrom}
250 | * for any token owned by the caller.
251 | *
252 | * Requirements:
253 | *
254 | * - The `operator` cannot be the caller.
255 | *
256 | * Emits an {ApprovalForAll} event.
257 | */
258 | function setApprovalForAll(address operator, bool _approved) external;
259 |
260 | /**
261 | * @dev Returns the account approved for `tokenId` token.
262 | *
263 | * Requirements:
264 | *
265 | * - `tokenId` must exist.
266 | */
267 | function getApproved(uint256 tokenId) external view returns (address operator);
268 |
269 | /**
270 | * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
271 | *
272 | * See {setApprovalForAll}.
273 | */
274 | function isApprovedForAll(address owner, address operator) external view returns (bool);
275 |
276 | // =============================================================
277 | // IERC721Metadata
278 | // =============================================================
279 |
280 | /**
281 | * @dev Returns the token collection name.
282 | */
283 | function name() external view returns (string memory);
284 |
285 | /**
286 | * @dev Returns the token collection symbol.
287 | */
288 | function symbol() external view returns (string memory);
289 |
290 | /**
291 | * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
292 | */
293 | function tokenURI(uint256 tokenId) external view returns (string memory);
294 |
295 | // =============================================================
296 | // IERC2309
297 | // =============================================================
298 |
299 | /**
300 | * @dev Emitted when tokens in `fromTokenId` to `toTokenId`
301 | * (inclusive) is transferred from `from` to `to`, as defined in the
302 | * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
303 | *
304 | * See {_mintERC2309} for more details.
305 | */
306 | event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
307 | }
308 |
--------------------------------------------------------------------------------
/contracts/extensions/ERC4907AStorage.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.0;
4 |
5 | import {ERC4907AUpgradeable} from './ERC4907AUpgradeable.sol';
6 |
7 | library ERC4907AStorage {
8 | struct Layout {
9 | // Mapping from token ID to user info.
10 | //
11 | // Bits Layout:
12 | // - [0..159] `user`
13 | // - [160..223] `expires`
14 | mapping(uint256 => uint256) _packedUserInfo;
15 | }
16 |
17 | bytes32 internal constant STORAGE_SLOT = keccak256('ERC721A.contracts.storage.ERC4907A');
18 |
19 | function layout() internal pure returns (Layout storage l) {
20 | bytes32 slot = STORAGE_SLOT;
21 | assembly {
22 | l.slot := slot
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/contracts/extensions/ERC4907AUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import './IERC4907AUpgradeable.sol';
8 | import '../ERC721AUpgradeable.sol';
9 | import {ERC4907AStorage} from './ERC4907AStorage.sol';
10 | import '../ERC721A__Initializable.sol';
11 |
12 | /**
13 | * @title ERC4907A
14 | *
15 | * @dev [ERC4907](https://eips.ethereum.org/EIPS/eip-4907) compliant
16 | * extension of ERC721A, which allows owners and authorized addresses
17 | * to add a time-limited role with restricted permissions to ERC721 tokens.
18 | */
19 | abstract contract ERC4907AUpgradeable is ERC721A__Initializable, ERC721AUpgradeable, IERC4907AUpgradeable {
20 | using ERC4907AStorage for ERC4907AStorage.Layout;
21 |
22 | function __ERC4907A_init() internal onlyInitializingERC721A {
23 | __ERC4907A_init_unchained();
24 | }
25 |
26 | function __ERC4907A_init_unchained() internal onlyInitializingERC721A {}
27 |
28 | // The bit position of `expires` in packed user info.
29 | uint256 private constant _BITPOS_EXPIRES = 160;
30 |
31 | /**
32 | * @dev Sets the `user` and `expires` for `tokenId`.
33 | * The zero address indicates there is no user.
34 | *
35 | * Requirements:
36 | *
37 | * - The caller must own `tokenId` or be an approved operator.
38 | */
39 | function setUser(
40 | uint256 tokenId,
41 | address user,
42 | uint64 expires
43 | ) public virtual override {
44 | // Require the caller to be either the token owner or an approved operator.
45 | address owner = ownerOf(tokenId);
46 | if (_msgSenderERC721A() != owner)
47 | if (!isApprovedForAll(owner, _msgSenderERC721A()))
48 | if (getApproved(tokenId) != _msgSenderERC721A()) _revert(SetUserCallerNotOwnerNorApproved.selector);
49 |
50 | ERC4907AStorage.layout()._packedUserInfo[tokenId] =
51 | (uint256(expires) << _BITPOS_EXPIRES) |
52 | uint256(uint160(user));
53 |
54 | emit UpdateUser(tokenId, user, expires);
55 | }
56 |
57 | /**
58 | * @dev Returns the user address for `tokenId`.
59 | * The zero address indicates that there is no user or if the user is expired.
60 | */
61 | function userOf(uint256 tokenId) public view virtual override returns (address) {
62 | uint256 packed = ERC4907AStorage.layout()._packedUserInfo[tokenId];
63 | assembly {
64 | // Branchless `packed *= (block.timestamp <= expires ? 1 : 0)`.
65 | // If the `block.timestamp == expires`, the `lt` clause will be true
66 | // if there is a non-zero user address in the lower 160 bits of `packed`.
67 | packed := mul(
68 | packed,
69 | // `block.timestamp <= expires ? 1 : 0`.
70 | lt(shl(_BITPOS_EXPIRES, timestamp()), packed)
71 | )
72 | }
73 | return address(uint160(packed));
74 | }
75 |
76 | /**
77 | * @dev Returns the user's expires of `tokenId`.
78 | */
79 | function userExpires(uint256 tokenId) public view virtual override returns (uint256) {
80 | return ERC4907AStorage.layout()._packedUserInfo[tokenId] >> _BITPOS_EXPIRES;
81 | }
82 |
83 | /**
84 | * @dev Override of {IERC165-supportsInterface}.
85 | */
86 | function supportsInterface(bytes4 interfaceId)
87 | public
88 | view
89 | virtual
90 | override(ERC721AUpgradeable, IERC721AUpgradeable)
91 | returns (bool)
92 | {
93 | // The interface ID for ERC4907 is `0xad092b5c`,
94 | // as defined in [ERC4907](https://eips.ethereum.org/EIPS/eip-4907).
95 | return super.supportsInterface(interfaceId) || interfaceId == 0xad092b5c;
96 | }
97 |
98 | /**
99 | * @dev Returns the user address for `tokenId`, ignoring the expiry status.
100 | */
101 | function _explicitUserOf(uint256 tokenId) internal view virtual returns (address) {
102 | return address(uint160(ERC4907AStorage.layout()._packedUserInfo[tokenId]));
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/contracts/extensions/ERC721ABurnableUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import './IERC721ABurnableUpgradeable.sol';
8 | import '../ERC721AUpgradeable.sol';
9 | import '../ERC721A__Initializable.sol';
10 |
11 | /**
12 | * @title ERC721ABurnable.
13 | *
14 | * @dev ERC721A token that can be irreversibly burned (destroyed).
15 | */
16 | abstract contract ERC721ABurnableUpgradeable is
17 | ERC721A__Initializable,
18 | ERC721AUpgradeable,
19 | IERC721ABurnableUpgradeable
20 | {
21 | function __ERC721ABurnable_init() internal onlyInitializingERC721A {
22 | __ERC721ABurnable_init_unchained();
23 | }
24 |
25 | function __ERC721ABurnable_init_unchained() internal onlyInitializingERC721A {}
26 |
27 | /**
28 | * @dev Burns `tokenId`. See {ERC721A-_burn}.
29 | *
30 | * Requirements:
31 | *
32 | * - The caller must own `tokenId` or be an approved operator.
33 | */
34 | function burn(uint256 tokenId) public virtual override {
35 | _burn(tokenId, true);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/contracts/extensions/ERC721AQueryableUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import './IERC721AQueryableUpgradeable.sol';
8 | import '../ERC721AUpgradeable.sol';
9 | import '../ERC721A__Initializable.sol';
10 |
11 | /**
12 | * @title ERC721AQueryable.
13 | *
14 | * @dev ERC721A subclass with convenience query functions.
15 | */
16 | abstract contract ERC721AQueryableUpgradeable is
17 | ERC721A__Initializable,
18 | ERC721AUpgradeable,
19 | IERC721AQueryableUpgradeable
20 | {
21 | function __ERC721AQueryable_init() internal onlyInitializingERC721A {
22 | __ERC721AQueryable_init_unchained();
23 | }
24 |
25 | function __ERC721AQueryable_init_unchained() internal onlyInitializingERC721A {}
26 |
27 | /**
28 | * @dev Returns the `TokenOwnership` struct at `tokenId` without reverting.
29 | *
30 | * If the `tokenId` is out of bounds:
31 | *
32 | * - `addr = address(0)`
33 | * - `startTimestamp = 0`
34 | * - `burned = false`
35 | * - `extraData = 0`
36 | *
37 | * If the `tokenId` is burned:
38 | *
39 | * - `addr =
`
40 | * - `startTimestamp = `
41 | * - `burned = true`
42 | * - `extraData = `
43 | *
44 | * Otherwise:
45 | *
46 | * - `addr = `
47 | * - `startTimestamp = `
48 | * - `burned = false`
49 | * - `extraData = `
50 | */
51 | function explicitOwnershipOf(uint256 tokenId)
52 | public
53 | view
54 | virtual
55 | override
56 | returns (TokenOwnership memory ownership)
57 | {
58 | unchecked {
59 | if (tokenId >= _startTokenId()) {
60 | if (tokenId > _sequentialUpTo()) return _ownershipAt(tokenId);
61 |
62 | if (tokenId < _nextTokenId()) {
63 | // If the `tokenId` is within bounds,
64 | // scan backwards for the initialized ownership slot.
65 | while (!_ownershipIsInitialized(tokenId)) --tokenId;
66 | return _ownershipAt(tokenId);
67 | }
68 | }
69 | }
70 | }
71 |
72 | /**
73 | * @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
74 | * See {ERC721AQueryable-explicitOwnershipOf}
75 | */
76 | function explicitOwnershipsOf(uint256[] calldata tokenIds)
77 | external
78 | view
79 | virtual
80 | override
81 | returns (TokenOwnership[] memory)
82 | {
83 | TokenOwnership[] memory ownerships;
84 | uint256 i = tokenIds.length;
85 | assembly {
86 | // Grab the free memory pointer.
87 | ownerships := mload(0x40)
88 | // Store the length.
89 | mstore(ownerships, i)
90 | // Allocate one word for the length,
91 | // `tokenIds.length` words for the pointers.
92 | i := shl(5, i) // Multiply `i` by 32.
93 | mstore(0x40, add(add(ownerships, 0x20), i))
94 | }
95 | while (i != 0) {
96 | uint256 tokenId;
97 | assembly {
98 | i := sub(i, 0x20)
99 | tokenId := calldataload(add(tokenIds.offset, i))
100 | }
101 | TokenOwnership memory ownership = explicitOwnershipOf(tokenId);
102 | assembly {
103 | // Store the pointer of `ownership` in the `ownerships` array.
104 | mstore(add(add(ownerships, 0x20), i), ownership)
105 | }
106 | }
107 | return ownerships;
108 | }
109 |
110 | /**
111 | * @dev Returns an array of token IDs owned by `owner`,
112 | * in the range [`start`, `stop`)
113 | * (i.e. `start <= tokenId < stop`).
114 | *
115 | * This function allows for tokens to be queried if the collection
116 | * grows too big for a single call of {ERC721AQueryable-tokensOfOwner}.
117 | *
118 | * Requirements:
119 | *
120 | * - `start < stop`
121 | */
122 | function tokensOfOwnerIn(
123 | address owner,
124 | uint256 start,
125 | uint256 stop
126 | ) external view virtual override returns (uint256[] memory) {
127 | return _tokensOfOwnerIn(owner, start, stop);
128 | }
129 |
130 | /**
131 | * @dev Returns an array of token IDs owned by `owner`.
132 | *
133 | * This function scans the ownership mapping and is O(`totalSupply`) in complexity.
134 | * It is meant to be called off-chain.
135 | *
136 | * See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into
137 | * multiple smaller scans if the collection is large enough to cause
138 | * an out-of-gas error (10K collections should be fine).
139 | */
140 | function tokensOfOwner(address owner) external view virtual override returns (uint256[] memory) {
141 | // If spot mints are enabled, full-range scan is disabled.
142 | if (_sequentialUpTo() != type(uint256).max) _revert(NotCompatibleWithSpotMints.selector);
143 | uint256 start = _startTokenId();
144 | uint256 stop = _nextTokenId();
145 | uint256[] memory tokenIds;
146 | if (start != stop) tokenIds = _tokensOfOwnerIn(owner, start, stop);
147 | return tokenIds;
148 | }
149 |
150 | /**
151 | * @dev Helper function for returning an array of token IDs owned by `owner`.
152 | *
153 | * Note that this function is optimized for smaller bytecode size over runtime gas,
154 | * since it is meant to be called off-chain.
155 | */
156 | function _tokensOfOwnerIn(
157 | address owner,
158 | uint256 start,
159 | uint256 stop
160 | ) private view returns (uint256[] memory tokenIds) {
161 | unchecked {
162 | if (start >= stop) _revert(InvalidQueryRange.selector);
163 | // Set `start = max(start, _startTokenId())`.
164 | if (start < _startTokenId()) start = _startTokenId();
165 | uint256 nextTokenId = _nextTokenId();
166 | // If spot mints are enabled, scan all the way until the specified `stop`.
167 | uint256 stopLimit = _sequentialUpTo() != type(uint256).max ? stop : nextTokenId;
168 | // Set `stop = min(stop, stopLimit)`.
169 | if (stop >= stopLimit) stop = stopLimit;
170 | // Number of tokens to scan.
171 | uint256 tokenIdsMaxLength = balanceOf(owner);
172 | // Set `tokenIdsMaxLength` to zero if the range contains no tokens.
173 | if (start >= stop) tokenIdsMaxLength = 0;
174 | // If there are one or more tokens to scan.
175 | if (tokenIdsMaxLength != 0) {
176 | // Set `tokenIdsMaxLength = min(balanceOf(owner), tokenIdsMaxLength)`.
177 | if (stop - start <= tokenIdsMaxLength) tokenIdsMaxLength = stop - start;
178 | uint256 m; // Start of available memory.
179 | assembly {
180 | // Grab the free memory pointer.
181 | tokenIds := mload(0x40)
182 | // Allocate one word for the length, and `tokenIdsMaxLength` words
183 | // for the data. `shl(5, x)` is equivalent to `mul(32, x)`.
184 | m := add(tokenIds, shl(5, add(tokenIdsMaxLength, 1)))
185 | mstore(0x40, m)
186 | }
187 | // We need to call `explicitOwnershipOf(start)`,
188 | // because the slot at `start` may not be initialized.
189 | TokenOwnership memory ownership = explicitOwnershipOf(start);
190 | address currOwnershipAddr;
191 | // If the starting slot exists (i.e. not burned),
192 | // initialize `currOwnershipAddr`.
193 | // `ownership.address` will not be zero,
194 | // as `start` is clamped to the valid token ID range.
195 | if (!ownership.burned) currOwnershipAddr = ownership.addr;
196 | uint256 tokenIdsIdx;
197 | // Use a do-while, which is slightly more efficient for this case,
198 | // as the array will at least contain one element.
199 | do {
200 | if (_sequentialUpTo() != type(uint256).max) {
201 | // Skip the remaining unused sequential slots.
202 | if (start == nextTokenId) start = _sequentialUpTo() + 1;
203 | // Reset `currOwnershipAddr`, as each spot-minted token is a batch of one.
204 | if (start > _sequentialUpTo()) currOwnershipAddr = address(0);
205 | }
206 | ownership = _ownershipAt(start); // This implicitly allocates memory.
207 | assembly {
208 | switch mload(add(ownership, 0x40))
209 | // if `ownership.burned == false`.
210 | case 0 {
211 | // if `ownership.addr != address(0)`.
212 | // The `addr` already has it's upper 96 bits clearned,
213 | // since it is written to memory with regular Solidity.
214 | if mload(ownership) {
215 | currOwnershipAddr := mload(ownership)
216 | }
217 | // if `currOwnershipAddr == owner`.
218 | // The `shl(96, x)` is to make the comparison agnostic to any
219 | // dirty upper 96 bits in `owner`.
220 | if iszero(shl(96, xor(currOwnershipAddr, owner))) {
221 | tokenIdsIdx := add(tokenIdsIdx, 1)
222 | mstore(add(tokenIds, shl(5, tokenIdsIdx)), start)
223 | }
224 | }
225 | // Otherwise, reset `currOwnershipAddr`.
226 | // This handles the case of batch burned tokens
227 | // (burned bit of first slot set, remaining slots left uninitialized).
228 | default {
229 | currOwnershipAddr := 0
230 | }
231 | start := add(start, 1)
232 | // Free temporary memory implicitly allocated for ownership
233 | // to avoid quadratic memory expansion costs.
234 | mstore(0x40, m)
235 | }
236 | } while (!(start == stop || tokenIdsIdx == tokenIdsMaxLength));
237 | // Store the length of the array.
238 | assembly {
239 | mstore(tokenIds, tokenIdsIdx)
240 | }
241 | }
242 | }
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/contracts/extensions/IERC4907AUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../IERC721AUpgradeable.sol';
8 |
9 | /**
10 | * @dev Interface of ERC4907A.
11 | */
12 | interface IERC4907AUpgradeable is IERC721AUpgradeable {
13 | /**
14 | * The caller must own the token or be an approved operator.
15 | */
16 | error SetUserCallerNotOwnerNorApproved();
17 |
18 | /**
19 | * @dev Emitted when the `user` of an NFT or the `expires` of the `user` is changed.
20 | * The zero address for user indicates that there is no user address.
21 | */
22 | event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires);
23 |
24 | /**
25 | * @dev Sets the `user` and `expires` for `tokenId`.
26 | * The zero address indicates there is no user.
27 | *
28 | * Requirements:
29 | *
30 | * - The caller must own `tokenId` or be an approved operator.
31 | */
32 | function setUser(
33 | uint256 tokenId,
34 | address user,
35 | uint64 expires
36 | ) external;
37 |
38 | /**
39 | * @dev Returns the user address for `tokenId`.
40 | * The zero address indicates that there is no user or if the user is expired.
41 | */
42 | function userOf(uint256 tokenId) external view returns (address);
43 |
44 | /**
45 | * @dev Returns the user's expires of `tokenId`.
46 | */
47 | function userExpires(uint256 tokenId) external view returns (uint256);
48 | }
49 |
--------------------------------------------------------------------------------
/contracts/extensions/IERC721ABurnableUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../IERC721AUpgradeable.sol';
8 |
9 | /**
10 | * @dev Interface of ERC721ABurnable.
11 | */
12 | interface IERC721ABurnableUpgradeable is IERC721AUpgradeable {
13 | /**
14 | * @dev Burns `tokenId`. See {ERC721A-_burn}.
15 | *
16 | * Requirements:
17 | *
18 | * - The caller must own `tokenId` or be an approved operator.
19 | */
20 | function burn(uint256 tokenId) external;
21 | }
22 |
--------------------------------------------------------------------------------
/contracts/extensions/IERC721AQueryableUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../IERC721AUpgradeable.sol';
8 |
9 | /**
10 | * @dev Interface of ERC721AQueryable.
11 | */
12 | interface IERC721AQueryableUpgradeable is IERC721AUpgradeable {
13 | /**
14 | * Invalid query range (`start` >= `stop`).
15 | */
16 | error InvalidQueryRange();
17 |
18 | /**
19 | * @dev Returns the `TokenOwnership` struct at `tokenId` without reverting.
20 | *
21 | * If the `tokenId` is out of bounds:
22 | *
23 | * - `addr = address(0)`
24 | * - `startTimestamp = 0`
25 | * - `burned = false`
26 | * - `extraData = 0`
27 | *
28 | * If the `tokenId` is burned:
29 | *
30 | * - `addr = `
31 | * - `startTimestamp = `
32 | * - `burned = true`
33 | * - `extraData = `
34 | *
35 | * Otherwise:
36 | *
37 | * - `addr = `
38 | * - `startTimestamp = `
39 | * - `burned = false`
40 | * - `extraData = `
41 | */
42 | function explicitOwnershipOf(uint256 tokenId) external view returns (TokenOwnership memory);
43 |
44 | /**
45 | * @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
46 | * See {ERC721AQueryable-explicitOwnershipOf}
47 | */
48 | function explicitOwnershipsOf(uint256[] memory tokenIds) external view returns (TokenOwnership[] memory);
49 |
50 | /**
51 | * @dev Returns an array of token IDs owned by `owner`,
52 | * in the range [`start`, `stop`)
53 | * (i.e. `start <= tokenId < stop`).
54 | *
55 | * This function allows for tokens to be queried if the collection
56 | * grows too big for a single call of {ERC721AQueryable-tokensOfOwner}.
57 | *
58 | * Requirements:
59 | *
60 | * - `start < stop`
61 | */
62 | function tokensOfOwnerIn(
63 | address owner,
64 | uint256 start,
65 | uint256 stop
66 | ) external view returns (uint256[] memory);
67 |
68 | /**
69 | * @dev Returns an array of token IDs owned by `owner`.
70 | *
71 | * This function scans the ownership mapping and is O(`totalSupply`) in complexity.
72 | * It is meant to be called off-chain.
73 | *
74 | * See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into
75 | * multiple smaller scans if the collection is large enough to cause
76 | * an out-of-gas error (10K collections should be fine).
77 | */
78 | function tokensOfOwner(address owner) external view returns (uint256[] memory);
79 | }
80 |
--------------------------------------------------------------------------------
/contracts/interfaces/IERC4907AUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../extensions/IERC4907AUpgradeable.sol';
8 |
--------------------------------------------------------------------------------
/contracts/interfaces/IERC721ABurnableUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../extensions/IERC721ABurnableUpgradeable.sol';
8 |
--------------------------------------------------------------------------------
/contracts/interfaces/IERC721AQueryableUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../extensions/IERC721AQueryableUpgradeable.sol';
8 |
--------------------------------------------------------------------------------
/contracts/interfaces/IERC721AUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creator: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../IERC721AUpgradeable.sol';
8 |
--------------------------------------------------------------------------------
/contracts/mocks/DirectBurnBitSetterHelperUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 | import '../ERC721A__Initializable.sol';
7 |
8 | contract DirectBurnBitSetterHelperUpgradeable is ERC721A__Initializable {
9 | function __DirectBurnBitSetterHelper_init() internal onlyInitializingERC721A {
10 | __DirectBurnBitSetterHelper_init_unchained();
11 | }
12 |
13 | function __DirectBurnBitSetterHelper_init_unchained() internal onlyInitializingERC721A {}
14 |
15 | function directSetBurnBit(uint256 index) public virtual {
16 | bytes32 erc721aDiamondStorageSlot = keccak256('ERC721A.contracts.storage.ERC721A');
17 |
18 | // This is `_BITMASK_BURNED` from ERC721A.
19 | uint256 bitmaskBurned = 1 << 224;
20 | // We use assembly to directly access the private mapping.
21 | assembly {
22 | // The `_packedOwnerships` mapping is at slot 4.
23 | mstore(0x20, 4)
24 | mstore(0x00, index)
25 | let ownershipStorageSlot := keccak256(0x00, 0x40)
26 | sstore(ownershipStorageSlot, or(sload(ownershipStorageSlot), bitmaskBurned))
27 |
28 | // For diamond storage, we'll simply add the offset of the layout struct.
29 | mstore(0x20, add(erc721aDiamondStorageSlot, 4))
30 | mstore(0x00, index)
31 | ownershipStorageSlot := keccak256(0x00, 0x40)
32 | sstore(ownershipStorageSlot, or(sload(ownershipStorageSlot), bitmaskBurned))
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC4907AMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../extensions/ERC4907AUpgradeable.sol';
8 | import '../ERC721A__Initializable.sol';
9 |
10 | contract ERC4907AMockUpgradeable is ERC721A__Initializable, ERC721AUpgradeable, ERC4907AUpgradeable {
11 | function __ERC4907AMock_init(string memory name_, string memory symbol_) internal onlyInitializingERC721A {
12 | __ERC721A_init_unchained(name_, symbol_);
13 | __ERC4907A_init_unchained();
14 | __ERC4907AMock_init_unchained(name_, symbol_);
15 | }
16 |
17 | function __ERC4907AMock_init_unchained(string memory, string memory) internal onlyInitializingERC721A {}
18 |
19 | function mint(address to, uint256 quantity) public {
20 | _mint(to, quantity);
21 | }
22 |
23 | function supportsInterface(bytes4 interfaceId)
24 | public
25 | view
26 | virtual
27 | override(ERC721AUpgradeable, ERC4907AUpgradeable)
28 | returns (bool)
29 | {
30 | return super.supportsInterface(interfaceId);
31 | }
32 |
33 | function explicitUserOf(uint256 tokenId) public view returns (address) {
34 | return _explicitUserOf(tokenId);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721ABurnableMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../extensions/ERC721ABurnableUpgradeable.sol';
8 | import '../ERC721A__Initializable.sol';
9 |
10 | contract ERC721ABurnableMockUpgradeable is ERC721A__Initializable, ERC721AUpgradeable, ERC721ABurnableUpgradeable {
11 | function __ERC721ABurnableMock_init(string memory name_, string memory symbol_) internal onlyInitializingERC721A {
12 | __ERC721A_init_unchained(name_, symbol_);
13 | __ERC721ABurnable_init_unchained();
14 | __ERC721ABurnableMock_init_unchained(name_, symbol_);
15 | }
16 |
17 | function __ERC721ABurnableMock_init_unchained(string memory, string memory) internal onlyInitializingERC721A {}
18 |
19 | function exists(uint256 tokenId) public view returns (bool) {
20 | return _exists(tokenId);
21 | }
22 |
23 | function safeMint(address to, uint256 quantity) public {
24 | _safeMint(to, quantity);
25 | }
26 |
27 | function getOwnershipAt(uint256 index) public view returns (TokenOwnership memory) {
28 | return _ownershipAt(index);
29 | }
30 |
31 | function totalMinted() public view returns (uint256) {
32 | return _totalMinted();
33 | }
34 |
35 | function totalBurned() public view returns (uint256) {
36 | return _totalBurned();
37 | }
38 |
39 | function numberBurned(address owner) public view returns (uint256) {
40 | return _numberBurned(owner);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721ABurnableStartTokenIdMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import './ERC721ABurnableMockUpgradeable.sol';
8 | import './StartTokenIdHelperUpgradeable.sol';
9 | import '../ERC721A__Initializable.sol';
10 |
11 | contract ERC721ABurnableStartTokenIdMockUpgradeable is
12 | ERC721A__Initializable,
13 | StartTokenIdHelperUpgradeable,
14 | ERC721ABurnableMockUpgradeable
15 | {
16 | function __ERC721ABurnableStartTokenIdMock_init(
17 | string memory name_,
18 | string memory symbol_,
19 | uint256 startTokenId_
20 | ) internal onlyInitializingERC721A {
21 | __StartTokenIdHelper_init_unchained(startTokenId_);
22 | __ERC721A_init_unchained(name_, symbol_);
23 | __ERC721ABurnable_init_unchained();
24 | __ERC721ABurnableMock_init_unchained(name_, symbol_);
25 | __ERC721ABurnableStartTokenIdMock_init_unchained(name_, symbol_, startTokenId_);
26 | }
27 |
28 | function __ERC721ABurnableStartTokenIdMock_init_unchained(
29 | string memory,
30 | string memory,
31 | uint256
32 | ) internal onlyInitializingERC721A {}
33 |
34 | function _startTokenId() internal view override returns (uint256) {
35 | return startTokenId();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721AGasReporterMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../ERC721AUpgradeable.sol';
8 | import '../ERC721A__Initializable.sol';
9 |
10 | contract ERC721AGasReporterMockUpgradeable is ERC721A__Initializable, ERC721AUpgradeable {
11 | function __ERC721AGasReporterMock_init(string memory name_, string memory symbol_)
12 | internal
13 | onlyInitializingERC721A
14 | {
15 | __ERC721A_init_unchained(name_, symbol_);
16 | __ERC721AGasReporterMock_init_unchained(name_, symbol_);
17 | }
18 |
19 | function __ERC721AGasReporterMock_init_unchained(string memory, string memory) internal onlyInitializingERC721A {}
20 |
21 | function safeMintOne(address to) public {
22 | _safeMint(to, 1);
23 | }
24 |
25 | function mintOne(address to) public {
26 | _mint(to, 1);
27 | }
28 |
29 | function safeMintTen(address to) public {
30 | _safeMint(to, 10);
31 | }
32 |
33 | function mintTen(address to) public {
34 | _mint(to, 10);
35 | }
36 |
37 | function transferTenAsc(address to) public {
38 | unchecked {
39 | transferFrom(msg.sender, to, 0);
40 | transferFrom(msg.sender, to, 1);
41 | transferFrom(msg.sender, to, 2);
42 | transferFrom(msg.sender, to, 3);
43 | transferFrom(msg.sender, to, 4);
44 | transferFrom(msg.sender, to, 5);
45 | transferFrom(msg.sender, to, 6);
46 | transferFrom(msg.sender, to, 7);
47 | transferFrom(msg.sender, to, 8);
48 | transferFrom(msg.sender, to, 9);
49 | }
50 | }
51 |
52 | function transferTenDesc(address to) public {
53 | unchecked {
54 | transferFrom(msg.sender, to, 9);
55 | transferFrom(msg.sender, to, 8);
56 | transferFrom(msg.sender, to, 7);
57 | transferFrom(msg.sender, to, 6);
58 | transferFrom(msg.sender, to, 5);
59 | transferFrom(msg.sender, to, 4);
60 | transferFrom(msg.sender, to, 3);
61 | transferFrom(msg.sender, to, 2);
62 | transferFrom(msg.sender, to, 1);
63 | transferFrom(msg.sender, to, 0);
64 | }
65 | }
66 |
67 | function transferTenAvg(address to) public {
68 | unchecked {
69 | transferFrom(msg.sender, to, 4);
70 | transferFrom(msg.sender, to, 5);
71 | transferFrom(msg.sender, to, 3);
72 | transferFrom(msg.sender, to, 6);
73 | transferFrom(msg.sender, to, 2);
74 | transferFrom(msg.sender, to, 7);
75 | transferFrom(msg.sender, to, 1);
76 | transferFrom(msg.sender, to, 8);
77 | transferFrom(msg.sender, to, 0);
78 | transferFrom(msg.sender, to, 9);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721AMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../ERC721AUpgradeable.sol';
8 | import './DirectBurnBitSetterHelperUpgradeable.sol';
9 | import '../ERC721A__Initializable.sol';
10 |
11 | contract ERC721AMockUpgradeable is ERC721A__Initializable, ERC721AUpgradeable, DirectBurnBitSetterHelperUpgradeable {
12 | function __ERC721AMock_init(string memory name_, string memory symbol_) internal onlyInitializingERC721A {
13 | __ERC721A_init_unchained(name_, symbol_);
14 | __DirectBurnBitSetterHelper_init_unchained();
15 | __ERC721AMock_init_unchained(name_, symbol_);
16 | }
17 |
18 | function __ERC721AMock_init_unchained(string memory, string memory) internal onlyInitializingERC721A {}
19 |
20 | function numberMinted(address owner) public view returns (uint256) {
21 | return _numberMinted(owner);
22 | }
23 |
24 | function totalMinted() public view returns (uint256) {
25 | return _totalMinted();
26 | }
27 |
28 | function totalBurned() public view returns (uint256) {
29 | return _totalBurned();
30 | }
31 |
32 | function nextTokenId() public view returns (uint256) {
33 | return _nextTokenId();
34 | }
35 |
36 | function getAux(address owner) public view returns (uint64) {
37 | return _getAux(owner);
38 | }
39 |
40 | function setAux(address owner, uint64 aux) public {
41 | _setAux(owner, aux);
42 | }
43 |
44 | function directApprove(address to, uint256 tokenId) public {
45 | _approve(to, tokenId);
46 | }
47 |
48 | function baseURI() public view returns (string memory) {
49 | return _baseURI();
50 | }
51 |
52 | function exists(uint256 tokenId) public view returns (bool) {
53 | return _exists(tokenId);
54 | }
55 |
56 | function safeMint(address to, uint256 quantity) public {
57 | _safeMint(to, quantity);
58 | }
59 |
60 | function safeMint(
61 | address to,
62 | uint256 quantity,
63 | bytes memory _data
64 | ) public {
65 | _safeMint(to, quantity, _data);
66 | }
67 |
68 | function mint(address to, uint256 quantity) public {
69 | _mint(to, quantity);
70 | }
71 |
72 | function burn(uint256 tokenId) public {
73 | _burn(tokenId);
74 | }
75 |
76 | function burn(uint256 tokenId, bool approvalCheck) public {
77 | _burn(tokenId, approvalCheck);
78 | }
79 |
80 | function toString(uint256 x) public pure returns (string memory) {
81 | return _toString(x);
82 | }
83 |
84 | function getOwnershipAt(uint256 index) public view returns (TokenOwnership memory) {
85 | return _ownershipAt(index);
86 | }
87 |
88 | function getOwnershipOf(uint256 index) public view returns (TokenOwnership memory) {
89 | return _ownershipOf(index);
90 | }
91 |
92 | function initializeOwnershipAt(uint256 index) public {
93 | _initializeOwnershipAt(index);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721AQueryableMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../extensions/ERC721AQueryableUpgradeable.sol';
8 | import '../extensions/ERC721ABurnableUpgradeable.sol';
9 | import './DirectBurnBitSetterHelperUpgradeable.sol';
10 | import '../ERC721A__Initializable.sol';
11 |
12 | contract ERC721AQueryableMockUpgradeable is
13 | ERC721A__Initializable,
14 | ERC721AQueryableUpgradeable,
15 | ERC721ABurnableUpgradeable,
16 | DirectBurnBitSetterHelperUpgradeable
17 | {
18 | function __ERC721AQueryableMock_init(string memory name_, string memory symbol_) internal onlyInitializingERC721A {
19 | __ERC721A_init_unchained(name_, symbol_);
20 | __ERC721AQueryable_init_unchained();
21 | __ERC721ABurnable_init_unchained();
22 | __DirectBurnBitSetterHelper_init_unchained();
23 | __ERC721AQueryableMock_init_unchained(name_, symbol_);
24 | }
25 |
26 | function __ERC721AQueryableMock_init_unchained(string memory, string memory) internal onlyInitializingERC721A {}
27 |
28 | function safeMint(address to, uint256 quantity) public {
29 | _safeMint(to, quantity);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721AQueryableStartTokenIdMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import './ERC721AQueryableMockUpgradeable.sol';
8 | import './StartTokenIdHelperUpgradeable.sol';
9 | import '../ERC721A__Initializable.sol';
10 |
11 | contract ERC721AQueryableStartTokenIdMockUpgradeable is
12 | ERC721A__Initializable,
13 | StartTokenIdHelperUpgradeable,
14 | ERC721AQueryableMockUpgradeable
15 | {
16 | function __ERC721AQueryableStartTokenIdMock_init(
17 | string memory name_,
18 | string memory symbol_,
19 | uint256 startTokenId_
20 | ) internal onlyInitializingERC721A {
21 | __StartTokenIdHelper_init_unchained(startTokenId_);
22 | __ERC721A_init_unchained(name_, symbol_);
23 | __ERC721AQueryable_init_unchained();
24 | __ERC721ABurnable_init_unchained();
25 | __DirectBurnBitSetterHelper_init_unchained();
26 | __ERC721AQueryableMock_init_unchained(name_, symbol_);
27 | __ERC721AQueryableStartTokenIdMock_init_unchained(name_, symbol_, startTokenId_);
28 | }
29 |
30 | function __ERC721AQueryableStartTokenIdMock_init_unchained(
31 | string memory,
32 | string memory,
33 | uint256
34 | ) internal onlyInitializingERC721A {}
35 |
36 | function _startTokenId() internal view override returns (uint256) {
37 | return startTokenId();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721ASpotMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import './ERC721AQueryableMockUpgradeable.sol';
8 | import './StartTokenIdHelperUpgradeable.sol';
9 | import './SequentialUpToHelperUpgradeable.sol';
10 | import '../ERC721A__Initializable.sol';
11 |
12 | contract ERC721ASpotMockUpgradeable is
13 | ERC721A__Initializable,
14 | StartTokenIdHelperUpgradeable,
15 | SequentialUpToHelperUpgradeable,
16 | ERC721AQueryableMockUpgradeable
17 | {
18 | function __ERC721ASpotMock_init(
19 | string memory name_,
20 | string memory symbol_,
21 | uint256 startTokenId_,
22 | uint256 sequentialUpTo_,
23 | uint256 quantity,
24 | bool mintInConstructor
25 | ) internal onlyInitializingERC721A {
26 | __StartTokenIdHelper_init_unchained(startTokenId_);
27 | __SequentialUpToHelper_init_unchained(sequentialUpTo_);
28 | __ERC721A_init_unchained(name_, symbol_);
29 | __ERC721AQueryable_init_unchained();
30 | __ERC721ABurnable_init_unchained();
31 | __DirectBurnBitSetterHelper_init_unchained();
32 | __ERC721AQueryableMock_init_unchained(name_, symbol_);
33 | __ERC721ASpotMock_init_unchained(name_, symbol_, startTokenId_, sequentialUpTo_, quantity, mintInConstructor);
34 | }
35 |
36 | function __ERC721ASpotMock_init_unchained(
37 | string memory,
38 | string memory,
39 | uint256,
40 | uint256,
41 | uint256 quantity,
42 | bool mintInConstructor
43 | ) internal onlyInitializingERC721A {
44 | if (mintInConstructor) {
45 | _mintERC2309(msg.sender, quantity);
46 | }
47 | }
48 |
49 | function _startTokenId() internal view override returns (uint256) {
50 | return startTokenId();
51 | }
52 |
53 | function _sequentialUpTo() internal view override returns (uint256) {
54 | return sequentialUpTo();
55 | }
56 |
57 | function exists(uint256 tokenId) public view returns (bool) {
58 | return _exists(tokenId);
59 | }
60 |
61 | function getOwnershipOf(uint256 index) public view returns (TokenOwnership memory) {
62 | return _ownershipOf(index);
63 | }
64 |
65 | function safeMintSpot(address to, uint256 tokenId) public {
66 | _safeMintSpot(to, tokenId);
67 | }
68 |
69 | function totalSpotMinted() public view returns (uint256) {
70 | return _totalSpotMinted();
71 | }
72 |
73 | function totalMinted() public view returns (uint256) {
74 | return _totalMinted();
75 | }
76 |
77 | function totalBurned() public view returns (uint256) {
78 | return _totalBurned();
79 | }
80 |
81 | function numberBurned(address owner) public view returns (uint256) {
82 | return _numberBurned(owner);
83 | }
84 |
85 | function setExtraDataAt(uint256 tokenId, uint24 value) public {
86 | _setExtraDataAt(tokenId, value);
87 | }
88 |
89 | function _extraData(
90 | address,
91 | address,
92 | uint24 previousExtraData
93 | ) internal view virtual override returns (uint24) {
94 | return previousExtraData;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721AStartTokenIdMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import './ERC721AMockUpgradeable.sol';
8 | import './StartTokenIdHelperUpgradeable.sol';
9 | import '../ERC721A__Initializable.sol';
10 |
11 | contract ERC721AStartTokenIdMockUpgradeable is
12 | ERC721A__Initializable,
13 | StartTokenIdHelperUpgradeable,
14 | ERC721AMockUpgradeable
15 | {
16 | function __ERC721AStartTokenIdMock_init(
17 | string memory name_,
18 | string memory symbol_,
19 | uint256 startTokenId_
20 | ) internal onlyInitializingERC721A {
21 | __StartTokenIdHelper_init_unchained(startTokenId_);
22 | __ERC721A_init_unchained(name_, symbol_);
23 | __DirectBurnBitSetterHelper_init_unchained();
24 | __ERC721AMock_init_unchained(name_, symbol_);
25 | __ERC721AStartTokenIdMock_init_unchained(name_, symbol_, startTokenId_);
26 | }
27 |
28 | function __ERC721AStartTokenIdMock_init_unchained(
29 | string memory,
30 | string memory,
31 | uint256
32 | ) internal onlyInitializingERC721A {}
33 |
34 | function _startTokenId() internal view override returns (uint256) {
35 | return startTokenId();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721ATransferCounterMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import './ERC721AMockUpgradeable.sol';
8 | import '../ERC721A__Initializable.sol';
9 |
10 | contract ERC721ATransferCounterMockUpgradeable is ERC721A__Initializable, ERC721AMockUpgradeable {
11 | function __ERC721ATransferCounterMock_init(string memory name_, string memory symbol_)
12 | internal
13 | onlyInitializingERC721A
14 | {
15 | __ERC721A_init_unchained(name_, symbol_);
16 | __DirectBurnBitSetterHelper_init_unchained();
17 | __ERC721AMock_init_unchained(name_, symbol_);
18 | __ERC721ATransferCounterMock_init_unchained(name_, symbol_);
19 | }
20 |
21 | function __ERC721ATransferCounterMock_init_unchained(string memory, string memory)
22 | internal
23 | onlyInitializingERC721A
24 | {}
25 |
26 | function _extraData(
27 | address from,
28 | address to,
29 | uint24 previousExtraData
30 | ) internal view virtual override returns (uint24) {
31 | if (from == address(0)) {
32 | return 42;
33 | }
34 | if (to == address(0)) {
35 | return 1337;
36 | }
37 | return previousExtraData + 1;
38 | }
39 |
40 | function setExtraDataAt(uint256 index, uint24 extraData) public {
41 | _setExtraDataAt(index, extraData);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721AWithERC2309MockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../ERC721AUpgradeable.sol';
8 | import '../ERC721A__Initializable.sol';
9 |
10 | contract ERC721AWithERC2309MockUpgradeable is ERC721A__Initializable, ERC721AUpgradeable {
11 | function __ERC721AWithERC2309Mock_init(
12 | string memory name_,
13 | string memory symbol_,
14 | address to,
15 | uint256 quantity,
16 | bool mintInConstructor
17 | ) internal onlyInitializingERC721A {
18 | __ERC721A_init_unchained(name_, symbol_);
19 | __ERC721AWithERC2309Mock_init_unchained(name_, symbol_, to, quantity, mintInConstructor);
20 | }
21 |
22 | function __ERC721AWithERC2309Mock_init_unchained(
23 | string memory,
24 | string memory,
25 | address to,
26 | uint256 quantity,
27 | bool mintInConstructor
28 | ) internal onlyInitializingERC721A {
29 | if (mintInConstructor) {
30 | _mintERC2309(to, quantity);
31 | }
32 | }
33 |
34 | /**
35 | * @dev This function is only for gas comparison purposes.
36 | * Calling `_mintERC3201` outside of contract creation is non-compliant
37 | * with the ERC721 standard.
38 | */
39 | function mintOneERC2309(address to) public {
40 | _mintERC2309(to, 1);
41 | }
42 |
43 | /**
44 | * @dev This function is only for gas comparison purposes.
45 | * Calling `_mintERC3201` outside of contract creation is non-compliant
46 | * with the ERC721 standard.
47 | */
48 | function mintTenERC2309(address to) public {
49 | _mintERC2309(to, 10);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721ReceiverMockStorage.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.0;
4 |
5 | import {ERC721ReceiverMockUpgradeable} from './ERC721ReceiverMockUpgradeable.sol';
6 |
7 | library ERC721ReceiverMockStorage {
8 | struct Layout {
9 | bytes4 _retval;
10 | address _erc721aMock;
11 | }
12 |
13 | bytes32 internal constant STORAGE_SLOT = keccak256('ERC721A.contracts.storage.ERC721ReceiverMock');
14 |
15 | function layout() internal pure returns (Layout storage l) {
16 | bytes32 slot = STORAGE_SLOT;
17 | assembly {
18 | l.slot := slot
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/contracts/mocks/ERC721ReceiverMockUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 |
7 | import '../ERC721AUpgradeable.sol';
8 | import {ERC721ReceiverMockStorage} from './ERC721ReceiverMockStorage.sol';
9 | import '../ERC721A__Initializable.sol';
10 |
11 | interface IERC721AMockUpgradeable {
12 | function safeMint(address to, uint256 quantity) external;
13 | }
14 |
15 | contract ERC721ReceiverMockUpgradeable is ERC721A__Initializable, ERC721A__IERC721ReceiverUpgradeable {
16 | using ERC721ReceiverMockStorage for ERC721ReceiverMockStorage.Layout;
17 | enum Error {
18 | None,
19 | RevertWithMessage,
20 | RevertWithoutMessage,
21 | Panic
22 | }
23 |
24 | event Received(address operator, address from, uint256 tokenId, bytes data, uint256 gas);
25 |
26 | function __ERC721ReceiverMock_init(bytes4 retval, address erc721aMock) internal onlyInitializingERC721A {
27 | __ERC721ReceiverMock_init_unchained(retval, erc721aMock);
28 | }
29 |
30 | function __ERC721ReceiverMock_init_unchained(bytes4 retval, address erc721aMock) internal onlyInitializingERC721A {
31 | ERC721ReceiverMockStorage.layout()._retval = retval;
32 | ERC721ReceiverMockStorage.layout()._erc721aMock = erc721aMock;
33 | }
34 |
35 | function onERC721Received(
36 | address operator,
37 | address from,
38 | uint256 tokenId,
39 | bytes memory data
40 | ) public override returns (bytes4) {
41 | uint256 dataValue = data.length == 0 ? 0 : uint256(uint8(data[0]));
42 |
43 | // For testing reverts with a message from the receiver contract.
44 | if (dataValue == 0x01) {
45 | revert('reverted in the receiver contract!');
46 | }
47 |
48 | // For testing with the returned wrong value from the receiver contract.
49 | if (dataValue == 0x02) {
50 | return 0x0;
51 | }
52 |
53 | // For testing the reentrancy protection.
54 | if (dataValue == 0x03) {
55 | IERC721AMockUpgradeable(ERC721ReceiverMockStorage.layout()._erc721aMock).safeMint(address(this), 1);
56 | }
57 |
58 | emit Received(operator, from, tokenId, data, 20000);
59 | return ERC721ReceiverMockStorage.layout()._retval;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/contracts/mocks/SequentialUpToHelperUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 | import '../ERC721A__Initializable.sol';
7 |
8 | /**
9 | * This Helper is used to return a dynamic value in the overridden _sequentialUpTo() function.
10 | * Extending this Helper before the ERC721A contract give us access to the herein set `sequentialUpTo`
11 | * to be returned by the overridden `_sequentialUpTo()` function of ERC721A in the ERC721ASpot mocks.
12 | */
13 | contract SequentialUpToHelperUpgradeable is ERC721A__Initializable {
14 | // `bytes4(keccak256('sequentialUpTo'))`.
15 | uint256 private constant SEQUENTIAL_UP_TO_STORAGE_SLOT = 0x9638c59e;
16 |
17 | function __SequentialUpToHelper_init(uint256 sequentialUpTo_) internal onlyInitializingERC721A {
18 | __SequentialUpToHelper_init_unchained(sequentialUpTo_);
19 | }
20 |
21 | function __SequentialUpToHelper_init_unchained(uint256 sequentialUpTo_) internal onlyInitializingERC721A {
22 | _initializeSequentialUpTo(sequentialUpTo_);
23 | }
24 |
25 | function sequentialUpTo() public view returns (uint256 result) {
26 | assembly {
27 | result := sload(SEQUENTIAL_UP_TO_STORAGE_SLOT)
28 | }
29 | }
30 |
31 | function _initializeSequentialUpTo(uint256 value) private {
32 | // We use assembly to directly set the `sequentialUpTo` in storage so that
33 | // inheriting this class won't affect the layout of other storage slots.
34 | assembly {
35 | sstore(SEQUENTIAL_UP_TO_STORAGE_SLOT, value)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/contracts/mocks/StartTokenIdHelperUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // ERC721A Contracts v4.3.0
3 | // Creators: Chiru Labs
4 |
5 | pragma solidity ^0.8.4;
6 | import '../ERC721A__Initializable.sol';
7 |
8 | /**
9 | * This Helper is used to return a dynamic value in the overridden _startTokenId() function.
10 | * Extending this Helper before the ERC721A contract give us access to the herein set `startTokenId`
11 | * to be returned by the overridden `_startTokenId()` function of ERC721A in the ERC721AStartTokenId mocks.
12 | */
13 | contract StartTokenIdHelperUpgradeable is ERC721A__Initializable {
14 | // `bytes4(keccak256('startTokenId'))`.
15 | uint256 private constant _START_TOKEN_ID_STORAGE_SLOT = 0x28f75032;
16 |
17 | function __StartTokenIdHelper_init(uint256 startTokenId_) internal onlyInitializingERC721A {
18 | __StartTokenIdHelper_init_unchained(startTokenId_);
19 | }
20 |
21 | function __StartTokenIdHelper_init_unchained(uint256 startTokenId_) internal onlyInitializingERC721A {
22 | _initializeStartTokenId(startTokenId_);
23 | }
24 |
25 | function startTokenId() public view returns (uint256 result) {
26 | assembly {
27 | result := sload(_START_TOKEN_ID_STORAGE_SLOT)
28 | }
29 | }
30 |
31 | function _initializeStartTokenId(uint256 value) private {
32 | // We use assembly to directly set the `startTokenId` in storage so that
33 | // inheriting this class won't affect the layout of other storage slots.
34 | assembly {
35 | sstore(_START_TOKEN_ID_STORAGE_SLOT, value)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/contracts/mocks/WithInit.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity >=0.7 <0.9;
3 | pragma experimental ABIEncoderV2;
4 |
5 | import '../ERC721AUpgradeable.sol';
6 |
7 | contract ERC721AUpgradeableWithInit is ERC721AUpgradeable {
8 | constructor(string memory name_, string memory symbol_) payable initializerERC721A {
9 | __ERC721A_init(name_, symbol_);
10 | }
11 | }
12 | import './ERC721ReceiverMockUpgradeable.sol';
13 |
14 | contract ERC721ReceiverMockUpgradeableWithInit is ERC721ReceiverMockUpgradeable {
15 | constructor(bytes4 retval, address erc721aMock) payable initializerERC721A {
16 | __ERC721ReceiverMock_init(retval, erc721aMock);
17 | }
18 | }
19 | import './ERC721AWithERC2309MockUpgradeable.sol';
20 |
21 | contract ERC721AWithERC2309MockUpgradeableWithInit is ERC721AWithERC2309MockUpgradeable {
22 | constructor(
23 | string memory name_,
24 | string memory symbol_,
25 | address to,
26 | uint256 quantity,
27 | bool mintInConstructor
28 | ) payable initializerERC721A {
29 | __ERC721AWithERC2309Mock_init(name_, symbol_, to, quantity, mintInConstructor);
30 | }
31 | }
32 | import './ERC721AMockUpgradeable.sol';
33 |
34 | contract ERC721AMockUpgradeableWithInit is ERC721AMockUpgradeable {
35 | constructor(string memory name_, string memory symbol_) payable initializerERC721A {
36 | __ERC721AMock_init(name_, symbol_);
37 | }
38 | }
39 | import './DirectBurnBitSetterHelperUpgradeable.sol';
40 |
41 | contract DirectBurnBitSetterHelperUpgradeableWithInit is DirectBurnBitSetterHelperUpgradeable {
42 | constructor() payable initializerERC721A {
43 | __DirectBurnBitSetterHelper_init();
44 | }
45 | }
46 | import './ERC721ATransferCounterMockUpgradeable.sol';
47 |
48 | contract ERC721ATransferCounterMockUpgradeableWithInit is ERC721ATransferCounterMockUpgradeable {
49 | constructor(string memory name_, string memory symbol_) payable initializerERC721A {
50 | __ERC721ATransferCounterMock_init(name_, symbol_);
51 | }
52 | }
53 | import './ERC721AStartTokenIdMockUpgradeable.sol';
54 |
55 | contract ERC721AStartTokenIdMockUpgradeableWithInit is ERC721AStartTokenIdMockUpgradeable {
56 | constructor(
57 | string memory name_,
58 | string memory symbol_,
59 | uint256 startTokenId_
60 | ) payable initializerERC721A {
61 | __ERC721AStartTokenIdMock_init(name_, symbol_, startTokenId_);
62 | }
63 | }
64 | import './StartTokenIdHelperUpgradeable.sol';
65 |
66 | contract StartTokenIdHelperUpgradeableWithInit is StartTokenIdHelperUpgradeable {
67 | constructor(uint256 startTokenId_) payable initializerERC721A {
68 | __StartTokenIdHelper_init(startTokenId_);
69 | }
70 | }
71 | import './ERC721ASpotMockUpgradeable.sol';
72 |
73 | contract ERC721ASpotMockUpgradeableWithInit is ERC721ASpotMockUpgradeable {
74 | constructor(
75 | string memory name_,
76 | string memory symbol_,
77 | uint256 startTokenId_,
78 | uint256 sequentialUpTo_,
79 | uint256 quantity,
80 | bool mintInConstructor
81 | ) payable initializerERC721A {
82 | __ERC721ASpotMock_init(name_, symbol_, startTokenId_, sequentialUpTo_, quantity, mintInConstructor);
83 | }
84 | }
85 | import './ERC721AQueryableMockUpgradeable.sol';
86 |
87 | contract ERC721AQueryableMockUpgradeableWithInit is ERC721AQueryableMockUpgradeable {
88 | constructor(string memory name_, string memory symbol_) payable initializerERC721A {
89 | __ERC721AQueryableMock_init(name_, symbol_);
90 | }
91 | }
92 | import './SequentialUpToHelperUpgradeable.sol';
93 |
94 | contract SequentialUpToHelperUpgradeableWithInit is SequentialUpToHelperUpgradeable {
95 | constructor(uint256 sequentialUpTo_) payable initializerERC721A {
96 | __SequentialUpToHelper_init(sequentialUpTo_);
97 | }
98 | }
99 | import './ERC721AQueryableStartTokenIdMockUpgradeable.sol';
100 |
101 | contract ERC721AQueryableStartTokenIdMockUpgradeableWithInit is ERC721AQueryableStartTokenIdMockUpgradeable {
102 | constructor(
103 | string memory name_,
104 | string memory symbol_,
105 | uint256 startTokenId_
106 | ) payable initializerERC721A {
107 | __ERC721AQueryableStartTokenIdMock_init(name_, symbol_, startTokenId_);
108 | }
109 | }
110 | import './ERC721ABurnableMockUpgradeable.sol';
111 |
112 | contract ERC721ABurnableMockUpgradeableWithInit is ERC721ABurnableMockUpgradeable {
113 | constructor(string memory name_, string memory symbol_) payable initializerERC721A {
114 | __ERC721ABurnableMock_init(name_, symbol_);
115 | }
116 | }
117 | import './ERC721ABurnableStartTokenIdMockUpgradeable.sol';
118 |
119 | contract ERC721ABurnableStartTokenIdMockUpgradeableWithInit is ERC721ABurnableStartTokenIdMockUpgradeable {
120 | constructor(
121 | string memory name_,
122 | string memory symbol_,
123 | uint256 startTokenId_
124 | ) payable initializerERC721A {
125 | __ERC721ABurnableStartTokenIdMock_init(name_, symbol_, startTokenId_);
126 | }
127 | }
128 | import './ERC4907AMockUpgradeable.sol';
129 |
130 | contract ERC4907AMockUpgradeableWithInit is ERC4907AMockUpgradeable {
131 | constructor(string memory name_, string memory symbol_) payable initializerERC721A {
132 | __ERC4907AMock_init(name_, symbol_);
133 | }
134 | }
135 | import './ERC721AGasReporterMockUpgradeable.sol';
136 |
137 | contract ERC721AGasReporterMockUpgradeableWithInit is ERC721AGasReporterMockUpgradeable {
138 | constructor(string memory name_, string memory symbol_) payable initializerERC721A {
139 | __ERC721AGasReporterMock_init(name_, symbol_);
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/hardhat.config.js:
--------------------------------------------------------------------------------
1 | require('@nomiclabs/hardhat-waffle');
2 | require('@nomiclabs/hardhat-ethers');
3 |
4 | const { internalTask } = require('hardhat/config');
5 | const { TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT } = require('hardhat/builtin-tasks/task-names');
6 |
7 | if (process.env.REPORT_GAS) {
8 | require('hardhat-gas-reporter');
9 | }
10 |
11 | if (process.env.REPORT_COVERAGE) {
12 | require('solidity-coverage');
13 | }
14 |
15 | internalTask(TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT, async (args, hre, runSuper) => {
16 | const input = await runSuper();
17 | input.settings.outputSelection['*']['*'].push('storageLayout');
18 | return input;
19 | });
20 |
21 | /**
22 | * @type import('hardhat/config').HardhatUserConfig
23 | */
24 | module.exports = {
25 | solidity: {
26 | version: '0.8.11',
27 | settings: {
28 | optimizer: {
29 | enabled: true,
30 | runs: 800,
31 | },
32 | },
33 | },
34 | gasReporter: {
35 | currency: 'USD',
36 | gasPrice: 100,
37 | showTimeSpent: true,
38 | },
39 | plugins: ['solidity-coverage'],
40 | };
41 |
42 | // The "ripemd160" algorithm is not available anymore in NodeJS 17+ (because of lib SSL 3).
43 | // The following code replaces it with "sha256" instead.
44 |
45 | const crypto = require('crypto');
46 |
47 | try {
48 | crypto.createHash('ripemd160');
49 | } catch (e) {
50 | const origCreateHash = crypto.createHash;
51 | crypto.createHash = (alg, opts) => {
52 | return origCreateHash(alg === 'ripemd160' ? 'sha256' : alg, opts);
53 | };
54 | }
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "erc721a-upgradeable",
3 | "version": "4.3.0",
4 | "description": "ERC721A Upgradeable contract for Solidity",
5 | "files": [
6 | "/contracts/**/*.sol",
7 | "/build/contracts/*.json",
8 | "!/contracts/mocks/**/*"
9 | ],
10 | "scripts": {
11 | "node": "hardhat node",
12 | "test": "hardhat test",
13 | "test:gas": "REPORT_GAS=true npx hardhat test",
14 | "coverage": "REPORT_COVERAGE=true npx hardhat coverage",
15 | "lint": "npm run lint:js && npm run lint:sol",
16 | "lint:js": "eslint --ignore-path .gitignore . --fix",
17 | "lint:sol": "prettier --write \"contracts/**/*.sol\""
18 | },
19 | "devDependencies": {
20 | "@openzeppelin/contracts": "^4.4.2",
21 | "@nomiclabs/hardhat-ethers": "^2.0.4",
22 | "@nomiclabs/hardhat-waffle": "^2.0.1",
23 | "@openzeppelin/test-helpers": "^0.5.15",
24 | "@gnus.ai/upgrade-safe-transpiler-diamond": "latest",
25 | "chai": "^4.3.4",
26 | "eslint": "^8.7.0",
27 | "eslint-plugin-mocha": "^10.0.3",
28 | "eslint-plugin-node": "^11.1.0",
29 | "ethereum-waffle": "^3.4.0",
30 | "ethers": "^5.5.3",
31 | "hardhat": "^2.8.2",
32 | "hardhat-gas-reporter": "^1.0.7",
33 | "prettier": "^2.5.1",
34 | "prettier-plugin-solidity": "^1.0.0-beta.19",
35 | "solidity-coverage": "^0.7.20"
36 | },
37 | "repository": {
38 | "type": "git",
39 | "url": "git+https://github.com/chiru-labs/ERC721A-Upgradeable.git"
40 | },
41 | "author": "chiru-labs",
42 | "license": "ISC",
43 | "bugs": {
44 | "url": "https://github.com/chiru-labs/ERC721A-Upgradeable/issues"
45 | },
46 | "homepage": "https://github.com/chiru-labs/ERC721A-Upgradeable#readme"
47 | }
48 |
--------------------------------------------------------------------------------
/scripts/replace-imports.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const fs = require('fs');
3 | const glob = require('glob');
4 |
5 | // Rename files.
6 | fs.renameSync('contracts/Initializable.sol', 'contracts/ERC721A__Initializable.sol');
7 | fs.renameSync('contracts/InitializableStorage.sol', 'contracts/ERC721A__InitializableStorage.sol');
8 |
9 | // Loop through all files with contracts/**/*.sol pattern.
10 | glob('contracts/**/*.sol', null, function (err, files) {
11 | files.forEach((file) => {
12 | // Read file content.
13 | const content = fs.readFileSync(file, 'utf8');
14 |
15 | const updatedContent = content
16 | .replace(/open.*?torage\./g, 'ERC721A.contracts.storage.')
17 | .replace(/modifier initializer/g, 'modifier initializerERC721A')
18 | .replace(/initializer\s*?\{/g, 'initializerERC721A {')
19 | .replace(/modifier onlyInitializing/g, 'modifier onlyInitializingERC721A')
20 | .replace(/onlyInitializing\s*?\{/g, 'onlyInitializingERC721A {')
21 | .replace(/Initializable/g, 'ERC721A__Initializable');
22 |
23 | // Write updated file.
24 | fs.writeFileSync(file, updatedContent);
25 | });
26 | });
27 |
28 | // Replace the TokenApprovalRef to break cyclic importing.
29 | let erc721aFilepath = 'contracts/ERC721AUpgradeable.sol';
30 | let erc721aContents = fs.readFileSync(erc721aFilepath, 'utf8');
31 | let tokenApprovalRefRe = /\/\/.*?\n\r?\s*struct TokenApprovalRef\s*\{[^}]+\}/;
32 | let tokenApprovalRefMatch = erc721aContents.match(tokenApprovalRefRe);
33 | if (tokenApprovalRefMatch) {
34 | erc721aContents = erc721aContents
35 | .replace(tokenApprovalRefMatch[0], '')
36 | .replace(/TokenApprovalRef/g, 'ERC721AStorage.TokenApprovalRef');
37 | fs.writeFileSync(erc721aFilepath, erc721aContents);
38 |
39 | let erc721aStorageFilepath = 'contracts/ERC721AStorage.sol';
40 | let erc721aStorageContents = fs.readFileSync(erc721aStorageFilepath, 'utf8');
41 | erc721aStorageContents = erc721aStorageContents
42 | .replace(/struct Layout\s*\{/, tokenApprovalRefMatch[0] + '\n\n struct Layout {')
43 | .replace(/ERC721AUpgradeable.TokenApprovalRef/g, 'ERC721AStorage.TokenApprovalRef')
44 | .replace(/import.*?\.\/ERC721AUpgradeable.sol[^;]+;/, '');
45 |
46 | fs.writeFileSync(erc721aStorageFilepath, erc721aStorageContents);
47 | }
48 |
--------------------------------------------------------------------------------
/scripts/transpile-merge.sh:
--------------------------------------------------------------------------------
1 | git checkout main;
2 |
3 | # Download the latest ERC721A.
4 | echo "Getting latest ERC721A";
5 | if [[ -f "ERC721A/package.json" ]]; then
6 | cd ERC721A;
7 | git fetch --all;
8 | git reset --hard origin/main;
9 | cd ..;
10 | else
11 | git clone https://github.com/chiru-labs/ERC721A.git;
12 | fi
13 |
14 | # Get the last commit hash of ERC721A
15 | cd ./ERC721A;
16 | commit="$(git rev-parse HEAD)";
17 | cd ..;
18 |
19 | # Replace the contracts and test folder with the latest copy.
20 | rm -r ./contracts;
21 | rm -r ./test;
22 | rsync -av --progress ERC721A/ ./ \
23 | --exclude README.md \
24 | --exclude projects.md \
25 | --exclude hardhat.config.js \
26 | --exclude .github/ \
27 | --exclude .git/ \
28 | --exclude docs/ \
29 | --exclude scripts/ \
30 | --exclude package.json \
31 | --exclude package-lock.json;
32 | rm -rf ./ERC721A;
33 |
34 | # Recompile the contracts.
35 | npx hardhat clean;
36 | npx hardhat compile;
37 |
38 | # Transpile.
39 | echo "Transpiling";
40 | # -D: delete original and excluded files
41 | # -E: extract storage for Diamond Pattern
42 | npx @gnus.ai/upgrade-safe-transpiler-diamond -D -E;
43 | node scripts/replace-imports.js;
44 | (npm run lint:sol) || true;
45 |
46 | # Commit and push
47 | echo "Committing latest code";
48 | git config user.name 'github-actions';
49 | git config user.email '41898282+github-actions[bot]@users.noreply.github.com';
50 | git add -A;
51 | (git commit -m "Transpile chiru-labs/ERC721A@$commit" && git push origin main) || echo "No changes to commit";
52 |
--------------------------------------------------------------------------------
/test/ERC721A.test.js:
--------------------------------------------------------------------------------
1 | const { deployContract, getBlockTimestamp, mineBlockTimestamp, offsettedIndex } = require('./helpers.js');
2 | const { expect } = require('chai');
3 | const { BigNumber } = require('ethers');
4 | const { constants } = require('@openzeppelin/test-helpers');
5 | const { ZERO_ADDRESS } = constants;
6 |
7 | const RECEIVER_MAGIC_VALUE = '0x150b7a02';
8 | const GAS_MAGIC_VALUE = 20000;
9 |
10 | const createTestSuite = ({ contract, constructorArgs }) =>
11 | function () {
12 | let offsetted;
13 |
14 | context(`${contract}`, function () {
15 | beforeEach(async function () {
16 | this.erc721a = await deployContract(contract, constructorArgs);
17 | this.receiver = await deployContract('ERC721ReceiverMock', [RECEIVER_MAGIC_VALUE, this.erc721a.address]);
18 | this.startTokenId = this.erc721a.startTokenId ? (await this.erc721a.startTokenId()).toNumber() : 0;
19 |
20 | offsetted = (...arr) => offsettedIndex(this.startTokenId, arr);
21 | });
22 |
23 | describe('EIP-165 support', async function () {
24 | it('supports ERC165', async function () {
25 | expect(await this.erc721a.supportsInterface('0x01ffc9a7')).to.eq(true);
26 | });
27 |
28 | it('supports IERC721', async function () {
29 | expect(await this.erc721a.supportsInterface('0x80ac58cd')).to.eq(true);
30 | });
31 |
32 | it('supports ERC721Metadata', async function () {
33 | expect(await this.erc721a.supportsInterface('0x5b5e139f')).to.eq(true);
34 | });
35 |
36 | it('does not support ERC721Enumerable', async function () {
37 | expect(await this.erc721a.supportsInterface('0x780e9d63')).to.eq(false);
38 | });
39 |
40 | it('does not support random interface', async function () {
41 | expect(await this.erc721a.supportsInterface('0x00000042')).to.eq(false);
42 | });
43 | });
44 |
45 | describe('ERC721Metadata support', async function () {
46 | it('name', async function () {
47 | expect(await this.erc721a.name()).to.eq(constructorArgs[0]);
48 | });
49 |
50 | it('symbol', async function () {
51 | expect(await this.erc721a.symbol()).to.eq(constructorArgs[1]);
52 | });
53 |
54 | describe('baseURI', async function () {
55 | it('sends an empty URI by default', async function () {
56 | expect(await this.erc721a.baseURI()).to.eq('');
57 | });
58 | });
59 | });
60 |
61 | context('with no minted tokens', async function () {
62 | it('has 0 totalSupply', async function () {
63 | const supply = await this.erc721a.totalSupply();
64 | expect(supply).to.equal(0);
65 | });
66 |
67 | it('has 0 totalMinted', async function () {
68 | const totalMinted = await this.erc721a.totalMinted();
69 | expect(totalMinted).to.equal(0);
70 | });
71 |
72 | it('has 0 totalBurned', async function () {
73 | const totalBurned = await this.erc721a.totalBurned();
74 | expect(totalBurned).to.equal(0);
75 | });
76 |
77 | it('_nextTokenId must be equal to _startTokenId', async function () {
78 | const nextTokenId = await this.erc721a.nextTokenId();
79 | expect(nextTokenId).to.equal(offsetted(0));
80 | });
81 | });
82 |
83 | context('with minted tokens', async function () {
84 | beforeEach(async function () {
85 | const [owner, addr1, addr2, addr3, addr4] = await ethers.getSigners();
86 | this.owner = owner;
87 | this.addr1 = addr1;
88 | this.addr2 = addr2;
89 | this.addr3 = addr3;
90 | this.addr4 = addr4;
91 | this.expectedMintCount = 6;
92 |
93 | this.addr1.expected = {
94 | mintCount: 1,
95 | tokens: [offsetted(0)],
96 | };
97 |
98 | this.addr2.expected = {
99 | mintCount: 2,
100 | tokens: offsetted(1, 2),
101 | };
102 |
103 | this.addr3.expected = {
104 | mintCount: 3,
105 | tokens: offsetted(3, 4, 5),
106 | };
107 |
108 | await this.erc721a['safeMint(address,uint256)'](addr1.address, this.addr1.expected.mintCount);
109 | await this.erc721a['safeMint(address,uint256)'](addr2.address, this.addr2.expected.mintCount);
110 | await this.erc721a['safeMint(address,uint256)'](addr3.address, this.addr3.expected.mintCount);
111 | });
112 |
113 | describe('tokenURI (ERC721Metadata)', async function () {
114 | describe('tokenURI', async function () {
115 | it('sends an empty uri by default', async function () {
116 | expect(await this.erc721a.tokenURI(offsetted(0))).to.eq('');
117 | });
118 |
119 | it('reverts when tokenId does not exist', async function () {
120 | await expect(this.erc721a.tokenURI(offsetted(this.expectedMintCount))).to.be.revertedWith(
121 | 'URIQueryForNonexistentToken'
122 | );
123 | });
124 | });
125 | });
126 |
127 | describe('exists', async function () {
128 | it('verifies valid tokens', async function () {
129 | for (let tokenId = offsetted(0); tokenId < offsetted(this.expectedMintCount); tokenId++) {
130 | const exists = await this.erc721a.exists(tokenId);
131 | expect(exists).to.be.true;
132 | }
133 | });
134 |
135 | it('verifies invalid tokens', async function () {
136 | expect(await this.erc721a.exists(offsetted(this.expectedMintCount))).to.be.false;
137 | });
138 | });
139 |
140 | describe('balanceOf', async function () {
141 | it('returns the amount for a given address', async function () {
142 | expect(await this.erc721a.balanceOf(this.owner.address)).to.equal('0');
143 | expect(await this.erc721a.balanceOf(this.addr1.address)).to.equal(this.addr1.expected.mintCount);
144 | expect(await this.erc721a.balanceOf(this.addr2.address)).to.equal(this.addr2.expected.mintCount);
145 | expect(await this.erc721a.balanceOf(this.addr3.address)).to.equal(this.addr3.expected.mintCount);
146 | });
147 |
148 | it('returns correct amount with transferred tokens', async function () {
149 | const tokenIdToTransfer = this.addr2.expected.tokens[0];
150 | await this.erc721a
151 | .connect(this.addr2)
152 | .transferFrom(this.addr2.address, this.addr3.address, tokenIdToTransfer);
153 | // sanity check
154 | expect(await this.erc721a.ownerOf(tokenIdToTransfer)).to.equal(this.addr3.address);
155 |
156 | expect(await this.erc721a.balanceOf(this.addr2.address)).to.equal(this.addr2.expected.mintCount - 1);
157 | expect(await this.erc721a.balanceOf(this.addr3.address)).to.equal(this.addr3.expected.mintCount + 1);
158 | });
159 |
160 | it('throws an exception for the 0 address', async function () {
161 | await expect(this.erc721a.balanceOf(ZERO_ADDRESS)).to.be.revertedWith('BalanceQueryForZeroAddress');
162 | });
163 | });
164 |
165 | describe('_numberMinted', async function () {
166 | it('returns the amount for a given address', async function () {
167 | expect(await this.erc721a.numberMinted(this.owner.address)).to.equal('0');
168 | expect(await this.erc721a.numberMinted(this.addr1.address)).to.equal(this.addr1.expected.mintCount);
169 | expect(await this.erc721a.numberMinted(this.addr2.address)).to.equal(this.addr2.expected.mintCount);
170 | expect(await this.erc721a.numberMinted(this.addr3.address)).to.equal(this.addr3.expected.mintCount);
171 | });
172 |
173 | it('returns the same amount with transferred token', async function () {
174 | const tokenIdToTransfer = this.addr2.expected.tokens[0];
175 | await this.erc721a
176 | .connect(this.addr2)
177 | .transferFrom(this.addr2.address, this.addr3.address, tokenIdToTransfer);
178 | // sanity check
179 | expect(await this.erc721a.ownerOf(tokenIdToTransfer)).to.equal(this.addr3.address);
180 |
181 | expect(await this.erc721a.numberMinted(this.addr2.address)).to.equal(this.addr2.expected.mintCount);
182 | expect(await this.erc721a.numberMinted(this.addr3.address)).to.equal(this.addr3.expected.mintCount);
183 | });
184 | });
185 |
186 | context('_totalMinted', async function () {
187 | it('has correct totalMinted', async function () {
188 | const totalMinted = await this.erc721a.totalMinted();
189 | expect(totalMinted).to.equal(this.expectedMintCount);
190 | });
191 | });
192 |
193 | context('_nextTokenId', async function () {
194 | it('has correct nextTokenId', async function () {
195 | const nextTokenId = await this.erc721a.nextTokenId();
196 | expect(nextTokenId).to.equal(offsetted(this.expectedMintCount));
197 | });
198 | });
199 |
200 | describe('aux', async function () {
201 | it('get and set works correctly', async function () {
202 | const uint64Max = BigNumber.from(2).pow(64).sub(1).toString();
203 | expect(await this.erc721a.getAux(this.owner.address)).to.equal('0');
204 | await this.erc721a.setAux(this.owner.address, uint64Max);
205 | expect(await this.erc721a.getAux(this.owner.address)).to.equal(uint64Max);
206 |
207 | expect(await this.erc721a.getAux(this.addr1.address)).to.equal('0');
208 | await this.erc721a.setAux(this.addr1.address, '1');
209 | expect(await this.erc721a.getAux(this.addr1.address)).to.equal('1');
210 |
211 | await this.erc721a.setAux(this.addr3.address, '5');
212 | expect(await this.erc721a.getAux(this.addr3.address)).to.equal('5');
213 |
214 | expect(await this.erc721a.getAux(this.addr1.address)).to.equal('1');
215 | });
216 | });
217 |
218 | describe('ownerOf', async function () {
219 | it('returns the right owner', async function () {
220 | for (const minter of [this.addr1, this.addr2, this.addr3]) {
221 | for (const tokenId of minter.expected.tokens) {
222 | expect(await this.erc721a.ownerOf(tokenId)).to.equal(minter.address);
223 | }
224 | }
225 | });
226 |
227 | it('reverts for an invalid token', async function () {
228 | await expect(this.erc721a.ownerOf(10)).to.be.revertedWith('OwnerQueryForNonexistentToken');
229 |
230 | if (this.startTokenId > 0) {
231 | await expect(this.erc721a.ownerOf(0)).to.be.revertedWith('OwnerQueryForNonexistentToken');
232 | }
233 | });
234 | });
235 |
236 | describe('approve', async function () {
237 | beforeEach(function () {
238 | this.tokenId = this.addr1.expected.tokens[0];
239 | this.tokenId2 = this.addr2.expected.tokens[0];
240 | });
241 |
242 | it('sets approval for the target address', async function () {
243 | await this.erc721a.connect(this.addr1).approve(this.addr2.address, this.tokenId);
244 | const approval = await this.erc721a.getApproved(this.tokenId);
245 | expect(approval).to.equal(this.addr2.address);
246 | });
247 |
248 | it('set approval for the target address on behalf of the owner', async function () {
249 | await this.erc721a.connect(this.addr1).setApprovalForAll(this.addr2.address, true);
250 | await this.erc721a.connect(this.addr2).approve(this.addr3.address, this.tokenId);
251 | const approval = await this.erc721a.getApproved(this.tokenId);
252 | expect(approval).to.equal(this.addr3.address);
253 | });
254 |
255 | it('rejects an unapproved caller', async function () {
256 | await expect(this.erc721a.approve(this.addr2.address, this.tokenId)).to.be.revertedWith(
257 | 'ApprovalCallerNotOwnerNorApproved'
258 | );
259 | });
260 |
261 | it('does not get approved for invalid tokens', async function () {
262 | await expect(this.erc721a.getApproved(10)).to.be.revertedWith('ApprovalQueryForNonexistentToken');
263 | });
264 |
265 | it('approval allows token transfer', async function () {
266 | await expect(
267 | this.erc721a.connect(this.addr3).transferFrom(this.addr1.address, this.addr3.address, this.tokenId)
268 | ).to.be.revertedWith('TransferCallerNotOwnerNorApproved');
269 | await this.erc721a.connect(this.addr1).approve(this.addr3.address, this.tokenId);
270 | await this.erc721a.connect(this.addr3).transferFrom(this.addr1.address, this.addr3.address, this.tokenId);
271 | await expect(
272 | this.erc721a.connect(this.addr1).transferFrom(this.addr3.address, this.addr1.address, this.tokenId)
273 | ).to.be.revertedWith('TransferCallerNotOwnerNorApproved');
274 | });
275 |
276 | it('token owner can approve self as operator', async function () {
277 | expect(await this.erc721a.getApproved(this.tokenId)).to.not.equal(this.addr1.address);
278 | await expect(this.erc721a.connect(this.addr1).approve(this.addr1.address, this.tokenId)
279 | ).to.not.be.reverted;
280 | expect(await this.erc721a.getApproved(this.tokenId)).to.equal(this.addr1.address);
281 | });
282 |
283 | it('self-approval is cleared on token transfer', async function () {
284 | await this.erc721a.connect(this.addr1).approve(this.addr1.address, this.tokenId);
285 | expect(await this.erc721a.getApproved(this.tokenId)).to.equal(this.addr1.address);
286 |
287 | await this.erc721a.connect(this.addr1).transferFrom(this.addr1.address, this.addr2.address, this.tokenId);
288 | expect(await this.erc721a.getApproved(this.tokenId)).to.not.equal(this.addr1.address);
289 | });
290 |
291 | it('direct approve works', async function () {
292 | expect(await this.erc721a.getApproved(this.tokenId)).to.not.equal(this.addr1.address);
293 | await this.erc721a.connect(this.addr2).directApprove(this.addr1.address, this.tokenId);
294 | expect(await this.erc721a.getApproved(this.tokenId)).to.equal(this.addr1.address);
295 | });
296 | });
297 |
298 | describe('setApprovalForAll', async function () {
299 | it('sets approval for all properly', async function () {
300 | const approvalTx = await this.erc721a.setApprovalForAll(this.addr1.address, true);
301 | await expect(approvalTx)
302 | .to.emit(this.erc721a, 'ApprovalForAll')
303 | .withArgs(this.owner.address, this.addr1.address, true);
304 | expect(await this.erc721a.isApprovedForAll(this.owner.address, this.addr1.address)).to.be.true;
305 | });
306 |
307 | it('caller can approve all with self as operator', async function () {
308 | expect(
309 | await this.erc721a.connect(this.addr1).isApprovedForAll(this.addr1.address, this.addr1.address)
310 | ).to.be.false;
311 | await expect(
312 | this.erc721a.connect(this.addr1).setApprovalForAll(this.addr1.address, true)
313 | ).to.not.be.reverted;
314 | expect(
315 | await this.erc721a.connect(this.addr1).isApprovedForAll(this.addr1.address, this.addr1.address)
316 | ).to.be.true;
317 | });
318 | });
319 |
320 | context('test transfer functionality', function () {
321 | const testSuccessfulTransfer = function (transferFn, transferToContract = true) {
322 | beforeEach(async function () {
323 | const sender = this.addr2;
324 | this.tokenId = this.addr2.expected.tokens[0];
325 | this.from = sender.address;
326 | this.to = transferToContract ? this.receiver : this.addr4;
327 | await this.erc721a.connect(sender).approve(this.to.address, this.tokenId);
328 |
329 | const ownershipBefore = await this.erc721a.getOwnershipAt(this.tokenId);
330 | this.timestampBefore = parseInt(ownershipBefore.startTimestamp);
331 | this.timestampToMine = (await getBlockTimestamp()) + 12345;
332 | await mineBlockTimestamp(this.timestampToMine);
333 | this.timestampMined = await getBlockTimestamp();
334 |
335 | // prettier-ignore
336 | this.transferTx = await this.erc721a
337 | .connect(sender)[transferFn](this.from, this.to.address, this.tokenId);
338 |
339 | const ownershipAfter = await this.erc721a.getOwnershipAt(this.tokenId);
340 | this.timestampAfter = parseInt(ownershipAfter.startTimestamp);
341 | });
342 |
343 | it('transfers the ownership of the given token ID to the given address', async function () {
344 | expect(await this.erc721a.ownerOf(this.tokenId)).to.be.equal(this.to.address);
345 | });
346 |
347 | it('emits a Transfer event', async function () {
348 | await expect(this.transferTx)
349 | .to.emit(this.erc721a, 'Transfer')
350 | .withArgs(this.from, this.to.address, this.tokenId);
351 | });
352 |
353 | it('clears the approval for the token ID', async function () {
354 | expect(await this.erc721a.getApproved(this.tokenId)).to.be.equal(ZERO_ADDRESS);
355 | });
356 |
357 | it('adjusts owners balances', async function () {
358 | expect(await this.erc721a.balanceOf(this.from)).to.be.equal(1);
359 | });
360 |
361 | it('startTimestamp updated correctly', async function () {
362 | expect(this.timestampBefore).to.be.lt(this.timestampToMine);
363 | expect(this.timestampAfter).to.be.gte(this.timestampToMine);
364 | expect(this.timestampAfter).to.be.lt(this.timestampToMine + 10);
365 | expect(this.timestampToMine).to.be.eq(this.timestampMined);
366 | });
367 | };
368 |
369 | const testUnsuccessfulTransfer = function (transferFn) {
370 | beforeEach(function () {
371 | this.tokenId = this.addr2.expected.tokens[0];
372 | this.sender = this.addr1;
373 | });
374 |
375 | it('rejects unapproved transfer', async function () {
376 | await expect(
377 | this.erc721a.connect(this.sender)[transferFn](this.addr2.address, this.sender.address, this.tokenId)
378 | ).to.be.revertedWith('TransferCallerNotOwnerNorApproved');
379 | });
380 |
381 | it('rejects transfer from incorrect owner', async function () {
382 | await this.erc721a.connect(this.addr2).setApprovalForAll(this.sender.address, true);
383 | await expect(
384 | this.erc721a.connect(this.sender)[transferFn](this.addr3.address, this.sender.address, this.tokenId)
385 | ).to.be.revertedWith('TransferFromIncorrectOwner');
386 | });
387 |
388 | it('rejects transfer to zero address', async function () {
389 | await this.erc721a.connect(this.addr2).setApprovalForAll(this.sender.address, true);
390 | await expect(
391 | this.erc721a.connect(this.sender)[transferFn](this.addr2.address, ZERO_ADDRESS, this.tokenId)
392 | ).to.be.revertedWith('TransferToZeroAddress');
393 | });
394 | };
395 |
396 | context('successful transfers', function () {
397 | context('transferFrom', function () {
398 | describe('to contract', function () {
399 | testSuccessfulTransfer('transferFrom');
400 | });
401 |
402 | describe('to EOA', function () {
403 | testSuccessfulTransfer('transferFrom', false);
404 | });
405 | });
406 |
407 | context('safeTransferFrom', function () {
408 | describe('to contract', function () {
409 | testSuccessfulTransfer('safeTransferFrom(address,address,uint256)');
410 |
411 | it('validates ERC721Received', async function () {
412 | await expect(this.transferTx)
413 | .to.emit(this.receiver, 'Received')
414 | .withArgs(this.addr2.address, this.addr2.address, this.tokenId, '0x', GAS_MAGIC_VALUE);
415 | });
416 | });
417 |
418 | describe('to EOA', function () {
419 | testSuccessfulTransfer('safeTransferFrom(address,address,uint256)', false);
420 | });
421 | });
422 | });
423 |
424 | context('unsuccessful transfers', function () {
425 | describe('transferFrom', function () {
426 | testUnsuccessfulTransfer('transferFrom');
427 | });
428 |
429 | describe('safeTransferFrom', function () {
430 | testUnsuccessfulTransfer('safeTransferFrom(address,address,uint256)');
431 |
432 | it('reverts for non-receivers', async function () {
433 | const nonReceiver = this.erc721a;
434 | // prettier-ignore
435 | await expect(
436 | this.erc721a.connect(this.addr1)['safeTransferFrom(address,address,uint256)'](
437 | this.addr1.address,
438 | nonReceiver.address,
439 | offsetted(0)
440 | )
441 | ).to.be.revertedWith('TransferToNonERC721ReceiverImplementer');
442 | });
443 |
444 | it('reverts when the receiver reverted', async function () {
445 | // prettier-ignore
446 | await expect(
447 | this.erc721a.connect(this.addr1)['safeTransferFrom(address,address,uint256,bytes)'](
448 | this.addr1.address,
449 | this.receiver.address,
450 | offsetted(0),
451 | '0x01'
452 | )
453 | ).to.be.revertedWith('reverted in the receiver contract!');
454 | });
455 |
456 | it('reverts if the receiver returns the wrong value', async function () {
457 | // prettier-ignore
458 | await expect(
459 | this.erc721a.connect(this.addr1)['safeTransferFrom(address,address,uint256,bytes)'](
460 | this.addr1.address,
461 | this.receiver.address,
462 | offsetted(0),
463 | '0x02'
464 | )
465 | ).to.be.revertedWith('TransferToNonERC721ReceiverImplementer');
466 | });
467 | });
468 | });
469 | });
470 |
471 | describe('_burn', async function () {
472 | beforeEach(function () {
473 | this.tokenIdToBurn = offsetted(0);
474 | });
475 |
476 | it('can burn if approvalCheck is false', async function () {
477 | expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.true;
478 | await this.erc721a.connect(this.addr2)['burn(uint256,bool)'](this.tokenIdToBurn, false);
479 | expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.false;
480 | });
481 |
482 | it('revert if approvalCheck is true', async function () {
483 | await expect(
484 | this.erc721a.connect(this.addr2)['burn(uint256,bool)'](this.tokenIdToBurn, true)
485 | ).to.be.revertedWith('TransferCallerNotOwnerNorApproved');
486 | });
487 |
488 | it('can burn without approvalCheck parameter', async function () {
489 | expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.true;
490 | await this.erc721a.connect(this.addr2)['burn(uint256)'](this.tokenIdToBurn);
491 | expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.false;
492 | });
493 |
494 | it('cannot burn a token owned by another if not approved', async function () {
495 | expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.true;
496 | await this.erc721a.connect(this.addr2)['burn(uint256)'](this.tokenIdToBurn);
497 | expect(await this.erc721a.exists(this.tokenIdToBurn)).to.be.false;
498 | });
499 | });
500 |
501 | describe('_initializeOwnershipAt', async function () {
502 | it('successfuly sets ownership of empty slot', async function () {
503 | const lastTokenId = this.addr3.expected.tokens[2];
504 | const ownership1 = await this.erc721a.getOwnershipAt(lastTokenId);
505 | expect(ownership1[0]).to.equal(ZERO_ADDRESS);
506 | await this.erc721a.initializeOwnershipAt(lastTokenId);
507 | const ownership2 = await this.erc721a.getOwnershipAt(lastTokenId);
508 | expect(ownership2[0]).to.equal(this.addr3.address);
509 | });
510 |
511 | it("doesn't set ownership if it's already setted", async function () {
512 | const lastTokenId = this.addr3.expected.tokens[2];
513 | expect(await this.erc721a.ownerOf(lastTokenId)).to.be.equal(this.addr3.address);
514 | const tx1 = await this.erc721a.initializeOwnershipAt(lastTokenId);
515 | expect(await this.erc721a.ownerOf(lastTokenId)).to.be.equal(this.addr3.address);
516 | const tx2 = await this.erc721a.initializeOwnershipAt(lastTokenId);
517 |
518 | // We assume the 2nd initialization doesn't set again due to less gas used.
519 | const receipt1 = await tx1.wait();
520 | const receipt2 = await tx2.wait();
521 | expect(receipt2.gasUsed.toNumber()).to.be.lessThan(receipt1.gasUsed.toNumber());
522 | });
523 | });
524 | });
525 |
526 | context('test mint functionality', function () {
527 | beforeEach(async function () {
528 | const [owner, addr1] = await ethers.getSigners();
529 | this.owner = owner;
530 | this.addr1 = addr1;
531 | });
532 |
533 | const testSuccessfulMint = function (safe, quantity, mintForContract = true) {
534 | beforeEach(async function () {
535 | this.minter = mintForContract ? this.receiver : this.addr1;
536 |
537 | const mintFn = safe ? 'safeMint(address,uint256)' : 'mint(address,uint256)';
538 |
539 | this.balanceBefore = (await this.erc721a.balanceOf(this.minter.address)).toNumber();
540 |
541 | this.timestampToMine = (await getBlockTimestamp()) + 12345;
542 | await mineBlockTimestamp(this.timestampToMine);
543 | this.timestampMined = await getBlockTimestamp();
544 |
545 | this.mintTx = await this.erc721a[mintFn](this.minter.address, quantity);
546 | });
547 |
548 | it('changes ownership', async function () {
549 | for (let tokenId = offsetted(0); tokenId < offsetted(quantity); tokenId++) {
550 | expect(await this.erc721a.ownerOf(tokenId)).to.equal(this.minter.address);
551 | }
552 | });
553 |
554 | it('emits a Transfer event', async function () {
555 | for (let tokenId = offsetted(0); tokenId < offsetted(quantity); tokenId++) {
556 | await expect(this.mintTx)
557 | .to.emit(this.erc721a, 'Transfer')
558 | .withArgs(ZERO_ADDRESS, this.minter.address, tokenId);
559 | }
560 | });
561 |
562 | it('adjusts owners balances', async function () {
563 | expect(await this.erc721a.balanceOf(this.minter.address)).to.be.equal(this.balanceBefore + quantity);
564 | });
565 |
566 | it('adjusts OwnershipAt and OwnershipOf', async function () {
567 | const ownership = await this.erc721a.getOwnershipAt(offsetted(0));
568 | expect(ownership.startTimestamp).to.be.gte(this.timestampToMine);
569 | expect(ownership.startTimestamp).to.be.lt(this.timestampToMine + 10);
570 | expect(ownership.burned).to.be.false;
571 |
572 | for (let tokenId = offsetted(0); tokenId < offsetted(quantity); tokenId++) {
573 | const ownership = await this.erc721a.getOwnershipOf(tokenId);
574 | expect(ownership.addr).to.equal(this.minter.address);
575 | expect(ownership.startTimestamp).to.be.gte(this.timestampToMine);
576 | expect(ownership.startTimestamp).to.be.lt(this.timestampToMine + 10);
577 | expect(ownership.burned).to.be.false;
578 | }
579 |
580 | expect(this.timestampToMine).to.be.eq(this.timestampMined);
581 | });
582 |
583 | if (safe && mintForContract) {
584 | it('validates ERC721Received', async function () {
585 | for (let tokenId = offsetted(0); tokenId < offsetted(quantity); tokenId++) {
586 | await expect(this.mintTx)
587 | .to.emit(this.minter, 'Received')
588 | .withArgs(this.owner.address, ZERO_ADDRESS, tokenId, '0x', GAS_MAGIC_VALUE);
589 | }
590 | });
591 | }
592 | };
593 |
594 | const testUnsuccessfulMint = function (safe) {
595 | beforeEach(async function () {
596 | this.mintFn = safe ? 'safeMint(address,uint256)' : 'mint(address,uint256)';
597 | });
598 |
599 | it('rejects mints to the zero address', async function () {
600 | await expect(this.erc721a[this.mintFn](ZERO_ADDRESS, 1)).to.be.revertedWith('MintToZeroAddress');
601 | });
602 |
603 | it('requires quantity to be greater than 0', async function () {
604 | await expect(this.erc721a[this.mintFn](this.owner.address, 0)).to.be.revertedWith('MintZeroQuantity');
605 | });
606 | };
607 |
608 | context('successful mints', function () {
609 | context('mint', function () {
610 | context('for contract', function () {
611 | describe('single token', function () {
612 | testSuccessfulMint(false, 1);
613 | });
614 |
615 | describe('multiple tokens', function () {
616 | testSuccessfulMint(false, 5);
617 | });
618 |
619 | it('does not revert for non-receivers', async function () {
620 | const nonReceiver = this.erc721a;
621 | await this.erc721a.mint(nonReceiver.address, 1);
622 | expect(await this.erc721a.ownerOf(offsetted(0))).to.equal(nonReceiver.address);
623 | });
624 | });
625 |
626 | context('for EOA', function () {
627 | describe('single token', function () {
628 | testSuccessfulMint(false, 1, false);
629 | });
630 |
631 | describe('multiple tokens', function () {
632 | testSuccessfulMint(false, 5, false);
633 | });
634 | });
635 | });
636 |
637 | context('safeMint', function () {
638 | context('for contract', function () {
639 | describe('single token', function () {
640 | testSuccessfulMint(true, 1);
641 | });
642 |
643 | describe('multiple tokens', function () {
644 | testSuccessfulMint(true, 5);
645 | });
646 |
647 | it('validates ERC721Received with custom _data', async function () {
648 | const customData = ethers.utils.formatBytes32String('custom data');
649 | const tx = await this.erc721a['safeMint(address,uint256,bytes)'](this.receiver.address, 1, customData);
650 | await expect(tx)
651 | .to.emit(this.receiver, 'Received')
652 | .withArgs(this.owner.address, ZERO_ADDRESS, offsetted(0), customData, GAS_MAGIC_VALUE);
653 | });
654 | });
655 |
656 | context('for EOA', function () {
657 | describe('single token', function () {
658 | testSuccessfulMint(true, 1, false);
659 | });
660 |
661 | describe('multiple tokens', function () {
662 | testSuccessfulMint(true, 5, false);
663 | });
664 | });
665 | });
666 | });
667 |
668 | context('unsuccessful mints', function () {
669 | context('mint', function () {
670 | testUnsuccessfulMint(false);
671 | });
672 |
673 | context('safeMint', function () {
674 | testUnsuccessfulMint(true);
675 |
676 | it('reverts for non-receivers', async function () {
677 | const nonReceiver = this.erc721a;
678 | await expect(this.erc721a['safeMint(address,uint256)'](nonReceiver.address, 1)).to.be.revertedWith(
679 | 'TransferToNonERC721ReceiverImplementer'
680 | );
681 | });
682 |
683 | it('reverts when the receiver reverted', async function () {
684 | await expect(
685 | this.erc721a['safeMint(address,uint256,bytes)'](this.receiver.address, 1, '0x01')
686 | ).to.be.revertedWith('reverted in the receiver contract!');
687 | });
688 |
689 | it('reverts if the receiver returns the wrong value', async function () {
690 | await expect(
691 | this.erc721a['safeMint(address,uint256,bytes)'](this.receiver.address, 1, '0x02')
692 | ).to.be.revertedWith('TransferToNonERC721ReceiverImplementer');
693 | });
694 |
695 | it('reverts with reentrant call', async function () {
696 | await expect(
697 | this.erc721a['safeMint(address,uint256,bytes)'](this.receiver.address, 1, '0x03')
698 | ).to.be.reverted;
699 | });
700 | });
701 | });
702 | });
703 |
704 | context('with direct set burn bit', async function () {
705 | it('ownerOf reverts for an uninitialized burnt token', async function () {
706 | const [owner] = await ethers.getSigners();
707 | await this.erc721a['safeMint(address,uint256)'](owner.address, 3);
708 | await this.erc721a['safeMint(address,uint256)'](owner.address, 2);
709 | await this.erc721a['safeMint(address,uint256)'](owner.address, 1);
710 | for (let i = 0; i < 3 + 2 + 1; ++i) {
711 | expect(await this.erc721a.ownerOf(this.startTokenId + i)).to.eq(owner.address);
712 | }
713 | await this.erc721a.directSetBurnBit(this.startTokenId + 3);
714 | for (let i = 0; i < 3 + 2 + 1; ++i) {
715 | if (3 <= i && i < 3 + 2) {
716 | await expect(this.erc721a.ownerOf(this.startTokenId + i))
717 | .to.be.revertedWith('OwnerQueryForNonexistentToken');
718 | await expect(this.erc721a.getApproved(this.startTokenId + i))
719 | .to.be.revertedWith('ApprovalQueryForNonexistentToken');
720 | await expect(this.erc721a.tokenURI(this.startTokenId + i))
721 | .to.be.revertedWith('URIQueryForNonexistentToken');
722 | } else {
723 | expect(await this.erc721a.ownerOf(this.startTokenId + i)).to.eq(owner.address);
724 | }
725 | }
726 | });
727 | });
728 |
729 | context('_toString', async function () {
730 | it('returns correct value', async function () {
731 | expect(await this.erc721a['toString(uint256)']('0')).to.eq('0');
732 | expect(await this.erc721a['toString(uint256)']('1')).to.eq('1');
733 | expect(await this.erc721a['toString(uint256)']('2')).to.eq('2');
734 | const uint256Max = BigNumber.from(2).pow(256).sub(1).toString();
735 | expect(await this.erc721a['toString(uint256)'](uint256Max)).to.eq(uint256Max);
736 | });
737 | });
738 | });
739 | };
740 |
741 | describe('ERC721A', createTestSuite({ contract: 'ERC721AMock', constructorArgs: ['Azuki', 'AZUKI'] }));
742 |
743 | describe(
744 | 'ERC721A override _startTokenId()',
745 | createTestSuite({ contract: 'ERC721AStartTokenIdMock', constructorArgs: ['Azuki', 'AZUKI', 1] })
746 | );
747 |
748 | describe('ERC721A with ERC2309', async function () {
749 | beforeEach(async function () {
750 | const [owner, addr1] = await ethers.getSigners();
751 | this.owner = owner;
752 | this.addr1 = addr1;
753 |
754 | let args;
755 | args = ['Azuki', 'AZUKI', this.owner.address, 1, true];
756 | this.erc721aMint1 = await deployContract('ERC721AWithERC2309Mock', args);
757 | args = ['Azuki', 'AZUKI', this.owner.address, 10, true];
758 | this.erc721aMint10 = await deployContract('ERC721AWithERC2309Mock', args);
759 | });
760 |
761 | it('emits a ConsecutiveTransfer event for single mint', async function () {
762 | expect(this.erc721aMint1.deployTransaction)
763 | .to.emit(this.erc721aMint1, 'ConsecutiveTransfer')
764 | .withArgs(0, 0, ZERO_ADDRESS, this.owner.address);
765 | });
766 |
767 | it('emits a ConsecutiveTransfer event for a batch mint', async function () {
768 | expect(this.erc721aMint10.deployTransaction)
769 | .to.emit(this.erc721aMint10, 'ConsecutiveTransfer')
770 | .withArgs(0, 9, ZERO_ADDRESS, this.owner.address);
771 | });
772 |
773 | it('requires quantity to be below mint limit', async function () {
774 | let args;
775 | const mintLimit = 5000;
776 | args = ['Azuki', 'AZUKI', this.owner.address, mintLimit, true];
777 | await deployContract('ERC721AWithERC2309Mock', args);
778 | args = ['Azuki', 'AZUKI', this.owner.address, mintLimit + 1, true];
779 | await expect(deployContract('ERC721AWithERC2309Mock', args)).to.be.revertedWith('MintERC2309QuantityExceedsLimit');
780 | })
781 |
782 | it('rejects mints to the zero address', async function () {
783 | let args = ['Azuki', 'AZUKI', ZERO_ADDRESS, 1, true];
784 | await expect(deployContract('ERC721AWithERC2309Mock', args)).to.be.revertedWith('MintToZeroAddress');
785 | });
786 |
787 | it('requires quantity to be greater than 0', async function () {
788 | let args = ['Azuki', 'AZUKI', this.owner.address, 0, true];
789 | await expect(deployContract('ERC721AWithERC2309Mock', args)).to.be.revertedWith('MintZeroQuantity');
790 | });
791 | });
792 |
--------------------------------------------------------------------------------
/test/GasUsage.test.js:
--------------------------------------------------------------------------------
1 | const { deployContract } = require('./helpers.js');
2 |
3 | describe('ERC721A Gas Usage', function () {
4 | beforeEach(async function () {
5 | this.erc721a = await deployContract('ERC721AGasReporterMock', ['Azuki', 'AZUKI']);
6 | const [owner, addr1] = await ethers.getSigners();
7 | this.owner = owner;
8 | this.addr1 = addr1;
9 | });
10 |
11 | context('mintOne', function () {
12 | it('runs mintOne 2 times', async function () {
13 | for (let i = 0; i < 2; i++) {
14 | await this.erc721a.mintOne(this.addr1.address);
15 | }
16 | });
17 | });
18 |
19 | context('safeMintOne', function () {
20 | it('runs safeMintOne 2 times', async function () {
21 | for (let i = 0; i < 2; i++) {
22 | await this.erc721a.safeMintOne(this.addr1.address);
23 | }
24 | });
25 | });
26 |
27 | context('mintTen', function () {
28 | it('runs mintTen 2 times', async function () {
29 | for (let i = 0; i < 2; i++) {
30 | await this.erc721a.mintTen(this.addr1.address);
31 | }
32 | });
33 | });
34 |
35 | context('safeMintTen', function () {
36 | it('runs safeMintTen 2 times', async function () {
37 | for (let i = 0; i < 2; i++) {
38 | await this.erc721a.safeMintTen(this.addr1.address);
39 | }
40 | });
41 | });
42 |
43 | context('transferFrom', function () {
44 | beforeEach(async function () {
45 | await this.erc721a.mintTen(this.owner.address);
46 | await this.erc721a.mintOne(this.owner.address);
47 |
48 | await this.erc721a.mintTen(this.addr1.address);
49 | await this.erc721a.mintOne(this.addr1.address);
50 | });
51 |
52 | it('transfer to and from two addresses', async function () {
53 | for (let i = 0; i < 2; ++i) {
54 | await this.erc721a.connect(this.owner).transferFrom(this.owner.address, this.addr1.address, 1);
55 | await this.erc721a.connect(this.addr1).transferFrom(this.addr1.address, this.owner.address, 1);
56 | }
57 | });
58 |
59 | it('transferTen ascending order', async function () {
60 | await this.erc721a.connect(this.owner).transferTenAsc(this.addr1.address);
61 | });
62 |
63 | it('transferTen descending order', async function () {
64 | await this.erc721a.connect(this.owner).transferTenDesc(this.addr1.address);
65 | });
66 |
67 | it('transferTen average order', async function () {
68 | await this.erc721a.connect(this.owner).transferTenAvg(this.addr1.address);
69 | });
70 | });
71 |
72 | it('mintOneERC2309', async function () {
73 | // The following call `_mintERC3201` outside of contract creation.
74 | // This is non-compliant with the ERC721 standard,
75 | // and is only meant for gas comparisons.
76 | let args = ['Azuki', 'AZUKI', this.owner.address, 0, false];
77 | let contract = await deployContract('ERC721AWithERC2309Mock', args);
78 | await contract.mintOneERC2309(this.owner.address);
79 | await contract.mintOneERC2309(this.owner.address);
80 | await contract.mintOneERC2309(this.addr1.address);
81 | });
82 |
83 | it('mintTenERC2309', async function () {
84 | // The following call `_mintERC3201` outside of contract creation.
85 | // This is non-compliant with the ERC721 standard,
86 | // and is only meant for gas comparisons.
87 | let args = ['Azuki', 'AZUKI', this.owner.address, 0, false];
88 | let contract = await deployContract('ERC721AWithERC2309Mock', args);
89 | await contract.mintTenERC2309(this.owner.address);
90 | await contract.mintTenERC2309(this.owner.address);
91 | await contract.mintTenERC2309(this.addr1.address);
92 | });
93 | });
94 |
--------------------------------------------------------------------------------
/test/extensions/ERC4907A.test.js:
--------------------------------------------------------------------------------
1 | const { deployContract, getBlockTimestamp, mineBlockTimestamp } = require('../helpers.js');
2 | const { expect } = require('chai');
3 | const { constants } = require('@openzeppelin/test-helpers');
4 | const { ZERO_ADDRESS } = constants;
5 |
6 | const createTestSuite = ({ contract, constructorArgs }) =>
7 | function () {
8 | context(`${contract}`, function () {
9 | beforeEach(async function () {
10 | this.erc4097a = await deployContract(contract, constructorArgs);
11 | });
12 |
13 | describe('EIP-165 support', async function () {
14 | it('supports ERC165', async function () {
15 | expect(await this.erc4097a.supportsInterface('0x01ffc9a7')).to.eq(true);
16 | });
17 |
18 | it('supports IERC721', async function () {
19 | expect(await this.erc4097a.supportsInterface('0x80ac58cd')).to.eq(true);
20 | });
21 |
22 | it('supports ERC721Metadata', async function () {
23 | expect(await this.erc4097a.supportsInterface('0x5b5e139f')).to.eq(true);
24 | });
25 |
26 | it('supports ERC4907', async function () {
27 | expect(await this.erc4097a.supportsInterface('0xad092b5c')).to.eq(true);
28 | });
29 |
30 | it('does not support random interface', async function () {
31 | expect(await this.erc4097a.supportsInterface('0x00000042')).to.eq(false);
32 | });
33 | });
34 |
35 | context('with minted tokens', async function () {
36 | beforeEach(async function () {
37 | const [owner, addr1] = await ethers.getSigners();
38 | this.owner = owner;
39 | this.addr1 = addr1;
40 |
41 | await this.erc4097a['mint(address,uint256)'](this.owner.address, 1);
42 | await this.erc4097a['mint(address,uint256)'](this.addr1.address, 2);
43 |
44 | this.expires = (await getBlockTimestamp()) + 123;
45 | this.tokenId = 2;
46 | this.user = this.owner;
47 | });
48 |
49 | it('explicitUserOf returns zero address after minting', async function () {
50 | expect(await this.erc4097a.explicitUserOf(0)).to.equal(ZERO_ADDRESS);
51 | expect(await this.erc4097a.explicitUserOf(1)).to.equal(ZERO_ADDRESS);
52 | expect(await this.erc4097a.explicitUserOf(2)).to.equal(ZERO_ADDRESS);
53 | });
54 |
55 | it('userOf returns zero address after minting', async function () {
56 | expect(await this.erc4097a.userOf(0)).to.equal(ZERO_ADDRESS);
57 | expect(await this.erc4097a.userOf(1)).to.equal(ZERO_ADDRESS);
58 | expect(await this.erc4097a.userOf(2)).to.equal(ZERO_ADDRESS);
59 | });
60 |
61 | it('userExpires returns zero timestamp after minting', async function () {
62 | expect(await this.erc4097a.userExpires(0)).to.equal(0);
63 | expect(await this.erc4097a.userExpires(1)).to.equal(0);
64 | expect(await this.erc4097a.userExpires(2)).to.equal(0);
65 | });
66 |
67 | describe('setUser', async function () {
68 | beforeEach(async function () {
69 | this.setUser = async () => await this.erc4097a.connect(this.addr1)
70 | .setUser(this.tokenId, this.user.address, this.expires);
71 |
72 | this.setupAuthTest = async () => {
73 | this.tokenId = 0;
74 | await expect(this.setUser()).to.be.revertedWith('SetUserCallerNotOwnerNorApproved');
75 | };
76 | });
77 |
78 | it('correctly changes the return value of explicitUserOf', async function () {
79 | await this.setUser();
80 | expect(await this.erc4097a.explicitUserOf(this.tokenId)).to.equal(this.user.address);
81 | });
82 |
83 | it('correctly changes the return value of userOf', async function () {
84 | await this.setUser();
85 | expect(await this.erc4097a.userOf(this.tokenId)).to.equal(this.user.address);
86 | });
87 |
88 | it('correctly changes the return value of expires', async function () {
89 | await this.setUser();
90 | expect(await this.erc4097a.userExpires(this.tokenId)).to.equal(this.expires);
91 | });
92 |
93 | it('emits the UpdateUser event properly', async function () {
94 | await expect(await this.setUser())
95 | .to.emit(this.erc4097a, 'UpdateUser')
96 | .withArgs(this.tokenId, this.user.address, this.expires);
97 | });
98 |
99 | it('reverts for an invalid token', async function () {
100 | this.tokenId = 123;
101 | await expect(this.setUser()).to.be.revertedWith('OwnerQueryForNonexistentToken');
102 | });
103 |
104 | it('requires token ownership', async function () {
105 | await this.setupAuthTest();
106 | await this.erc4097a.transferFrom(this.owner.address, this.addr1.address, this.tokenId);
107 | await this.setUser();
108 | });
109 |
110 | it('requires token approval', async function () {
111 | await this.setupAuthTest();
112 | await this.erc4097a.approve(this.addr1.address, this.tokenId);
113 | await this.setUser();
114 | });
115 |
116 | it('requires operator approval', async function () {
117 | await this.setupAuthTest();
118 | await this.erc4097a.setApprovalForAll(this.addr1.address, 1);
119 | await this.setUser();
120 | });
121 | });
122 |
123 | describe('after expiry', async function () {
124 | beforeEach(async function () {
125 | await this.erc4097a.connect(this.addr1)
126 | .setUser(this.tokenId, this.user.address, this.expires);
127 | });
128 |
129 | it('userOf returns zero address after expires', async function () {
130 | expect(await this.erc4097a.userOf(this.tokenId)).to.equal(this.user.address);
131 | await mineBlockTimestamp(this.expires);
132 | expect(await this.erc4097a.userOf(this.tokenId)).to.equal(this.user.address);
133 | await mineBlockTimestamp(this.expires + 1);
134 | expect(await this.erc4097a.userOf(this.tokenId)).to.equal(ZERO_ADDRESS);
135 | });
136 |
137 | it('explicitUserOf returns correct address after expiry', async function () {
138 | expect(await this.erc4097a.explicitUserOf(this.tokenId)).to.equal(this.user.address);
139 | await mineBlockTimestamp(this.expires);
140 | expect(await this.erc4097a.explicitUserOf(this.tokenId)).to.equal(this.user.address);
141 | await mineBlockTimestamp(this.expires + 1);
142 | expect(await this.erc4097a.explicitUserOf(this.tokenId)).to.equal(this.user.address);
143 | });
144 | });
145 | });
146 | });
147 | };
148 |
149 | describe(
150 | 'ERC4907A',
151 | createTestSuite({
152 | contract: 'ERC4907AMock',
153 | constructorArgs: ['Azuki', 'AZUKI'],
154 | })
155 | );
156 |
--------------------------------------------------------------------------------
/test/extensions/ERC721ABurnable.test.js:
--------------------------------------------------------------------------------
1 | const { deployContract, getBlockTimestamp, mineBlockTimestamp, offsettedIndex } = require('../helpers.js');
2 | const { expect } = require('chai');
3 | const { constants } = require('@openzeppelin/test-helpers');
4 | const { ZERO_ADDRESS } = constants;
5 |
6 | const createTestSuite = ({ contract, constructorArgs }) =>
7 | function () {
8 | let offsetted;
9 |
10 | context(`${contract}`, function () {
11 | beforeEach(async function () {
12 | this.erc721aBurnable = await deployContract(contract, constructorArgs);
13 |
14 | this.startTokenId = this.erc721aBurnable.startTokenId
15 | ? (await this.erc721aBurnable.startTokenId()).toNumber()
16 | : 0;
17 |
18 | offsetted = (...arr) => offsettedIndex(this.startTokenId, arr);
19 | });
20 |
21 | beforeEach(async function () {
22 | const [owner, addr1, addr2, spender] = await ethers.getSigners();
23 | this.owner = owner;
24 | this.addr1 = addr1;
25 | this.addr2 = addr2;
26 | this.spender = spender;
27 | this.numTestTokens = 10;
28 | this.burnedTokenId = 5;
29 | this.notBurnedTokenId = 6;
30 | await this.erc721aBurnable['safeMint(address,uint256)'](this.addr1.address, this.numTestTokens);
31 | await this.erc721aBurnable.connect(this.addr1).burn(this.burnedTokenId);
32 | });
33 |
34 | context('totalSupply()', function () {
35 | it('has the expected value', async function () {
36 | expect(await this.erc721aBurnable.totalSupply()).to.equal(9);
37 | });
38 |
39 | it('is reduced by burns', async function () {
40 | const supplyBefore = await this.erc721aBurnable.totalSupply();
41 |
42 | for (let i = 0; i < offsetted(2); ++i) {
43 | await this.erc721aBurnable.connect(this.addr1).burn(offsetted(i));
44 |
45 | const supplyNow = await this.erc721aBurnable.totalSupply();
46 | expect(supplyNow).to.equal(supplyBefore - (i + 1));
47 | }
48 | });
49 | });
50 |
51 | it('changes numberBurned', async function () {
52 | expect(await this.erc721aBurnable.numberBurned(this.addr1.address)).to.equal(1);
53 | await this.erc721aBurnable.connect(this.addr1).burn(offsetted(0));
54 | expect(await this.erc721aBurnable.numberBurned(this.addr1.address)).to.equal(2);
55 | });
56 |
57 | it('changes totalBurned', async function () {
58 | const totalBurnedBefore = (await this.erc721aBurnable.totalBurned()).toNumber();
59 | expect(totalBurnedBefore).to.equal(1);
60 |
61 | for (let i = 0; i < offsetted(2); ++i) {
62 | await this.erc721aBurnable.connect(this.addr1).burn(offsetted(i));
63 |
64 | const totalBurnedNow = (await this.erc721aBurnable.totalBurned()).toNumber();
65 | expect(totalBurnedNow).to.equal(totalBurnedBefore + (i + 1));
66 | }
67 | });
68 |
69 | it('changes exists', async function () {
70 | expect(await this.erc721aBurnable.exists(this.burnedTokenId)).to.be.false;
71 | expect(await this.erc721aBurnable.exists(this.notBurnedTokenId)).to.be.true;
72 | });
73 |
74 | it('cannot burn a non-existing token', async function () {
75 | const query = this.erc721aBurnable.connect(this.addr1).burn(offsetted(this.numTestTokens));
76 | await expect(query).to.be.revertedWith('OwnerQueryForNonexistentToken');
77 | });
78 |
79 | it('cannot burn a burned token', async function () {
80 | const query = this.erc721aBurnable.connect(this.addr1).burn(this.burnedTokenId);
81 | await expect(query).to.be.revertedWith('OwnerQueryForNonexistentToken');
82 | });
83 |
84 | it('cannot burn with wrong caller or spender', async function () {
85 | const tokenIdToBurn = this.notBurnedTokenId;
86 |
87 | // sanity check
88 | await this.erc721aBurnable.connect(this.addr1).approve(ZERO_ADDRESS, tokenIdToBurn);
89 | await this.erc721aBurnable.connect(this.addr1).setApprovalForAll(this.spender.address, false);
90 |
91 | const query = this.erc721aBurnable.connect(this.spender).burn(tokenIdToBurn);
92 | await expect(query).to.be.revertedWith('TransferCallerNotOwnerNorApproved');
93 | });
94 |
95 | it('spender can burn with specific approved tokenId', async function () {
96 | const tokenIdToBurn = this.notBurnedTokenId;
97 |
98 | await this.erc721aBurnable.connect(this.addr1).approve(this.spender.address, tokenIdToBurn);
99 | await this.erc721aBurnable.connect(this.spender).burn(tokenIdToBurn);
100 | expect(await this.erc721aBurnable.exists(tokenIdToBurn)).to.be.false;
101 | });
102 |
103 | it('spender can burn with one-time approval', async function () {
104 | const tokenIdToBurn = this.notBurnedTokenId;
105 |
106 | await this.erc721aBurnable.connect(this.addr1).setApprovalForAll(this.spender.address, true);
107 | await this.erc721aBurnable.connect(this.spender).burn(tokenIdToBurn);
108 | expect(await this.erc721aBurnable.exists(tokenIdToBurn)).to.be.false;
109 | });
110 |
111 | it('cannot transfer a burned token', async function () {
112 | const query = this.erc721aBurnable
113 | .connect(this.addr1)
114 | .transferFrom(this.addr1.address, this.addr2.address, this.burnedTokenId);
115 | await expect(query).to.be.revertedWith('OwnerQueryForNonexistentToken');
116 | });
117 |
118 | it('does not affect _totalMinted', async function () {
119 | const totalMintedBefore = await this.erc721aBurnable.totalMinted();
120 | expect(totalMintedBefore).to.equal(this.numTestTokens);
121 | for (let i = 0; i < 2; ++i) {
122 | await this.erc721aBurnable.connect(this.addr1).burn(offsetted(i));
123 | }
124 | expect(await this.erc721aBurnable.totalMinted()).to.equal(totalMintedBefore);
125 | });
126 |
127 | it('adjusts owners balances', async function () {
128 | expect(await this.erc721aBurnable.balanceOf(this.addr1.address)).to.be.equal(this.numTestTokens - 1);
129 | });
130 |
131 | it('startTimestamp updated correctly', async function () {
132 | const tokenIdToBurn = this.burnedTokenId + 1;
133 | const ownershipBefore = await this.erc721aBurnable.getOwnershipAt(tokenIdToBurn);
134 | const timestampBefore = parseInt(ownershipBefore.startTimestamp);
135 | const timestampToMine = (await getBlockTimestamp()) + 12345;
136 | await mineBlockTimestamp(timestampToMine);
137 | const timestampMined = await getBlockTimestamp();
138 | await this.erc721aBurnable.connect(this.addr1).burn(tokenIdToBurn);
139 | const ownershipAfter = await this.erc721aBurnable.getOwnershipAt(tokenIdToBurn);
140 | const timestampAfter = parseInt(ownershipAfter.startTimestamp);
141 | expect(timestampBefore).to.be.lt(timestampToMine);
142 | expect(timestampAfter).to.be.gte(timestampToMine);
143 | expect(timestampAfter).to.be.lt(timestampToMine + 10);
144 | expect(timestampToMine).to.be.eq(timestampMined);
145 | });
146 |
147 | describe('ownerships correctly set', async function () {
148 | it('with token before previously burnt token transferred and burned', async function () {
149 | const tokenIdToBurn = this.burnedTokenId - 1;
150 | await this.erc721aBurnable
151 | .connect(this.addr1)
152 | .transferFrom(this.addr1.address, this.addr2.address, tokenIdToBurn);
153 | expect(await this.erc721aBurnable.ownerOf(tokenIdToBurn)).to.be.equal(this.addr2.address);
154 | await this.erc721aBurnable.connect(this.addr2).burn(tokenIdToBurn);
155 | for (let i = offsetted(0); i < offsetted(this.numTestTokens); ++i) {
156 | if (i == tokenIdToBurn || i == this.burnedTokenId) {
157 | await expect(this.erc721aBurnable.ownerOf(i)).to.be.revertedWith('OwnerQueryForNonexistentToken');
158 | } else {
159 | expect(await this.erc721aBurnable.ownerOf(i)).to.be.equal(this.addr1.address);
160 | }
161 | }
162 | });
163 |
164 | it('with token after previously burnt token transferred and burned', async function () {
165 | const tokenIdToBurn = this.burnedTokenId + 1;
166 | await this.erc721aBurnable
167 | .connect(this.addr1)
168 | .transferFrom(this.addr1.address, this.addr2.address, tokenIdToBurn);
169 | expect(await this.erc721aBurnable.ownerOf(tokenIdToBurn)).to.be.equal(this.addr2.address);
170 | await this.erc721aBurnable.connect(this.addr2).burn(tokenIdToBurn);
171 | for (let i = offsetted(0); i < offsetted(this.numTestTokens); ++i) {
172 | if (i == tokenIdToBurn || i == this.burnedTokenId) {
173 | await expect(this.erc721aBurnable.ownerOf(i)).to.be.revertedWith('OwnerQueryForNonexistentToken');
174 | } else {
175 | expect(await this.erc721aBurnable.ownerOf(i)).to.be.equal(this.addr1.address);
176 | }
177 | }
178 | });
179 |
180 | it('with first token burned', async function () {
181 | await this.erc721aBurnable.connect(this.addr1).burn(offsetted(0));
182 | for (let i = offsetted(0); i < offsetted(this.numTestTokens); ++i) {
183 | if (i == offsetted(0).toNumber() || i == this.burnedTokenId) {
184 | await expect(this.erc721aBurnable.ownerOf(i)).to.be.revertedWith('OwnerQueryForNonexistentToken');
185 | } else {
186 | expect(await this.erc721aBurnable.ownerOf(i)).to.be.equal(this.addr1.address);
187 | }
188 | }
189 | });
190 |
191 | it('with last token burned', async function () {
192 | await expect(this.erc721aBurnable.ownerOf(offsetted(this.numTestTokens))).to.be.revertedWith(
193 | 'OwnerQueryForNonexistentToken'
194 | );
195 | await this.erc721aBurnable.connect(this.addr1).burn(offsetted(this.numTestTokens - 1));
196 | await expect(this.erc721aBurnable.ownerOf(offsetted(this.numTestTokens - 1))).to.be.revertedWith(
197 | 'OwnerQueryForNonexistentToken'
198 | );
199 | });
200 | });
201 | });
202 | };
203 |
204 | describe('ERC721ABurnable', createTestSuite({ contract: 'ERC721ABurnableMock', constructorArgs: ['Azuki', 'AZUKI'] }));
205 |
206 | describe(
207 | 'ERC721ABurnable override _startTokenId()',
208 | createTestSuite({ contract: 'ERC721ABurnableStartTokenIdMock', constructorArgs: ['Azuki', 'AZUKI', 1] })
209 | );
210 |
--------------------------------------------------------------------------------
/test/extensions/ERC721AQueryable.test.js:
--------------------------------------------------------------------------------
1 | const { deployContract, offsettedIndex } = require('../helpers.js');
2 | const { expect } = require('chai');
3 | const { BigNumber } = require('ethers');
4 | const { constants } = require('@openzeppelin/test-helpers');
5 | const { ZERO_ADDRESS } = constants;
6 |
7 | const createTestSuite = ({ contract, constructorArgs }) =>
8 | function () {
9 | let offsetted;
10 |
11 | context(`${contract}`, function () {
12 | beforeEach(async function () {
13 | this.erc721aQueryable = await deployContract(contract, constructorArgs);
14 |
15 | this.startTokenId = this.erc721aQueryable.startTokenId
16 | ? (await this.erc721aQueryable.startTokenId()).toNumber()
17 | : 0;
18 |
19 | offsetted = (...arr) => offsettedIndex(this.startTokenId, arr);
20 | });
21 |
22 | const expectExplicitOwnershipBurned = function (explicitOwnership, address) {
23 | expect(explicitOwnership.burned).to.eql(true);
24 | expect(explicitOwnership.addr).to.eql(address);
25 | expect(explicitOwnership.startTimestamp).to.not.eql(BigNumber.from(0));
26 | expect(explicitOwnership.extraData).to.equal(BigNumber.from(0));
27 | };
28 |
29 | const expectExplicitOwnershipNotExists = function (explicitOwnership) {
30 | expect(explicitOwnership.burned).to.eql(false);
31 | expect(explicitOwnership.addr).to.eql(ZERO_ADDRESS);
32 | expect(explicitOwnership.startTimestamp).to.eql(BigNumber.from(0));
33 | expect(explicitOwnership.extraData).to.equal(BigNumber.from(0));
34 | };
35 |
36 | const expectExplicitOwnershipExists = function (explicitOwnership, address) {
37 | expect(explicitOwnership.burned).to.eql(false);
38 | expect(explicitOwnership.addr).to.eql(address);
39 | expect(explicitOwnership.startTimestamp).to.not.eql(BigNumber.from(0));
40 | expect(explicitOwnership.extraData).to.equal(BigNumber.from(0));
41 | };
42 |
43 | context('with no minted tokens', async function () {
44 | beforeEach(async function () {
45 | const [owner, addr1] = await ethers.getSigners();
46 | this.owner = owner;
47 | this.addr1 = addr1;
48 | });
49 |
50 | describe('tokensOfOwner', async function () {
51 | it('returns empty array', async function () {
52 | expect(await this.erc721aQueryable.tokensOfOwner(this.owner.address)).to.eql([]);
53 | expect(await this.erc721aQueryable.tokensOfOwner(this.addr1.address)).to.eql([]);
54 | });
55 | });
56 |
57 | describe('tokensOfOwnerIn', async function () {
58 | it('returns empty array', async function () {
59 | expect(await this.erc721aQueryable.tokensOfOwnerIn(this.owner.address, 0, 9)).to.eql([]);
60 | expect(await this.erc721aQueryable.tokensOfOwnerIn(this.addr1.address, 0, 9)).to.eql([]);
61 | });
62 | });
63 |
64 | describe('explicitOwnershipOf', async function () {
65 | it('returns empty struct', async function () {
66 | expectExplicitOwnershipNotExists(await this.erc721aQueryable.explicitOwnershipOf(0));
67 | expectExplicitOwnershipNotExists(await this.erc721aQueryable.explicitOwnershipOf(1));
68 | });
69 | });
70 |
71 | describe('explicitOwnershipsOf', async function () {
72 | it('returns empty structs', async function () {
73 | const tokenIds = [0, 1, 2, 3];
74 | const explicitOwnerships = await this.erc721aQueryable.explicitOwnershipsOf(tokenIds);
75 | for (let i = 0; i < explicitOwnerships.length; ++i) {
76 | expectExplicitOwnershipNotExists(explicitOwnerships[i]);
77 | }
78 | });
79 | });
80 | });
81 |
82 | context('with minted tokens', async function () {
83 | beforeEach(async function () {
84 | const [owner, addr1, addr2, addr3, addr4] = await ethers.getSigners();
85 | this.owner = owner;
86 | this.addr1 = addr1;
87 | this.addr2 = addr2;
88 | this.addr3 = addr3;
89 | this.addr4 = addr4;
90 |
91 | this.addr1.expected = {
92 | balance: 1,
93 | tokens: [offsetted(0)],
94 | };
95 |
96 | this.addr2.expected = {
97 | balance: 2,
98 | tokens: offsetted(1, 2),
99 | };
100 |
101 | this.addr3.expected = {
102 | balance: 3,
103 | tokens: offsetted(3, 4, 5),
104 | };
105 |
106 | this.addr4.expected = {
107 | balance: 0,
108 | tokens: [],
109 | };
110 |
111 | this.owner.expected = {
112 | balance: 3,
113 | tokens: offsetted(6, 7, 8),
114 | };
115 |
116 | this.lastTokenId = offsetted(8);
117 | this.currentIndex = this.lastTokenId.add(1);
118 |
119 | this.mintOrder = [this.addr1, this.addr2, this.addr3, this.addr4, owner];
120 |
121 | for (const minter of this.mintOrder) {
122 | const balance = minter.expected.balance;
123 | if (balance > 0) {
124 | await this.erc721aQueryable['safeMint(address,uint256)'](minter.address, balance);
125 | }
126 | // sanity check
127 | expect(await this.erc721aQueryable.balanceOf(minter.address)).to.equal(minter.expected.balance);
128 | }
129 | });
130 |
131 | describe('tokensOfOwner', async function () {
132 | it('initial', async function () {
133 | for (const minter of this.mintOrder) {
134 | const tokens = await this.erc721aQueryable.tokensOfOwner(minter.address);
135 | expect(tokens).to.eql(minter.expected.tokens);
136 | }
137 | });
138 |
139 | it('after a transfer', async function () {
140 | // Break sequential order by transfering 7th token from owner to addr4
141 | const tokenIdToTransfer = [offsetted(7)];
142 | await this.erc721aQueryable.transferFrom(this.owner.address, this.addr4.address, tokenIdToTransfer[0]);
143 |
144 | // Load balances
145 | const ownerTokens = await this.erc721aQueryable.tokensOfOwner(this.owner.address);
146 | const addr4Tokens = await this.erc721aQueryable.tokensOfOwner(this.addr4.address);
147 |
148 | // Verify the function can still read the correct token ids
149 | expect(ownerTokens).to.eql(offsetted(6, 8));
150 | expect(addr4Tokens).to.eql(tokenIdToTransfer);
151 | });
152 |
153 | it('after a burn', async function () {
154 | // Burn tokens
155 | const tokenIdToBurn = [offsetted(7)];
156 | await this.erc721aQueryable.burn(tokenIdToBurn[0]);
157 |
158 | // Load balances
159 | const ownerTokens = await this.erc721aQueryable.tokensOfOwner(this.owner.address);
160 |
161 | // Verify the function can still read the correct token ids
162 | expect(ownerTokens).to.eql(offsetted(6, 8));
163 | });
164 |
165 | it('with direct set burn bit', async function () {
166 | await this.erc721aQueryable['safeMint(address,uint256)'](this.addr3.address, 3);
167 | await this.erc721aQueryable['safeMint(address,uint256)'](this.addr3.address, 2);
168 | const nextTokenId = this.owner.expected.tokens[this.owner.expected.tokens.length - 1].add(1);
169 | expect(await this.erc721aQueryable.tokensOfOwner(this.addr3.address))
170 | .to.eql(this.addr3.expected.tokens.concat(Array.from({length: 5}, (_, i) => nextTokenId.add(i))));
171 | await this.erc721aQueryable.directSetBurnBit(nextTokenId);
172 | expect(await this.erc721aQueryable.tokensOfOwner(this.addr3.address))
173 | .to.eql(this.addr3.expected.tokens.concat(Array.from({length: 2}, (_, i) => nextTokenId.add(3 + i))));
174 | });
175 | });
176 |
177 | describe('tokensOfOwnerIn', async function () {
178 | const expectCorrect = async function (addr, start, stop) {
179 | if (BigNumber.from(start).gte(BigNumber.from(stop))) {
180 | await expect(this.erc721aQueryable.tokensOfOwnerIn(addr, start, stop)).to.be.revertedWith(
181 | 'InvalidQueryRange'
182 | );
183 | } else {
184 | const expectedTokens = (await this.erc721aQueryable.tokensOfOwner(addr)).filter(
185 | (x) => BigNumber.from(start).lte(x) && BigNumber.from(stop).gt(x)
186 | );
187 | const tokens = await this.erc721aQueryable.tokensOfOwnerIn(addr, start, stop);
188 | expect(tokens).to.eql(expectedTokens);
189 | }
190 | };
191 |
192 | const subTests = function (description, beforeEachFunction) {
193 | describe(description, async function () {
194 | it('all token ids', async function () {
195 | await beforeEachFunction.call(this);
196 | await expectCorrect.call(this, this.owner.address, offsetted(0), this.currentIndex);
197 | await expectCorrect.call(this, this.owner.address, offsetted(0), this.currentIndex.add(1));
198 | });
199 |
200 | it('partial token ids', async function () {
201 | await beforeEachFunction.call(this);
202 | const ownerTokens = this.owner.expected.tokens;
203 | const start = ownerTokens[0];
204 | const stop = ownerTokens[ownerTokens.length - 1] + 1;
205 | for (let o = 1; o <= ownerTokens.length; ++o) {
206 | // Start truncated.
207 | await expectCorrect.call(this, this.owner.address, start + o, stop);
208 | // End truncated.
209 | await expectCorrect.call(this, this.owner.address, start, stop - o);
210 | // Start and end truncated. This also tests for start + o >= stop - o.
211 | await expectCorrect.call(this, this.owner.address, start + o, stop - o);
212 | }
213 | for (let l = 0; l < ownerTokens.length; ++l) {
214 | for (let o = 0, n = parseInt(this.currentIndex) + 1; o <= n; ++o) {
215 | // Sliding window.
216 | await expectCorrect.call(this, this.owner.address, o, o + l);
217 | }
218 | }
219 | });
220 | });
221 | };
222 |
223 | subTests('initial', async function () {});
224 |
225 | subTests('after a token tranfer', async function () {
226 | await this.erc721aQueryable.transferFrom(this.owner.address, this.addr4.address, offsetted(7));
227 | });
228 |
229 | subTests('after a token burn', async function () {
230 | await this.erc721aQueryable.burn(offsetted(7));
231 | });
232 |
233 | it('with direct set burn bit', async function () {
234 | await this.erc721aQueryable['safeMint(address,uint256)'](this.addr3.address, 3);
235 | await this.erc721aQueryable['safeMint(address,uint256)'](this.addr3.address, 2);
236 | const nextTokenId = this.owner.expected.tokens[this.owner.expected.tokens.length - 1].add(1);
237 | expect(await this.erc721aQueryable.tokensOfOwnerIn(this.addr3.address, 0, 99))
238 | .to.eql(this.addr3.expected.tokens.concat(Array.from({length: 5}, (_, i) => nextTokenId.add(i))));
239 | await this.erc721aQueryable.directSetBurnBit(nextTokenId);
240 | expect(await this.erc721aQueryable.tokensOfOwnerIn(this.addr3.address, 0, 99))
241 | .to.eql(this.addr3.expected.tokens.concat(Array.from({length: 2}, (_, i) => nextTokenId.add(3 + i))));
242 | })
243 | });
244 |
245 | describe('explicitOwnershipOf', async function () {
246 | it('token exists', async function () {
247 | const tokenId = this.owner.expected.tokens[0];
248 | const explicitOwnership = await this.erc721aQueryable.explicitOwnershipOf(tokenId);
249 | expectExplicitOwnershipExists(explicitOwnership, this.owner.address);
250 | });
251 |
252 | it('after a token burn', async function () {
253 | const tokenId = this.owner.expected.tokens[0];
254 | await this.erc721aQueryable.burn(tokenId);
255 | const explicitOwnership = await this.erc721aQueryable.explicitOwnershipOf(tokenId);
256 | expectExplicitOwnershipBurned(explicitOwnership, this.owner.address);
257 | });
258 |
259 | it('after a token transfer', async function () {
260 | const tokenId = this.owner.expected.tokens[0];
261 | await this.erc721aQueryable.transferFrom(this.owner.address, this.addr4.address, tokenId);
262 | const explicitOwnership = await this.erc721aQueryable.explicitOwnershipOf(tokenId);
263 | expectExplicitOwnershipExists(explicitOwnership, this.addr4.address);
264 | });
265 |
266 | it('out of bounds', async function () {
267 | const explicitOwnership = await this.erc721aQueryable.explicitOwnershipOf(this.currentIndex);
268 | expectExplicitOwnershipNotExists(explicitOwnership);
269 | });
270 |
271 | it('with direct set burn bit', async function () {
272 | await this.erc721aQueryable.directSetBurnBit(this.addr3.expected.tokens[0]);
273 | for (let i = 0; i < this.addr3.expected.tokens.length; ++i) {
274 | const explicitOwnership = await this.erc721aQueryable.explicitOwnershipOf(this.addr3.expected.tokens[i]);
275 | expectExplicitOwnershipBurned(explicitOwnership, this.addr3.address);
276 | }
277 | });
278 | });
279 |
280 | describe('explicitOwnershipsOf', async function () {
281 | it('tokens exist', async function () {
282 | const tokenIds = [].concat(this.owner.expected.tokens, this.addr3.expected.tokens);
283 | const explicitOwnerships = await this.erc721aQueryable.explicitOwnershipsOf(tokenIds);
284 | for (let i = 0; i < tokenIds.length; ++i) {
285 | const owner = await this.erc721aQueryable.ownerOf(tokenIds[i]);
286 | expectExplicitOwnershipExists(explicitOwnerships[i], owner);
287 | }
288 | });
289 |
290 | it('after a token burn', async function () {
291 | const tokenIds = [].concat(this.owner.expected.tokens, this.addr3.expected.tokens);
292 | await this.erc721aQueryable.burn(tokenIds[0]);
293 | const explicitOwnerships = await this.erc721aQueryable.explicitOwnershipsOf(tokenIds);
294 | expectExplicitOwnershipBurned(explicitOwnerships[0], this.owner.address);
295 | for (let i = 1; i < tokenIds.length; ++i) {
296 | const owner = await this.erc721aQueryable.ownerOf(tokenIds[i]);
297 | expectExplicitOwnershipExists(explicitOwnerships[i], owner);
298 | }
299 | });
300 |
301 | it('after a token transfer', async function () {
302 | const tokenIds = [].concat(this.owner.expected.tokens, this.addr3.expected.tokens);
303 | await this.erc721aQueryable.transferFrom(this.owner.address, this.addr4.address, tokenIds[0]);
304 | const explicitOwnerships = await this.erc721aQueryable.explicitOwnershipsOf(tokenIds);
305 | expectExplicitOwnershipExists(explicitOwnerships[0], this.addr4.address);
306 | for (let i = 1; i < tokenIds.length; ++i) {
307 | const owner = await this.erc721aQueryable.ownerOf(tokenIds[i]);
308 | expectExplicitOwnershipExists(explicitOwnerships[i], owner);
309 | }
310 | });
311 |
312 | it('out of bounds', async function () {
313 | const tokenIds = [].concat([this.currentIndex], this.addr3.expected.tokens);
314 | const explicitOwnerships = await this.erc721aQueryable.explicitOwnershipsOf(tokenIds);
315 | expectExplicitOwnershipNotExists(explicitOwnerships[0]);
316 | for (let i = 1; i < tokenIds.length; ++i) {
317 | const owner = await this.erc721aQueryable.ownerOf(tokenIds[i]);
318 | expectExplicitOwnershipExists(explicitOwnerships[i], owner);
319 | }
320 | });
321 | });
322 | });
323 | });
324 | };
325 |
326 | describe(
327 | 'ERC721AQueryable',
328 | createTestSuite({
329 | contract: 'ERC721AQueryableMock',
330 | constructorArgs: ['Azuki', 'AZUKI'],
331 | })
332 | );
333 |
334 | describe(
335 | 'ERC721AQueryable override _startTokenId()',
336 | createTestSuite({
337 | contract: 'ERC721AQueryableStartTokenIdMock',
338 | constructorArgs: ['Azuki', 'AZUKI', 1],
339 | })
340 | );
341 |
--------------------------------------------------------------------------------
/test/extensions/ERC721ASpot.test.js:
--------------------------------------------------------------------------------
1 | const { deployContract } = require('../helpers.js');
2 | const { expect } = require('chai');
3 | const { BigNumber } = require('ethers');
4 | const { constants } = require('@openzeppelin/test-helpers');
5 | const { ZERO_ADDRESS } = constants;
6 |
7 | describe('ERC721ASpot', function () {
8 |
9 | context('constructor', function () {
10 | const testConstructor = async (args, expectedError) => {
11 | const deployment = deployContract('ERC721ASpotMock', args);
12 | if (expectedError) await expect(deployment).to.be.revertedWith(expectedError);
13 | else await deployment;
14 | };
15 |
16 | it('reverts if _sequentialUpTo is not greater than _startTokenId', async function () {
17 | const t = async (startTokenId, sequentialUpTo, expectSuccess) => {
18 | await testConstructor(
19 | ['Azuki', 'AZUKI', startTokenId, sequentialUpTo, 0, false],
20 | expectSuccess ? false : 'SequentialUpToTooSmall'
21 | );
22 | };
23 | await t(0, 0, true);
24 | await t(1, 0, false);
25 | await t(0, 1, true);
26 | await t(100, 99, false);
27 | await t(100, 100, true);
28 | await t(100, 101, true);
29 | await t(100, 999, true);
30 | });
31 |
32 | it('reverts if ERC2309 mint exceeds limit', async function () {
33 | const t = async (startTokenId, sequentialUpTo, quantity, expectSuccess) => {
34 | await testConstructor(
35 | ['Azuki', 'AZUKI', startTokenId, sequentialUpTo, quantity, true],
36 | expectSuccess ? false : 'SequentialMintExceedsLimit'
37 | );
38 | };
39 | await t(0, 1, 1, true);
40 | await t(0, 1, 2, true);
41 | await t(0, 1, 3, false);
42 | await t(100, 101, 1, true);
43 | await t(100, 101, 2, true);
44 | await t(100, 101, 3, false);
45 | await t(100, 109, 2, true);
46 | await t(100, 109, 9, true);
47 | await t(100, 109, 10, true);
48 | await t(100, 109, 11, false);
49 | });
50 | });
51 |
52 | context('mint sequential and spot', function () {
53 | beforeEach(async function () {
54 | const [owner, addr1] = await ethers.getSigners();
55 | this.owner = owner;
56 | this.addr1 = addr1;
57 | this.startTokenId = BigNumber.from(10);
58 | this.sequentialUpTo = BigNumber.from(19);
59 | const args = ['Azuki', 'AZUKI', this.startTokenId, this.sequentialUpTo, 0, false];
60 | this.erc721aSpot = await deployContract('ERC721ASpotMock', args);
61 | });
62 |
63 | it('_mintSpot emits a Transfer event', async function () {
64 | await expect(this.erc721aSpot.safeMintSpot(this.addr1.address, 20))
65 | .to.emit(this.erc721aSpot, 'Transfer')
66 | .withArgs(ZERO_ADDRESS, this.addr1.address, 20);
67 | });
68 |
69 | it('increases _totalSpotMinted, totalSupply', async function () {
70 | await this.erc721aSpot.safeMint(this.addr1.address, 5);
71 | expect(await this.erc721aSpot.totalSpotMinted()).to.eq(0);
72 | expect(await this.erc721aSpot.totalSupply()).to.eq(5);
73 |
74 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 20);
75 | expect(await this.erc721aSpot.totalSpotMinted()).to.eq(1);
76 | expect(await this.erc721aSpot.totalSupply()).to.eq(6);
77 |
78 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 30);
79 | expect(await this.erc721aSpot.totalSpotMinted()).to.eq(2);
80 | expect(await this.erc721aSpot.totalSupply()).to.eq(7);
81 | });
82 |
83 | it('tokensOfOwnerIn', async function () {
84 | expect(await this.erc721aSpot.tokensOfOwnerIn(this.addr1.address, 0, 4294967295)).to.eql([]);
85 |
86 | await this.erc721aSpot.safeMint(this.addr1.address, 5);
87 | expect(await this.erc721aSpot.tokensOfOwnerIn(this.addr1.address, 0, 4294967295))
88 | .to.eql([10, 11, 12, 13, 14].map(BigNumber.from));
89 |
90 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 21);
91 | expect(await this.erc721aSpot.tokensOfOwnerIn(this.addr1.address, 0, 4294967295))
92 | .to.eql([10, 11, 12, 13, 14, 21].map(BigNumber.from));
93 |
94 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 31);
95 | expect(await this.erc721aSpot.tokensOfOwnerIn(this.addr1.address, 0, 4294967295))
96 | .to.eql([10, 11, 12, 13, 14, 21, 31].map(BigNumber.from));
97 |
98 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 22);
99 | expect(await this.erc721aSpot.tokensOfOwnerIn(this.addr1.address, 0, 4294967295))
100 | .to.eql([10, 11, 12, 13, 14, 21, 22, 31].map(BigNumber.from));
101 |
102 | await this.erc721aSpot.safeMint(this.addr1.address, 5);
103 | expect(await this.erc721aSpot.tokensOfOwnerIn(this.addr1.address, 0, 4294967295))
104 | .to.eql([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 31].map(BigNumber.from));
105 |
106 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 20);
107 | expect(await this.erc721aSpot.tokensOfOwnerIn(this.addr1.address, 0, 4294967295))
108 | .to.eql([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 31].map(BigNumber.from));
109 |
110 | expect(await this.erc721aSpot.tokensOfOwnerIn(this.addr1.address, 0, 32))
111 | .to.eql([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 31].map(BigNumber.from));
112 |
113 | expect(await this.erc721aSpot.tokensOfOwnerIn(this.addr1.address, 0, 31))
114 | .to.eql([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22].map(BigNumber.from));
115 | });
116 |
117 | it('explicitOwnershipOf', async function () {
118 | let explicitOwnership = await this.erc721aSpot.explicitOwnershipOf(10);
119 | expect(explicitOwnership.addr).to.eq(ZERO_ADDRESS);
120 | expect(explicitOwnership.burned).to.eq(false);
121 |
122 | await this.erc721aSpot.safeMint(this.addr1.address, 1);
123 | explicitOwnership = await this.erc721aSpot.explicitOwnershipOf(10);
124 | expect(explicitOwnership.addr).to.eq(this.addr1.address);
125 | expect(explicitOwnership.burned).to.eq(false);
126 |
127 | explicitOwnership = await this.erc721aSpot.explicitOwnershipOf(11);
128 | expect(explicitOwnership.addr).to.eq(ZERO_ADDRESS);
129 | expect(explicitOwnership.burned).to.eq(false);
130 |
131 | explicitOwnership = await this.erc721aSpot.explicitOwnershipOf(20);
132 | expect(explicitOwnership.addr).to.eq(ZERO_ADDRESS);
133 | expect(explicitOwnership.burned).to.eq(false);
134 |
135 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 20);
136 | explicitOwnership = await this.erc721aSpot.explicitOwnershipOf(20);
137 | expect(explicitOwnership.addr).to.eq(this.addr1.address);
138 | expect(explicitOwnership.burned).to.eq(false);
139 | });
140 |
141 | it('tokensOfOwner reverts', async function () {
142 | await expect(this.erc721aSpot.tokensOfOwner(this.addr1.address)).to.be.revertedWith(
143 | 'NotCompatibleWithSpotMints'
144 | );
145 | });
146 |
147 | it('spot minting to an existing token reverts', async function () {
148 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 20);
149 | await expect(this.erc721aSpot.safeMintSpot(this.addr1.address, 20)).to.be.revertedWith(
150 | 'TokenAlreadyExists'
151 | );
152 | });
153 |
154 | it('reverts if sequential mint exceeds limit', async function () {
155 | await expect(this.erc721aSpot.safeMint(this.addr1.address, 11)).to.be.revertedWith(
156 | 'SequentialMintExceedsLimit'
157 | );
158 | await this.erc721aSpot.safeMint(this.addr1.address, 10);
159 | });
160 |
161 | it('reverts if _mintSpot tokenId is too small', async function () {
162 | await expect(this.erc721aSpot.safeMintSpot(this.addr1.address, 19)).to.be.revertedWith(
163 | 'SpotMintTokenIdTooSmall'
164 | );
165 | });
166 |
167 | context('with transfers', function () {
168 | it('reverts if token is not minted', async function () {
169 | await this.erc721aSpot.safeMint(this.addr1.address, 10);
170 | await expect(this.erc721aSpot
171 | .connect(this.addr1)
172 | .transferFrom(this.addr1.address, this.owner.address, 21)).to.be.revertedWith(
173 | 'OwnerQueryForNonexistentToken'
174 | );
175 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 21);
176 | await this.erc721aSpot
177 | .connect(this.addr1)
178 | .transferFrom(this.addr1.address, this.owner.address, 21);
179 | });
180 |
181 | it('edge case 1', async function () {
182 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 20);
183 | await this.erc721aSpot.safeMint(this.addr1.address, 10);
184 | await this.erc721aSpot.connect(this.addr1).transferFrom(this.addr1.address, this.owner.address, 20);
185 | expect(await this.erc721aSpot.ownerOf(20)).to.eq(this.owner.address);
186 | expect(await this.erc721aSpot.ownerOf(19)).to.eq(this.addr1.address);
187 | expect(await this.erc721aSpot.ownerOf(18)).to.eq(this.addr1.address);
188 | await this.erc721aSpot.connect(this.addr1).transferFrom(this.addr1.address, this.owner.address, 19);
189 | expect(await this.erc721aSpot.ownerOf(20)).to.eq(this.owner.address);
190 | expect(await this.erc721aSpot.ownerOf(19)).to.eq(this.owner.address);
191 | expect(await this.erc721aSpot.ownerOf(18)).to.eq(this.addr1.address);
192 | });
193 |
194 | it('edge case 2', async function () {
195 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 20);
196 | await this.erc721aSpot.safeMint(this.addr1.address, 10);
197 | await this.erc721aSpot.connect(this.addr1).transferFrom(this.addr1.address, this.owner.address, 19);
198 | expect(await this.erc721aSpot.ownerOf(20)).to.eq(this.addr1.address);
199 | expect(await this.erc721aSpot.ownerOf(19)).to.eq(this.owner.address);
200 | expect(await this.erc721aSpot.ownerOf(18)).to.eq(this.addr1.address);
201 | await this.erc721aSpot.connect(this.addr1).transferFrom(this.addr1.address, this.owner.address, 20);
202 | expect(await this.erc721aSpot.ownerOf(20)).to.eq(this.owner.address);
203 | expect(await this.erc721aSpot.ownerOf(19)).to.eq(this.owner.address);
204 | expect(await this.erc721aSpot.ownerOf(18)).to.eq(this.addr1.address);
205 | });
206 | });
207 |
208 | context('with burns', function () {
209 | beforeEach(async function () {
210 | await this.erc721aSpot.safeMint(this.addr1.address, 5);
211 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 20);
212 | await this.erc721aSpot.safeMintSpot(this.addr1.address, 30);
213 | });
214 |
215 | it('sets ownership correctly', async function () {
216 | const t = async (tokenIds) => {
217 | for (let i = 0; i < 35; ++i) {
218 | const tx = this.erc721aSpot.getOwnershipOf(i);
219 | if (tokenIds.includes(i)) await tx;
220 | else await expect(tx).to.be.revertedWith('OwnerQueryForNonexistentToken');
221 | }
222 | };
223 | await t([10, 11, 12, 13, 14, 20, 30]);
224 | await this.erc721aSpot.connect(this.addr1).burn(20);
225 | await t([10, 11, 12, 13, 14, 30]);
226 | });
227 |
228 | it('reduces balanceOf, totalSupply', async function () {
229 | expect(await this.erc721aSpot.balanceOf(this.addr1.address)).to.eq(7);
230 | await this.erc721aSpot.connect(this.addr1).burn(10);
231 | expect(await this.erc721aSpot.balanceOf(this.addr1.address)).to.eq(6);
232 | expect(await this.erc721aSpot.totalSupply()).to.eq(6);
233 |
234 | await this.erc721aSpot.connect(this.addr1).burn(20);
235 | expect(await this.erc721aSpot.balanceOf(this.addr1.address)).to.eq(5);
236 | expect(await this.erc721aSpot.totalSupply()).to.eq(5);
237 |
238 | await this.erc721aSpot.connect(this.addr1).burn(30);
239 | expect(await this.erc721aSpot.balanceOf(this.addr1.address)).to.eq(4);
240 | expect(await this.erc721aSpot.totalSupply()).to.eq(4);
241 |
242 | await this.erc721aSpot.connect(this.addr1).burn(11);
243 | await this.erc721aSpot.connect(this.addr1).burn(12);
244 | await this.erc721aSpot.connect(this.addr1).burn(13);
245 | await this.erc721aSpot.connect(this.addr1).burn(14);
246 | expect(await this.erc721aSpot.balanceOf(this.addr1.address)).to.eq(0);
247 | expect(await this.erc721aSpot.totalSupply()).to.eq(0);
248 | });
249 |
250 | it('does not reduce totalMinted', async function () {
251 | expect(await this.erc721aSpot.balanceOf(this.addr1.address)).to.eq(7);
252 | await this.erc721aSpot.connect(this.addr1).burn(10);
253 | expect(await this.erc721aSpot.totalMinted()).to.eq(7);
254 |
255 | await this.erc721aSpot.connect(this.addr1).burn(20);
256 | expect(await this.erc721aSpot.totalMinted()).to.eq(7);
257 |
258 | await this.erc721aSpot.connect(this.addr1).burn(30);
259 | expect(await this.erc721aSpot.totalMinted()).to.eq(7);
260 | });
261 |
262 | it('increases _numberBurned, totalBurned', async function () {
263 | expect(await this.erc721aSpot.balanceOf(this.addr1.address)).to.eq(7);
264 | await this.erc721aSpot.connect(this.addr1).burn(10);
265 | expect(await this.erc721aSpot.numberBurned(this.addr1.address)).to.eq(1);
266 | expect(await this.erc721aSpot.totalBurned()).to.eq(1);
267 |
268 | await this.erc721aSpot.connect(this.addr1).burn(20);
269 | expect(await this.erc721aSpot.numberBurned(this.addr1.address)).to.eq(2);
270 | expect(await this.erc721aSpot.totalBurned()).to.eq(2);
271 |
272 | await this.erc721aSpot.connect(this.addr1).burn(30);
273 | expect(await this.erc721aSpot.numberBurned(this.addr1.address)).to.eq(3);
274 | expect(await this.erc721aSpot.totalBurned()).to.eq(3);
275 |
276 | await this.erc721aSpot.connect(this.addr1).burn(11);
277 | await this.erc721aSpot.connect(this.addr1).burn(12);
278 | await this.erc721aSpot.connect(this.addr1).burn(13);
279 | await this.erc721aSpot.connect(this.addr1).burn(14);
280 | expect(await this.erc721aSpot.numberBurned(this.addr1.address)).to.eq(7);
281 | expect(await this.erc721aSpot.totalBurned()).to.eq(7);
282 | });
283 |
284 | it('affects _exists', async function () {
285 | expect(await this.erc721aSpot.exists(0)).to.eq(false);
286 | expect(await this.erc721aSpot.exists(9)).to.eq(false);
287 | expect(await this.erc721aSpot.exists(10)).to.eq(true);
288 |
289 | expect(await this.erc721aSpot.exists(20)).to.eq(true);
290 |
291 | await this.erc721aSpot.connect(this.addr1).burn(20);
292 | expect(await this.erc721aSpot.exists(20)).to.eq(false);
293 |
294 | this.erc721aSpot.safeMintSpot(this.owner.address, 20);
295 | expect(await this.erc721aSpot.exists(20)).to.eq(true);
296 | });
297 |
298 | it('forwards extraData after burn and re-mint', async function () {
299 | await this.erc721aSpot.setExtraDataAt(20, 123);
300 | let explicitOwnership = await this.erc721aSpot.explicitOwnershipOf(20);
301 | expect(explicitOwnership.addr).to.eq(this.addr1.address);
302 | expect(explicitOwnership.burned).to.eq(false);
303 | expect(explicitOwnership.extraData).to.eq(123);
304 |
305 | await this.erc721aSpot.connect(this.addr1).burn(20);
306 | explicitOwnership = await this.erc721aSpot.explicitOwnershipOf(20);
307 | expect(explicitOwnership.addr).to.eq(this.addr1.address);
308 | expect(explicitOwnership.burned).to.eq(true);
309 | expect(explicitOwnership.extraData).to.eq(123);
310 |
311 | this.erc721aSpot.safeMintSpot(this.owner.address, 20);
312 | explicitOwnership = await this.erc721aSpot.explicitOwnershipOf(20);
313 | expect(explicitOwnership.addr).to.eq(this.owner.address);
314 | expect(explicitOwnership.burned).to.eq(false);
315 | expect(explicitOwnership.extraData).to.eq(123);
316 | });
317 | });
318 | });
319 | });
320 |
--------------------------------------------------------------------------------
/test/extensions/ERC721ATransferCounter.test.js:
--------------------------------------------------------------------------------
1 | const { deployContract } = require('../helpers.js');
2 | const { expect } = require('chai');
3 |
4 | const createTestSuite = ({ contract, constructorArgs }) =>
5 | function () {
6 | context(`${contract}`, function () {
7 | beforeEach(async function () {
8 | this.erc721aCounter = await deployContract(contract, constructorArgs);
9 | });
10 |
11 | context('with minted tokens', async function () {
12 | beforeEach(async function () {
13 | const [owner, addr1] = await ethers.getSigners();
14 | this.owner = owner;
15 | this.addr1 = addr1;
16 |
17 | this.addr1.expected = {
18 | balance: 1,
19 | tokens: [0],
20 | };
21 |
22 | this.owner.expected = {
23 | balance: 2,
24 | tokens: [1, 2],
25 | };
26 |
27 | this.mintOrder = [this.addr1, this.owner];
28 |
29 | for (const minter of this.mintOrder) {
30 | const balance = minter.expected.balance;
31 | if (balance > 0) {
32 | await this.erc721aCounter['safeMint(address,uint256)'](minter.address, balance);
33 | }
34 | // sanity check
35 | expect(await this.erc721aCounter.balanceOf(minter.address)).to.equal(minter.expected.balance);
36 | }
37 | });
38 |
39 | describe('_ownershipOf', function () {
40 | it('initial', async function () {
41 | for (const minter of this.mintOrder) {
42 | for (const tokenId in minter.expected.tokens) {
43 | const ownership = await this.erc721aCounter.getOwnershipOf(tokenId);
44 | expect(ownership.extraData).to.equal(42);
45 | }
46 | }
47 | });
48 |
49 | it('after a transfer', async function () {
50 | await this.erc721aCounter.transferFrom(this.owner.address, this.addr1.address, 1);
51 |
52 | const tests = [
53 | { tokenId: 0, expectedData: 42 },
54 | { tokenId: 1, expectedData: 43 },
55 | { tokenId: 2, expectedData: 42 },
56 | ];
57 |
58 | for (const test of tests) {
59 | const ownership = await this.erc721aCounter.getOwnershipOf(test.tokenId);
60 | expect(ownership.extraData).to.equal(test.expectedData);
61 | }
62 | });
63 |
64 | it('after a burn', async function () {
65 | await this.erc721aCounter['burn(uint256)'](2);
66 |
67 | const tests = [
68 | { tokenId: 0, expectedData: 42 },
69 | { tokenId: 1, expectedData: 42 },
70 | { tokenId: 2, expectedData: 1337 },
71 | ];
72 |
73 | for (const test of tests) {
74 | const ownership = await this.erc721aCounter.getOwnershipAt(test.tokenId);
75 | expect(ownership.extraData).to.equal(test.expectedData);
76 | }
77 | });
78 | });
79 |
80 | describe('setExtraData', function () {
81 | it('can set and get the extraData directly', async function () {
82 | const extraData = 12345;
83 | await this.erc721aCounter.setExtraDataAt(0, extraData);
84 | const ownership = await this.erc721aCounter.getOwnershipAt(0);
85 | expect(ownership.extraData).to.equal(extraData);
86 | });
87 |
88 | it('setting the extraData for uninitialized slot reverts', async function () {
89 | const extraData = 12345;
90 | await expect(this.erc721aCounter.setExtraDataAt(2, extraData))
91 | .to.be.revertedWith('OwnershipNotInitializedForExtraData');
92 | await this.erc721aCounter.transferFrom(this.owner.address, this.addr1.address, 2);
93 | await this.erc721aCounter.setExtraDataAt(2, extraData);
94 | const ownership = await this.erc721aCounter.getOwnershipAt(2);
95 | expect(ownership.extraData).to.equal(extraData);
96 | });
97 | });
98 | });
99 | });
100 | };
101 |
102 | describe(
103 | 'ERC721A override _extraData()',
104 | createTestSuite({
105 | contract: 'ERC721ATransferCounterMock',
106 | constructorArgs: ['Azuki', 'AZUKI'],
107 | })
108 | );
109 |
--------------------------------------------------------------------------------
/test/helpers.js:
--------------------------------------------------------------------------------
1 | const { ethers } = require('hardhat');
2 | const { BigNumber } = require('ethers');
3 |
4 | const deployContract = async function (contractName, constructorArgs) {
5 | let factory;
6 | try {
7 | factory = await ethers.getContractFactory(contractName);
8 | } catch (e) {
9 | factory = await ethers.getContractFactory(contractName + 'UpgradeableWithInit');
10 | }
11 | let contract = await factory.deploy(...(constructorArgs || []));
12 | await contract.deployed();
13 | return contract;
14 | };
15 |
16 | const getBlockTimestamp = async function () {
17 | return parseInt((await ethers.provider.getBlock('latest'))['timestamp']);
18 | };
19 |
20 | const mineBlockTimestamp = async function (timestamp) {
21 | await ethers.provider.send('evm_setNextBlockTimestamp', [timestamp]);
22 | await ethers.provider.send('evm_mine');
23 | };
24 |
25 | const offsettedIndex = function (startTokenId, arr) {
26 | // return one item if arr length is 1
27 | if (arr.length === 1) {
28 | return BigNumber.from(startTokenId + arr[0]);
29 | }
30 | return arr.map((num) => BigNumber.from(startTokenId + num));
31 | };
32 |
33 | module.exports = { deployContract, getBlockTimestamp, mineBlockTimestamp, offsettedIndex };
34 |
--------------------------------------------------------------------------------