From a58852bc4785b6e5bd2c19f29bc139e8495fe488 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 11:53:13 -0400 Subject: [PATCH 001/145] chore: forge init --- .github/workflows/test.yml | 45 +++++++++++++ README.md | 132 +++++++++++-------------------------- foundry.toml | 6 ++ script/Counter.s.sol | 19 ++++++ src/Counter.sol | 14 ++++ test/Counter.t.sol | 24 +++++++ 6 files changed, 148 insertions(+), 92 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 foundry.toml create mode 100644 script/Counter.s.sol create mode 100644 src/Counter.sol create mode 100644 test/Counter.t.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..762a2966 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/README.md b/README.md index 840420dd..9265b455 100644 --- a/README.md +++ b/README.md @@ -1,118 +1,66 @@ -# MagicDrop +## Foundry -[![NPM][npm-shield]][npm-url] -[![CI][ci-shield]][ci-url] -[![MIT License][license-shield]][license-url] -[![Coverage][coverage-shield]][coverage-url] +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** -MagicDrop is a collection of EVM minting protocols that enable the multi stage minting, per stage WL management, per stage supply limit, and crossmint support. +Foundry consists of: -## Motivation +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. -We'd like to introduce the standard of "minting stages". At each stage, the creators can define the following properties: +## Documentation -- per-stage price -- per-stage walletLimit -- per-stage merkleRoot(whitelist) -- per-stage maxStageSupply +https://book.getfoundry.sh/ -The composability of the stages is generic enough to enable flexible and complicated EVM minting contracts. +## Usage -

- -

+### Build - -## Tech/ Framework - -Built with -- [Hardhat](https://hardhat.org) -- [ERC721A](https://github.com/chiru-labs/ERC721A) by Azuki. Fully compliant implementation of IERC721 with significant gas savings for batch minting. -- [ERC721C](https://github.com/limitbreakinc/creator-token-standards) by LimitBreak. Extends ERC721 and add creator-definable transfer security profiles that are the foundation for enforceable, programmable royalties. - -## Features - -- Minting Stages -- Permenent BaseURI Support -- Non-incresing Max Total Supply Support -- Per-stage whitelist Merkle Tree -- Per-stage Max Supply -- Global and Per-stage Limit -- Crossmint support -- Native TypeScript and Typechain-Types Support - -## Contracts -| Contract | Description | -|-------------------------|---------------------------------------------------------------------------------------| -| ERC721M | The basic minting contract based on ERC721A. | -| ERC721CM | The basic minting contract based on ERC721C and ERC721M. | -| ERC721CMRoyalties | Based on ERC721CM, implementing ERC2981 for on-chain royalty. | -| ERC721MOperatorFilterer | ERC721M with OpenSea Operator Filterer | -| BucketAuction | Bucket auction style minting contract. The contract is on beta. Use at your own risk. | - -Please read [ERC721M Contract Usage Guide](./docs/ContractUsageGuide.md) for more details. - -## Installation -Provide step by step series of examples and explanations about how to get a development env running. - - -```bash -npm add @magiceden-oss/erc721m +```shell +$ forge build ``` -## Code Example +### Test -```typescript -import { ERC721M, ERC721M__factory } from '@magiceden-oss/erc721m'; - -const contract = ERC721M__factory.connect( - contractAddress, - signerOrProvider, -); +```shell +$ forge test ``` -## API Reference - -```bash -# Compile the contract -npm run build +### Format -# Get the auto generated typechain-types -./typechain-types +```shell +$ forge fmt ``` -## Tests +### Gas Snapshots -```bash -npm run test +```shell +$ forge snapshot ``` -We are targeting 100% lines coverage. - -![](https://bafkreic3dyzp5i2fi7co2fekkbgmyxgv342irjy5zfiuhvjqic6fuu53ju.ipfs.nftstorage.link/) - -## Security -- [ERC721M Kudelski Security Audit](./docs/AUDIT-PUBLIC-RELEASE-MagicEden-ERC721M1.pdf) -### Bounty Program - - HackerOne program: please contact https://magiceden.io/.well-known/security.txt - - Please be noted that there are some prerequites need to be met and certain assumptions are made when using the contracts. Please check the [Contract Usage Guide](./docs/ContractUsageGuide.md) for more details. +### Anvil -## Used By +```shell +$ anvil +``` -- [Magic Eden Launchpad](https://magiceden.io/launchpad/about) +### Deploy -## License +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` -MIT © [MagicEden Open Source](https://github.com/magiceden-oss) +### Cast +```shell +$ cast +``` - +### Help -[ci-shield]: https://img.shields.io/github/actions/workflow/status/magiceden-oss/erc721m/ci.yml?label=build&style=for-the-badge&branch=main -[ci-url]: https://github.com/magiceden-oss/erc721m/actions/workflows/run_tests.yml -[npm-shield]: https://img.shields.io/npm/v/@magiceden-oss/erc721m.svg?style=for-the-badge -[npm-url]: https://www.npmjs.com/package/@magiceden-oss/erc721m -[license-shield]: https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge -[license-url]: https://github.com/magiceden-oss/erc721m/blob/main/LICENSE.txt -[coverage-shield]: https://img.shields.io/codecov/c/gh/magicoss/erc721m?style=for-the-badge -[coverage-url]: https://codecov.io/gh/magicoss/erc721m +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 00000000..25b918f9 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/script/Counter.s.sol b/script/Counter.s.sol new file mode 100644 index 00000000..cdc1fe9a --- /dev/null +++ b/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/src/Counter.sol b/src/Counter.sol new file mode 100644 index 00000000..aded7997 --- /dev/null +++ b/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol new file mode 100644 index 00000000..54b724f7 --- /dev/null +++ b/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} From 5cc74a37c899949cb38f1557e85e3c5c229cd6d2 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 11:53:14 -0400 Subject: [PATCH 002/145] forge install: forge-std v1.9.2 --- .gitmodules | 3 +++ lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..888d42dc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 00000000..1714bee7 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1714bee72e286e73f76e320d110e0eaf5c4e649d From 104f3287edb1447f571b070df5e6f8630cdda5dd Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 11:55:57 -0400 Subject: [PATCH 003/145] cleanup gitignore Signed-off-by: Wolfy --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index e5b71b95..8a6d78d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ node_modules .env -.env -coverage converage/ coverage.json typechain From f0ec3b8044319162da4733979cd4eb13078a49cc Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:03:36 -0400 Subject: [PATCH 004/145] cleanup Signed-off-by: Wolfy --- .gitmodules | 9 +++++++++ foundry.toml | 5 +++-- remappings.txt | 3 +++ script/Counter.s.sol | 19 ------------------- src/Counter.sol | 14 -------------- test/Counter.t.sol | 24 ------------------------ 6 files changed, 15 insertions(+), 59 deletions(-) create mode 100644 remappings.txt delete mode 100644 script/Counter.s.sol delete mode 100644 src/Counter.sol delete mode 100644 test/Counter.t.sol diff --git a/.gitmodules b/.gitmodules index 888d42dc..823b5e90 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,12 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/solmate"] + path = lib/solmate + url = https://github.com/transmissions11/solmate +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/foundry.toml b/foundry.toml index 25b918f9..bf4b7dd4 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,6 +1,7 @@ [profile.default] -src = "src" +src = "contracts" out = "out" -libs = ["lib"] +libs = ["lib", "node_modules"] +solc_version = "0.8.20" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 00000000..dbd10d0f --- /dev/null +++ b/remappings.txt @@ -0,0 +1,3 @@ +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +forge-std/=lib/forge-std/src/ \ No newline at end of file diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index cdc1fe9a..00000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded7997..00000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f7..00000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} From 7918e61d6db37f356076e5c298d5bfddbcf5c14c Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:03:43 -0400 Subject: [PATCH 005/145] forge install: openzeppelin-contracts v5.0.2 --- lib/openzeppelin-contracts | 1 + 1 file changed, 1 insertion(+) create mode 160000 lib/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 00000000..dbb6104c --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 From f5e0d5e70cb02de9ac010d41eba5342edd8d012e Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:04:16 -0400 Subject: [PATCH 006/145] forge install: openzeppelin-contracts-upgradeable v5.0.2 --- lib/openzeppelin-contracts-upgradeable | 1 + 1 file changed, 1 insertion(+) create mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 00000000..723f8cab --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 From cf9feacc836c6673f5166ac553cf5001d69807b2 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:04:19 -0400 Subject: [PATCH 007/145] forge install: solmate --- lib/solmate | 1 + 1 file changed, 1 insertion(+) create mode 160000 lib/solmate diff --git a/lib/solmate b/lib/solmate new file mode 160000 index 00000000..97bdb200 --- /dev/null +++ b/lib/solmate @@ -0,0 +1 @@ +Subproject commit 97bdb2003b70382996a79a406813f76417b1cf90 From a3e85f7d71123f2747091bfc72f40cf079b371aa Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:04:27 -0400 Subject: [PATCH 008/145] forge install: solady v0.0.243 --- .gitmodules | 3 +++ lib/solady | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/solady diff --git a/.gitmodules b/.gitmodules index 823b5e90..41ec7608 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/solady"] + path = lib/solady + url = https://github.com/vectorized/solady diff --git a/lib/solady b/lib/solady new file mode 160000 index 00000000..362b2efd --- /dev/null +++ b/lib/solady @@ -0,0 +1 @@ +Subproject commit 362b2efd20f38aea7252b391e5e016633ff79641 From aea9eecdf5e3bc576cdbe3c37671e04efd99c77d Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:05:39 -0400 Subject: [PATCH 009/145] update gitmodules Signed-off-by: Wolfy --- .gitmodules | 6 +++--- remappings.txt | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 41ec7608..b7fa07f7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,9 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/solady"] + path = lib/solady + url = https://github.com/vectorized/solady [submodule "lib/solmate"] path = lib/solmate url = https://github.com/transmissions11/solmate @@ -10,6 +13,3 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable -[submodule "lib/solady"] - path = lib/solady - url = https://github.com/vectorized/solady diff --git a/remappings.txt b/remappings.txt index dbd10d0f..6027b1c5 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,5 @@ +solady/=lib/solady/src/ +solemate/=/lib/solemate/src/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ forge-std/=lib/forge-std/src/ \ No newline at end of file From 352ab91b09625e8ff086a4091598e876d7bee0c4 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:05:49 -0400 Subject: [PATCH 010/145] forge install: operator-filter-registry v1.4.2 --- .gitmodules | 3 +++ lib/operator-filter-registry | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/operator-filter-registry diff --git a/.gitmodules b/.gitmodules index b7fa07f7..b7a65329 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/operator-filter-registry"] + path = lib/operator-filter-registry + url = https://github.com/ProjectOpenSea/operator-filter-registry diff --git a/lib/operator-filter-registry b/lib/operator-filter-registry new file mode 160000 index 00000000..f542d9de --- /dev/null +++ b/lib/operator-filter-registry @@ -0,0 +1 @@ +Subproject commit f542d9de7593024c6d37c1c875c211c1974a4751 From 73bc80dd512609f8e48df475c34824d5d670db62 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:06:22 -0400 Subject: [PATCH 011/145] forge install: ERC721A v4.3.0 --- .gitmodules | 3 +++ lib/ERC721A | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/ERC721A diff --git a/.gitmodules b/.gitmodules index b7a65329..64dc1f80 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "lib/operator-filter-registry"] path = lib/operator-filter-registry url = https://github.com/ProjectOpenSea/operator-filter-registry +[submodule "lib/ERC721A"] + path = lib/ERC721A + url = https://github.com/chiru-labs/ERC721A diff --git a/lib/ERC721A b/lib/ERC721A new file mode 160000 index 00000000..6f8a82a7 --- /dev/null +++ b/lib/ERC721A @@ -0,0 +1 @@ +Subproject commit 6f8a82a7b2833ad8b2fc7b54349281143a731fdd From 32613506211d4a3110f910b5cfbec67f1d33d4c8 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:07:58 -0400 Subject: [PATCH 012/145] update remappings Signed-off-by: Wolfy --- remappings.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/remappings.txt b/remappings.txt index 6027b1c5..9158e96b 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,4 +2,5 @@ solady/=lib/solady/src/ solemate/=/lib/solemate/src/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ -forge-std/=lib/forge-std/src/ \ No newline at end of file +forge-std/=lib/forge-std/src/ +erc721a/contracts=lib/ERC721a/contracts/ From fca2b83d9fc8b42e675ee14f718270bcec614961 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:08:03 -0400 Subject: [PATCH 013/145] forge install: ERC721A-Upgradeable v4.3.0 --- .gitmodules | 3 +++ lib/ERC721A-Upgradeable | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/ERC721A-Upgradeable diff --git a/.gitmodules b/.gitmodules index 64dc1f80..efac94a1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "lib/ERC721A"] path = lib/ERC721A url = https://github.com/chiru-labs/ERC721A +[submodule "lib/ERC721A-Upgradeable"] + path = lib/ERC721A-Upgradeable + url = https://github.com/chiru-labs/ERC721A-Upgradeable diff --git a/lib/ERC721A-Upgradeable b/lib/ERC721A-Upgradeable new file mode 160000 index 00000000..4508a3d6 --- /dev/null +++ b/lib/ERC721A-Upgradeable @@ -0,0 +1 @@ +Subproject commit 4508a3d65ed75ccab59e80f25571c2818c03f55b From a03e964e56a82619c6e911148b41d91f723f8c08 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:09:44 -0400 Subject: [PATCH 014/145] update remappings Signed-off-by: Wolfy --- remappings.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/remappings.txt b/remappings.txt index 9158e96b..ee9282ef 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,3 +4,4 @@ solemate/=/lib/solemate/src/ @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ forge-std/=lib/forge-std/src/ erc721a/contracts=lib/ERC721a/contracts/ +erc721a-upgradeable/contracts=lib/ERC721A-Upgradeable/contracts/ From d9c25317845d2a08a1a4f0609df6aaf9845c3dc1 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:10:05 -0400 Subject: [PATCH 015/145] forge install: creator-token-standards v3.0.0 --- .gitmodules | 3 +++ lib/creator-token-standards | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/creator-token-standards diff --git a/.gitmodules b/.gitmodules index efac94a1..c9b6f3de 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "lib/ERC721A-Upgradeable"] path = lib/ERC721A-Upgradeable url = https://github.com/chiru-labs/ERC721A-Upgradeable +[submodule "lib/creator-token-standards"] + path = lib/creator-token-standards + url = https://github.com/limitbreakinc/creator-token-standards diff --git a/lib/creator-token-standards b/lib/creator-token-standards new file mode 160000 index 00000000..14eb0cb8 --- /dev/null +++ b/lib/creator-token-standards @@ -0,0 +1 @@ +Subproject commit 14eb0cb8d486692e8b522d46cc2a06eea800286a From c2aa6a54938c863ae2c9d7044db1582d9a33ba48 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:54:59 -0400 Subject: [PATCH 016/145] remaining foundry setup / fix build issues Signed-off-by: Wolfy --- .gitignore | 1 + contracts/ERC721CM.sol | 13 + contracts/ERC721CMRoyaltiesInitializable.sol | 13 + .../CreatorTokenBase.sol | 352 ------------------ .../ERC721ACQueryable.sol | 2 +- .../ERC721ACQueryableInitializable.sol | 2 +- package-lock.json | 48 ++- package.json | 8 +- remappings.txt | 1 + test/ERC721CM.test.ts | 10 - 10 files changed, 74 insertions(+), 376 deletions(-) delete mode 100644 contracts/creator-token-standards/CreatorTokenBase.sol diff --git a/.gitignore b/.gitignore index 8a6d78d2..402aa782 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ artifacts .vscode dist .DS_Store +out/ \ No newline at end of file diff --git a/contracts/ERC721CM.sol b/contracts/ERC721CM.sol index 99a04a15..fde741b9 100644 --- a/contracts/ERC721CM.sol +++ b/contracts/ERC721CM.sol @@ -714,4 +714,17 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard { function _requireCallerIsContractOwner() internal view virtual override { _checkOwner(); } + + /** + * @notice Returns the function selector for the transfer validator's validation function to be called + * @notice for transaction simulation. + */ + function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { + functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); + isViewFunction = true; + } + + function _tokenType() internal pure override returns(uint16) { + return uint16(721); + } } diff --git a/contracts/ERC721CMRoyaltiesInitializable.sol b/contracts/ERC721CMRoyaltiesInitializable.sol index 3ef3195a..288ec402 100644 --- a/contracts/ERC721CMRoyaltiesInitializable.sol +++ b/contracts/ERC721CMRoyaltiesInitializable.sol @@ -65,4 +65,17 @@ contract ERC721CMRoyaltiesInitializable is { _checkOwner(); } + + /** + * @notice Returns the function selector for the transfer validator's validation function to be called + * @notice for transaction simulation. + */ + function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { + functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); + isViewFunction = true; + } + + function _tokenType() internal pure override returns(uint16) { + return uint16(721); + } } diff --git a/contracts/creator-token-standards/CreatorTokenBase.sol b/contracts/creator-token-standards/CreatorTokenBase.sol deleted file mode 100644 index 248ecf47..00000000 --- a/contracts/creator-token-standards/CreatorTokenBase.sol +++ /dev/null @@ -1,352 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import "@limitbreak/creator-token-standards/src/access/OwnablePermissions.sol"; -import "@limitbreak/creator-token-standards/src/interfaces/ICreatorToken.sol"; -import "@limitbreak/creator-token-standards/src/interfaces/ICreatorTokenTransferValidator.sol"; -import "@limitbreak/creator-token-standards/src/utils/TransferValidation.sol"; -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - -/** - * @title CreatorTokenBase - * @author Limit Break, Inc. - * @notice CreatorTokenBase is an abstract contract that provides basic functionality for managing token - * transfer policies through an implementation of ICreatorTokenTransferValidator. This contract is intended to be used - * as a base for creator-specific token contracts, enabling customizable transfer restrictions and security policies. - * - *

Features:

- *
    Ownable: This contract can have an owner who can set and update the transfer validator.
- *
    TransferValidation: Implements the basic token transfer validation interface.
- *
    ICreatorToken: Implements the interface for creator tokens, providing view functions for token security policies.
- * - *

Benefits:

- *
    Provides a flexible and modular way to implement custom token transfer restrictions and security policies.
- *
    Allows creators to enforce policies such as whitelisted operators and permitted contract receivers.
- *
    Can be easily integrated into other token contracts as a base contract.
- * - *

Intended Usage:

- *
    Use as a base contract for creator token implementations that require advanced transfer restrictions and - * security policies.
- *
    Set and update the ICreatorTokenTransferValidator implementation contract to enforce desired policies for the - * creator token.
- */ -abstract contract CreatorTokenBase is - OwnablePermissions, - TransferValidation, - ICreatorToken -{ - /** - * @dev Thrown when the transfer validator address is the zero address - * @dev or it does not implement the `ICreatorTokenTransferValidator` interface. - */ - error CreatorTokenBase__InvalidTransferValidatorContract(); - - /// @dev Thrown when attempting to set transfer security settings before a transfer validator is set. - error CreatorTokenBase__SetTransferValidatorFirst(); - - /// @dev The default transfer validator address for calls to `setToDefaultSecurityPolicy`. - address public constant DEFAULT_TRANSFER_VALIDATOR = - address(0x721C00182a990771244d7A71B9FA2ea789A3b433); - - /// @dev The default transfer security level for calls to `setToDefaultSecurityPolicy`. - TransferSecurityLevels public constant DEFAULT_TRANSFER_SECURITY_LEVEL = - TransferSecurityLevels.Two; - - /// @dev The default operator whitelist id for calls to `setToDefaultSecurityPolicy`. - uint120 public constant DEFAULT_OPERATOR_WHITELIST_ID = uint120(1); - - ICreatorTokenTransferValidator private transferValidator; - - /** - * @notice Allows the contract owner to set the transfer validator to the official validator contract - * and set the security policy to the recommended default settings. - * @dev May be overridden to change the default behavior of an individual collection. - */ - function setToDefaultSecurityPolicy() public virtual { - _requireCallerIsContractOwner(); - setTransferValidator(DEFAULT_TRANSFER_VALIDATOR); - ICreatorTokenTransferValidator(DEFAULT_TRANSFER_VALIDATOR) - .setTransferSecurityLevelOfCollection( - address(this), - DEFAULT_TRANSFER_SECURITY_LEVEL - ); - ICreatorTokenTransferValidator(DEFAULT_TRANSFER_VALIDATOR) - .setOperatorWhitelistOfCollection( - address(this), - DEFAULT_OPERATOR_WHITELIST_ID - ); - } - - /** - * @notice Allows the contract owner to set the transfer validator to a custom validator contract - * and set the security policy to their own custom settings. - */ - function setToCustomValidatorAndSecurityPolicy( - address validator, - TransferSecurityLevels level, - uint120 operatorWhitelistId, - uint120 permittedContractReceiversAllowlistId - ) public { - _requireCallerIsContractOwner(); - - setTransferValidator(validator); - - ICreatorTokenTransferValidator(validator) - .setTransferSecurityLevelOfCollection(address(this), level); - - ICreatorTokenTransferValidator(validator) - .setOperatorWhitelistOfCollection( - address(this), - operatorWhitelistId - ); - - ICreatorTokenTransferValidator(validator) - .setPermittedContractReceiverAllowlistOfCollection( - address(this), - permittedContractReceiversAllowlistId - ); - } - - /** - * @notice Allows the contract owner to set the security policy to their own custom settings. - * @dev Reverts if the transfer validator has not been set. - */ - function setToCustomSecurityPolicy( - TransferSecurityLevels level, - uint120 operatorWhitelistId, - uint120 permittedContractReceiversAllowlistId - ) public { - _requireCallerIsContractOwner(); - - ICreatorTokenTransferValidator validator = getTransferValidator(); - if (address(validator) == address(0)) { - revert CreatorTokenBase__SetTransferValidatorFirst(); - } - - validator.setTransferSecurityLevelOfCollection(address(this), level); - validator.setOperatorWhitelistOfCollection( - address(this), - operatorWhitelistId - ); - validator.setPermittedContractReceiverAllowlistOfCollection( - address(this), - permittedContractReceiversAllowlistId - ); - } - - /** - * @notice Sets the transfer validator for the token contract. - * - * @dev Throws when provided validator contract is not the zero address and doesn't support - * the ICreatorTokenTransferValidator interface. - * @dev Throws when the caller is not the contract owner. - * - * @dev

Postconditions:

- * 1. The transferValidator address is updated. - * 2. The `TransferValidatorUpdated` event is emitted. - * - * @param transferValidator_ The address of the transfer validator contract. - */ - function setTransferValidator(address transferValidator_) public { - _requireCallerIsContractOwner(); - - bool isValidTransferValidator = false; - - if (transferValidator_.code.length > 0) { - try - IERC165(transferValidator_).supportsInterface( - type(ICreatorTokenTransferValidator).interfaceId - ) - returns (bool supportsInterface) { - isValidTransferValidator = supportsInterface; - } catch {} - } - - if (transferValidator_ != address(0) && !isValidTransferValidator) { - revert CreatorTokenBase__InvalidTransferValidatorContract(); - } - - emit TransferValidatorUpdated( - address(transferValidator), - transferValidator_ - ); - - transferValidator = ICreatorTokenTransferValidator(transferValidator_); - } - - /** - * @notice Returns the transfer validator contract address for this token contract. - */ - function getTransferValidator() - public - view - override - returns (ICreatorTokenTransferValidator) - { - return transferValidator; - } - - /** - * @notice Returns the security policy for this token contract, which includes: - * Transfer security level, operator whitelist id, permitted contract receiver allowlist id. - */ - function getSecurityPolicy() - public - view - override - returns (CollectionSecurityPolicy memory) - { - if (address(transferValidator) != address(0)) { - return transferValidator.getCollectionSecurityPolicy(address(this)); - } - - return - CollectionSecurityPolicy({ - transferSecurityLevel: TransferSecurityLevels.Recommended, - operatorWhitelistId: 0, - permittedContractReceiversId: 0 - }); - } - - /** - * @notice Returns the list of all whitelisted operators for this token contract. - * @dev This can be an expensive call and should only be used in view-only functions. - */ - function getWhitelistedOperators() - public - view - override - returns (address[] memory) - { - if (address(transferValidator) != address(0)) { - return - transferValidator.getWhitelistedOperators( - transferValidator - .getCollectionSecurityPolicy(address(this)) - .operatorWhitelistId - ); - } - - return new address[](0); - } - - /** - * @notice Returns the list of permitted contract receivers for this token contract. - * @dev This can be an expensive call and should only be used in view-only functions. - */ - function getPermittedContractReceivers() - public - view - override - returns (address[] memory) - { - if (address(transferValidator) != address(0)) { - return - transferValidator.getPermittedContractReceivers( - transferValidator - .getCollectionSecurityPolicy(address(this)) - .permittedContractReceiversId - ); - } - - return new address[](0); - } - - /** - * @notice Checks if an operator is whitelisted for this token contract. - * @param operator The address of the operator to check. - */ - function isOperatorWhitelisted( - address operator - ) public view override returns (bool) { - if (address(transferValidator) != address(0)) { - return - transferValidator.isOperatorWhitelisted( - transferValidator - .getCollectionSecurityPolicy(address(this)) - .operatorWhitelistId, - operator - ); - } - - return false; - } - - /** - * @notice Checks if a contract receiver is permitted for this token contract. - * @param receiver The address of the receiver to check. - */ - function isContractReceiverPermitted( - address receiver - ) public view override returns (bool) { - if (address(transferValidator) != address(0)) { - return - transferValidator.isContractReceiverPermitted( - transferValidator - .getCollectionSecurityPolicy(address(this)) - .permittedContractReceiversId, - receiver - ); - } - - return false; - } - - /** - * @notice Determines if a transfer is allowed based on the token contract's security policy. Use this function - * to simulate whether or not a transfer made by the specified `caller` from the `from` address to the `to` - * address would be allowed by this token's security policy. - * - * @notice This function only checks the security policy restrictions and does not check whether token ownership - * or approvals are in place. - * - * @param caller The address of the simulated caller. - * @param from The address of the sender. - * @param to The address of the receiver. - * @return True if the transfer is allowed, false otherwise. - */ - function isTransferAllowed( - address caller, - address from, - address to - ) public view override returns (bool) { - if (address(transferValidator) != address(0)) { - try - transferValidator.applyCollectionTransferPolicy( - caller, - from, - to - ) - { - return true; - } catch { - return false; - } - } - return true; - } - - /** - * @dev Pre-validates a token transfer, reverting if the transfer is not allowed by this token's security policy. - * Inheriting contracts are responsible for overriding the _beforeTokenTransfer function, or its equivalent - * and calling _validateBeforeTransfer so that checks can be properly applied during token transfers. - * - * @dev Throws when the transfer doesn't comply with the collection's transfer policy, if the transferValidator is - * set to a non-zero address. - * - * @param caller The address of the caller. - * @param from The address of the sender. - * @param to The address of the receiver. - */ - function _preValidateTransfer( - address caller, - address from, - address to, - uint256 /*tokenId*/, - uint256 /*value*/ - ) internal virtual override { - if (address(transferValidator) != address(0)) { - transferValidator.applyCollectionTransferPolicy(caller, from, to); - } - } -} diff --git a/contracts/creator-token-standards/ERC721ACQueryable.sol b/contracts/creator-token-standards/ERC721ACQueryable.sol index cfb13c44..17088d3f 100644 --- a/contracts/creator-token-standards/ERC721ACQueryable.sol +++ b/contracts/creator-token-standards/ERC721ACQueryable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import "./CreatorTokenBase.sol"; +import "@limitbreak/creator-token-standards/src/utils/CreatorTokenBase.sol"; import "erc721a/contracts/extensions/ERC721AQueryable.sol"; /** diff --git a/contracts/creator-token-standards/ERC721ACQueryableInitializable.sol b/contracts/creator-token-standards/ERC721ACQueryableInitializable.sol index 72e68930..ba650fdc 100644 --- a/contracts/creator-token-standards/ERC721ACQueryableInitializable.sol +++ b/contracts/creator-token-standards/ERC721ACQueryableInitializable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import "./CreatorTokenBase.sol"; +import "@limitbreak/creator-token-standards/src/utils/CreatorTokenBase.sol"; import "erc721a-upgradeable/contracts/extensions/ERC721AQueryableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; diff --git a/package-lock.json b/package-lock.json index 2556a595..217dc308 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "@me-foundation/magicdrop", - "version": "0.1.3", + "version": "0.1.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@me-foundation/magicdrop", - "version": "0.1.3", + "version": "0.1.5", "dependencies": { "@inquirer/prompts": "^2.2.0", "@layerzerolabs/solidity-examples": "^0.0.13", - "@limitbreak/creator-token-standards": "^2.0.1", + "@limitbreak/creator-token-standards": "^3.0.0", "@openzeppelin/contracts": "^5.0.2", "@openzeppelin/contracts-upgradeable": "^5.0.2", "erc721a": "^4.2.3", @@ -2483,10 +2483,11 @@ "peer": true }, "node_modules/@limitbreak/creator-token-standards": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@limitbreak/creator-token-standards/-/creator-token-standards-2.0.1.tgz", - "integrity": "sha512-gZwPE74lU3Bf41iK07bkeLieXq0GYs0anL5UILN3w2HdA3XzwxNM3Nsn0HMb558rXdWxUfN9WdV7xXco9S/9zQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@limitbreak/creator-token-standards/-/creator-token-standards-3.0.0.tgz", + "integrity": "sha512-3JxKaHBOs3vu7tTnm1LyeSlNjZqk+WKhtTvzETwcamYsqgkHHMTGTRbgkVfczuFYcOss7IDtt3f5Qf42M/nWeQ==", "dependencies": { + "@limitbreak/permit-c": "1.0.0", "@openzeppelin/contracts": "4.8.3", "erc721a": "4.2.3" } @@ -2496,6 +2497,19 @@ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.3.tgz", "integrity": "sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg==" }, + "node_modules/@limitbreak/permit-c": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@limitbreak/permit-c/-/permit-c-1.0.0.tgz", + "integrity": "sha512-7BooxTklXlCPzfdccfKL7Tt2Cm4MntOHR51dHqjKePn7AynMKsUtaKH75ZXHzWRPZSmyixFNzQ7tIJDdPxF2MA==", + "dependencies": { + "@openzeppelin/contracts": "4.8.3" + } + }, + "node_modules/@limitbreak/permit-c/node_modules/@openzeppelin/contracts": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.3.tgz", + "integrity": "sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg==" + }, "node_modules/@metamask/eth-sig-util": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", @@ -25889,10 +25903,11 @@ "peer": true }, "@limitbreak/creator-token-standards": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@limitbreak/creator-token-standards/-/creator-token-standards-2.0.1.tgz", - "integrity": "sha512-gZwPE74lU3Bf41iK07bkeLieXq0GYs0anL5UILN3w2HdA3XzwxNM3Nsn0HMb558rXdWxUfN9WdV7xXco9S/9zQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@limitbreak/creator-token-standards/-/creator-token-standards-3.0.0.tgz", + "integrity": "sha512-3JxKaHBOs3vu7tTnm1LyeSlNjZqk+WKhtTvzETwcamYsqgkHHMTGTRbgkVfczuFYcOss7IDtt3f5Qf42M/nWeQ==", "requires": { + "@limitbreak/permit-c": "1.0.0", "@openzeppelin/contracts": "4.8.3", "erc721a": "4.2.3" }, @@ -25904,6 +25919,21 @@ } } }, + "@limitbreak/permit-c": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@limitbreak/permit-c/-/permit-c-1.0.0.tgz", + "integrity": "sha512-7BooxTklXlCPzfdccfKL7Tt2Cm4MntOHR51dHqjKePn7AynMKsUtaKH75ZXHzWRPZSmyixFNzQ7tIJDdPxF2MA==", + "requires": { + "@openzeppelin/contracts": "4.8.3" + }, + "dependencies": { + "@openzeppelin/contracts": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.3.tgz", + "integrity": "sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg==" + } + } + }, "@metamask/eth-sig-util": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", diff --git a/package.json b/package.json index 3bf76059..cf17fcf7 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "dependencies": { "@inquirer/prompts": "^2.2.0", "@layerzerolabs/solidity-examples": "^0.0.13", - "@limitbreak/creator-token-standards": "^2.0.1", + "@limitbreak/creator-token-standards": "^3.0.0", "@openzeppelin/contracts": "^5.0.2", "@openzeppelin/contracts-upgradeable": "^5.0.2", "erc721a": "^4.2.3", @@ -93,6 +93,8 @@ "eslint --ext .ts,.js scripts test --fix", "prettier --write scripts cosign-server test hardhat.config.ts" ], - "*.sol": ["prettier --write --plugin=prettier-plugin-solidity contracts"] + "*.sol": [ + "prettier --write --plugin=prettier-plugin-solidity contracts" + ] } -} +} \ No newline at end of file diff --git a/remappings.txt b/remappings.txt index ee9282ef..f2a460f1 100644 --- a/remappings.txt +++ b/remappings.txt @@ -5,3 +5,4 @@ solemate/=/lib/solemate/src/ forge-std/=lib/forge-std/src/ erc721a/contracts=lib/ERC721a/contracts/ erc721a-upgradeable/contracts=lib/ERC721A-Upgradeable/contracts/ +@limitbreak/creator-token-standards/src/=lib/creator-token-standards/src/ diff --git a/test/ERC721CM.test.ts b/test/ERC721CM.test.ts index 0ee65210..75426519 100644 --- a/test/ERC721CM.test.ts +++ b/test/ERC721CM.test.ts @@ -1991,14 +1991,4 @@ describe('ERC721CM', function () { ); }); }); - - describe('Transfer validator', function () { - it('default validator settings', async () => { - expect(await contract.getTransferValidator()).to.equal( - '0x0000000000000000000000000000000000000000', - ); - }); - - // TODO: figure out a way to mock the validator contract - }); }); From 50d39129ade47d30ffc9542f9feff704ac0771fb Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:56:45 -0400 Subject: [PATCH 017/145] revert readme change Signed-off-by: Wolfy --- README.md | 132 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 92 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 9265b455..840420dd 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,118 @@ -## Foundry +# MagicDrop -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +[![NPM][npm-shield]][npm-url] +[![CI][ci-shield]][ci-url] +[![MIT License][license-shield]][license-url] +[![Coverage][coverage-shield]][coverage-url] -Foundry consists of: +MagicDrop is a collection of EVM minting protocols that enable the multi stage minting, per stage WL management, per stage supply limit, and crossmint support. -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +## Motivation -## Documentation +We'd like to introduce the standard of "minting stages". At each stage, the creators can define the following properties: -https://book.getfoundry.sh/ +- per-stage price +- per-stage walletLimit +- per-stage merkleRoot(whitelist) +- per-stage maxStageSupply -## Usage +The composability of the stages is generic enough to enable flexible and complicated EVM minting contracts. -### Build +

+ +

-```shell -$ forge build -``` -### Test +## Tech/ Framework -```shell -$ forge test -``` +Built with +- [Hardhat](https://hardhat.org) +- [ERC721A](https://github.com/chiru-labs/ERC721A) by Azuki. Fully compliant implementation of IERC721 with significant gas savings for batch minting. +- [ERC721C](https://github.com/limitbreakinc/creator-token-standards) by LimitBreak. Extends ERC721 and add creator-definable transfer security profiles that are the foundation for enforceable, programmable royalties. -### Format +## Features -```shell -$ forge fmt -``` +- Minting Stages +- Permenent BaseURI Support +- Non-incresing Max Total Supply Support +- Per-stage whitelist Merkle Tree +- Per-stage Max Supply +- Global and Per-stage Limit +- Crossmint support +- Native TypeScript and Typechain-Types Support -### Gas Snapshots +## Contracts +| Contract | Description | +|-------------------------|---------------------------------------------------------------------------------------| +| ERC721M | The basic minting contract based on ERC721A. | +| ERC721CM | The basic minting contract based on ERC721C and ERC721M. | +| ERC721CMRoyalties | Based on ERC721CM, implementing ERC2981 for on-chain royalty. | +| ERC721MOperatorFilterer | ERC721M with OpenSea Operator Filterer | +| BucketAuction | Bucket auction style minting contract. The contract is on beta. Use at your own risk. | -```shell -$ forge snapshot -``` +Please read [ERC721M Contract Usage Guide](./docs/ContractUsageGuide.md) for more details. + +## Installation +Provide step by step series of examples and explanations about how to get a development env running. -### Anvil -```shell -$ anvil +```bash +npm add @magiceden-oss/erc721m ``` -### Deploy +## Code Example -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +```typescript +import { ERC721M, ERC721M__factory } from '@magiceden-oss/erc721m'; + +const contract = ERC721M__factory.connect( + contractAddress, + signerOrProvider, +); ``` -### Cast +## API Reference + +```bash +# Compile the contract +npm run build -```shell -$ cast +# Get the auto generated typechain-types +./typechain-types ``` -### Help +## Tests -```shell -$ forge --help -$ anvil --help -$ cast --help +```bash +npm run test ``` + +We are targeting 100% lines coverage. + +![](https://bafkreic3dyzp5i2fi7co2fekkbgmyxgv342irjy5zfiuhvjqic6fuu53ju.ipfs.nftstorage.link/) + +## Security +- [ERC721M Kudelski Security Audit](./docs/AUDIT-PUBLIC-RELEASE-MagicEden-ERC721M1.pdf) +### Bounty Program + - HackerOne program: please contact https://magiceden.io/.well-known/security.txt + - Please be noted that there are some prerequites need to be met and certain assumptions are made when using the contracts. Please check the [Contract Usage Guide](./docs/ContractUsageGuide.md) for more details. + +## Used By + +- [Magic Eden Launchpad](https://magiceden.io/launchpad/about) + +## License + +MIT © [MagicEden Open Source](https://github.com/magiceden-oss) + + + + +[ci-shield]: https://img.shields.io/github/actions/workflow/status/magiceden-oss/erc721m/ci.yml?label=build&style=for-the-badge&branch=main +[ci-url]: https://github.com/magiceden-oss/erc721m/actions/workflows/run_tests.yml +[npm-shield]: https://img.shields.io/npm/v/@magiceden-oss/erc721m.svg?style=for-the-badge +[npm-url]: https://www.npmjs.com/package/@magiceden-oss/erc721m +[license-shield]: https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge +[license-url]: https://github.com/magiceden-oss/erc721m/blob/main/LICENSE.txt +[coverage-shield]: https://img.shields.io/codecov/c/gh/magicoss/erc721m?style=for-the-badge +[coverage-url]: https://codecov.io/gh/magicoss/erc721m From eed84e599c44bfd89803ad021d14cb7639ec035f Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 12:58:04 -0400 Subject: [PATCH 018/145] update readme Signed-off-by: Wolfy --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 840420dd..62b7927c 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,32 @@ npm run build npm run test ``` +## Using Foundry + +### Install Foundry + +```bash +curl -L https://foundry.paradigm.xyz | bash +``` + +### Install Dependencies + +```bash +forge install +``` + +### Build Contracts + +```bash +forge build +``` + +### Run Tests + +```bash +forge test +``` + We are targeting 100% lines coverage. ![](https://bafkreic3dyzp5i2fi7co2fekkbgmyxgv342irjy5zfiuhvjqic6fuu53ju.ipfs.nftstorage.link/) From e5b300aa793eb3891e8565124ce891c48e93cf40 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 19 Sep 2024 17:01:44 -0400 Subject: [PATCH 019/145] contract factory Signed-off-by: Wolfy --- contracts/MagicDropTokenImplRegistry.sol | 78 +++++++++++ contracts/common/Structs.sol | 7 + contracts/factory/MagicDropCloneFactory.sol | 77 +++++++++++ contracts/interfaces/IInitializableToken.sol | 10 ++ .../interfaces/IMagicDropCloneFactory.sol | 27 ++++ .../IMagicDropTokenImplRegistry.sol | 15 ++ .../nft/MagicDropERC1155Initializable.sol | 43 ++++++ .../nft/MagicDropERC721Initializable.sol | 27 ++++ test/foundry/MagicDropCloneFactoryTest.t.sol | 130 ++++++++++++++++++ .../MagicDropTokenImplRegistryTest.t.sol | 69 ++++++++++ 10 files changed, 483 insertions(+) create mode 100644 contracts/MagicDropTokenImplRegistry.sol create mode 100644 contracts/common/Structs.sol create mode 100644 contracts/factory/MagicDropCloneFactory.sol create mode 100644 contracts/interfaces/IInitializableToken.sol create mode 100644 contracts/interfaces/IMagicDropCloneFactory.sol create mode 100644 contracts/interfaces/IMagicDropTokenImplRegistry.sol create mode 100644 contracts/nft/MagicDropERC1155Initializable.sol create mode 100644 contracts/nft/MagicDropERC721Initializable.sol create mode 100644 test/foundry/MagicDropCloneFactoryTest.t.sol create mode 100644 test/foundry/MagicDropTokenImplRegistryTest.t.sol diff --git a/contracts/MagicDropTokenImplRegistry.sol b/contracts/MagicDropTokenImplRegistry.sol new file mode 100644 index 00000000..949328ce --- /dev/null +++ b/contracts/MagicDropTokenImplRegistry.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import "./interfaces/IMagicDropTokenImplRegistry.sol"; +import {TokenStandard} from "./common/Structs.sol"; + +/** + * @title MagicDropTokenImplRegistry + * @dev A registry for managing token implementation addresses for different token standards. + * This contract is upgradeable and uses the UUPS pattern. + */ +contract MagicDropTokenImplRegistry is IMagicDropTokenImplRegistry, UUPSUpgradeable, Ownable2StepUpgradeable { + mapping(TokenStandard => mapping(uint256 => address)) private implementations; + mapping(TokenStandard => uint256) private nextImplId; + + /** + * @dev Initializes the contract, setting up the owner and UUPS upgradeability. + * This function replaces the constructor for upgradeable contracts. + */ + function initialize() public initializer { + __Ownable2Step_init(); + __Ownable_init(msg.sender); + __UUPSUpgradeable_init(); + + // Initialize nextImplId for each token standard to 1 + nextImplId[TokenStandard.ERC721] = 1; + nextImplId[TokenStandard.ERC1155] = 1; + } + + /** + * @dev Registers a new implementation for a given token standard. + * @param standard The token standard (ERC721, ERC1155). + * @param impl The address of the implementation contract. + * @notice Only the contract owner can call this function. + * @notice Reverts if an implementation with the same name is already registered. + */ + function registerImplementation(TokenStandard standard, address impl) external onlyOwner returns (uint256) { + uint256 implId = nextImplId[standard]; + implementations[standard][implId] = impl; + nextImplId[standard] = implId + 1; + emit ImplementationRegistered(standard, impl, implId); + return implId; + } + + /** + * @dev Unregisters an implementation for a given token standard. + * @param standard The token standard (ERC721, ERC1155). + * @param implId The ID of the implementation to unregister. + * @notice Only the contract owner can call this function. + * @notice Reverts if the implementation is not registered. + */ + function unregisterImplementation(TokenStandard standard, uint256 implId) external onlyOwner { + if (implementations[standard][implId] == address(0)) { + revert ImplementationNotRegistered(); + } + delete implementations[standard][implId]; + emit ImplementationUnregistered(standard, implId); + } + + /** + * @dev Retrieves the implementation address for a given token standard and implementation name. + * @param standard The token standard (ERC20, ERC721, ERC1155). + * @param implId The ID of the implementation. + * @return The address of the implementation contract. + */ + function getImplementation(TokenStandard standard, uint256 implId) external view returns (address) { + return implementations[standard][implId]; + } + + /** + * @dev Internal function to authorize an upgrade. + * @param newImplementation Address of the new implementation. + * @notice Only the contract owner can upgrade the contract. + */ + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} diff --git a/contracts/common/Structs.sol b/contracts/common/Structs.sol new file mode 100644 index 00000000..616b714b --- /dev/null +++ b/contracts/common/Structs.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +enum TokenStandard { + ERC721, + ERC1155 +} \ No newline at end of file diff --git a/contracts/factory/MagicDropCloneFactory.sol b/contracts/factory/MagicDropCloneFactory.sol new file mode 100644 index 00000000..ea395271 --- /dev/null +++ b/contracts/factory/MagicDropCloneFactory.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {IMagicDropCloneFactory} from "../interfaces/IMagicDropCloneFactory.sol"; +import {IMagicDropTokenImplRegistry} from "../interfaces/IMagicDropTokenImplRegistry.sol"; +import {IInitializableToken} from "../interfaces/IInitializableToken.sol"; +import {TokenStandard} from "../common/Structs.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; + +contract MagicDropCloneFactory is + IMagicDropCloneFactory, + Ownable2StepUpgradeable, + UUPSUpgradeable +{ + IMagicDropTokenImplRegistry public immutable registry; + + error ImplementationNotRegistered(); + + constructor( + IMagicDropTokenImplRegistry _registry + ) { + if (address(_registry) == address(0)) { + revert ConstructorRegistryAddressCannotBeZero(); + } + + registry = _registry; + } + + function initialize() public initializer { + __Ownable2Step_init(); + __Ownable_init(msg.sender); + __UUPSUpgradeable_init(); + emit MagicDropFactoryInitialized(); + } + + function createContract( + string calldata name, + string calldata symbol, + TokenStandard standard, + address payable initialOwner, + uint256 implId, + bytes32 salt + ) external override returns (address) { + address impl = registry.getImplementation(standard, implId); + + if (impl == address(0)) { + revert ImplementationNotRegistered(); + } + + bytes32 cloneSalt = keccak256(abi.encodePacked(salt, blockhash(block.number))); + + address instance = Clones.cloneDeterministic(impl, cloneSalt); + + IInitializableToken(instance).initialize( + name, + symbol, + initialOwner + ); + + emit NewContractInitialized({ + contractAddress: instance, + initialOwner: initialOwner, + implId: implId, + standard: standard, + name: name, + symbol: symbol + }); + + return instance; + } + + function _authorizeUpgrade( + address newImplementation + ) internal override onlyOwner {} +} diff --git a/contracts/interfaces/IInitializableToken.sol b/contracts/interfaces/IInitializableToken.sol new file mode 100644 index 00000000..145bb0d7 --- /dev/null +++ b/contracts/interfaces/IInitializableToken.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface IInitializableToken { + function initialize( + string calldata name, + string calldata symbol, + address payable initialOwner + ) external; +} diff --git a/contracts/interfaces/IMagicDropCloneFactory.sol b/contracts/interfaces/IMagicDropCloneFactory.sol new file mode 100644 index 00000000..c50a5fed --- /dev/null +++ b/contracts/interfaces/IMagicDropCloneFactory.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {TokenStandard} from "../common/Structs.sol"; + +interface IMagicDropCloneFactory { + error ConstructorRegistryAddressCannotBeZero(); + + event MagicDropFactoryInitialized(); + event NewContractInitialized( + string name, + string symbol, + address indexed contractAddress, + address indexed initialOwner, + uint256 implId, + TokenStandard standard + ); + + function createContract( + string calldata name, + string calldata symbol, + TokenStandard standard, + address payable initialOwner, + uint256 implId, + bytes32 salt + ) external returns (address); +} diff --git a/contracts/interfaces/IMagicDropTokenImplRegistry.sol b/contracts/interfaces/IMagicDropTokenImplRegistry.sol new file mode 100644 index 00000000..775705a8 --- /dev/null +++ b/contracts/interfaces/IMagicDropTokenImplRegistry.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {TokenStandard} from "../common/Structs.sol"; + +interface IMagicDropTokenImplRegistry { + event ImplementationRegistered(TokenStandard standard, address impl, uint256 implId); + event ImplementationUnregistered(TokenStandard standard, uint256 implId); + + error ImplementationNotRegistered(); + + function registerImplementation(TokenStandard standard, address impl) external returns (uint256); + function unregisterImplementation(TokenStandard standard, uint256 implId) external; + function getImplementation(TokenStandard standard, uint256 implId) external view returns (address); +} diff --git a/contracts/nft/MagicDropERC1155Initializable.sol b/contracts/nft/MagicDropERC1155Initializable.sol new file mode 100644 index 00000000..cf6bdd24 --- /dev/null +++ b/contracts/nft/MagicDropERC1155Initializable.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {IInitializableToken} from "../interfaces/IInitializableToken.sol"; + +contract MagicDropERC1155Initializable is ERC1155Upgradeable, OwnableUpgradeable, IInitializableToken { + string private _name; + string private _symbol; + + function initialize( + string calldata name_, + string calldata symbol_, + address payable initialOwner + ) external initializer override { + _name = name_; + _symbol = symbol_; + __ERC1155_init(""); + __Ownable_init(initialOwner); + } + + function name() external view returns (string memory) { + return _name; + } + + function symbol() external view returns (string memory) { + return _symbol; + } + + function setURI(string calldata newuri) external onlyOwner { + _setURI(newuri); + } + + function mint( + address to, + uint256 id, + uint256 amount, + bytes memory data + ) external { + _mint(to, id, amount, data); + } +} diff --git a/contracts/nft/MagicDropERC721Initializable.sol b/contracts/nft/MagicDropERC721Initializable.sol new file mode 100644 index 00000000..7a330e0c --- /dev/null +++ b/contracts/nft/MagicDropERC721Initializable.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {IInitializableToken} from "../interfaces/IInitializableToken.sol"; + +contract MagicDropERC721Initializable is ERC721Upgradeable, OwnableUpgradeable, IInitializableToken { + string private baseURI = ""; + + function initialize( + string memory name, + string memory symbol, + address payable initialOwner + ) external initializer override { + __ERC721_init(name, symbol); + __Ownable_init(initialOwner); + } + + function _baseURI() internal view virtual override returns (string memory) { + return baseURI; + } + + function mint(address to, uint256 tokenId) external onlyOwner { + _mint(to, tokenId); + } +} diff --git a/test/foundry/MagicDropCloneFactoryTest.t.sol b/test/foundry/MagicDropCloneFactoryTest.t.sol new file mode 100644 index 00000000..b29d6e31 --- /dev/null +++ b/test/foundry/MagicDropCloneFactoryTest.t.sol @@ -0,0 +1,130 @@ +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "../../contracts/factory/MagicDropCloneFactory.sol"; +import "../../contracts/MagicDropTokenImplRegistry.sol"; +import "../../contracts/nft/MagicDropERC721Initializable.sol"; +import "../../contracts/nft/MagicDropERC1155Initializable.sol"; +import {TokenStandard} from "../../contracts/common/Structs.sol"; + +contract MagicDropCloneFactoryTest is Test { + MagicDropCloneFactory factory; + MagicDropTokenImplRegistry registry; + MagicDropERC721Initializable erc721Implementation; + MagicDropERC1155Initializable erc1155Implementation; + address owner = address(0x1); + address user = address(0x2); + + uint256 erc721ImplId; + uint256 erc1155ImplId; + + function setUp() public { + vm.startPrank(owner); + + // Deploy and initialize registry + registry = new MagicDropTokenImplRegistry(); + registry.initialize(); + + // Deploy factory + factory = new MagicDropCloneFactory(registry); + factory.initialize(); + + // Deploy implementations + erc721Implementation = new MagicDropERC721Initializable(); + erc1155Implementation = new MagicDropERC1155Initializable(); + + // Register implementations + erc721ImplId = registry.registerImplementation(TokenStandard.ERC721, address(erc721Implementation)); + erc1155ImplId = registry.registerImplementation(TokenStandard.ERC1155, address(erc1155Implementation)); + + vm.stopPrank(); + } + + function testCreateERC721Contract() public { + vm.startPrank(user); + + address newContract = factory.createContract( + "TestNFT", + "TNFT", + TokenStandard.ERC721, + payable(user), + erc721ImplId, + bytes32(0) + ); + + MagicDropERC721Initializable nft = MagicDropERC721Initializable(newContract); + assertEq(nft.name(), "TestNFT"); + assertEq(nft.symbol(), "TNFT"); + assertEq(nft.owner(), user); + + // Test minting + nft.mint(user, 1); + assertEq(nft.ownerOf(1), user); + + vm.stopPrank(); + } + + function testCreateERC1155Contract() public { + vm.startPrank(user); + + address newContract = factory.createContract( + "TestMultiToken", + "TMT", + TokenStandard.ERC1155, + payable(user), + erc1155ImplId, + bytes32(0) + ); + + MagicDropERC1155Initializable multiToken = MagicDropERC1155Initializable(newContract); + assertEq(multiToken.name(), "TestMultiToken"); + assertEq(multiToken.symbol(), "TMT"); + assertEq(multiToken.owner(), user); + + // Test minting + multiToken.mint(user, 1, 100, ""); + assertEq(multiToken.balanceOf(user, 1), 100); + + vm.stopPrank(); + } + + function testCreateContractWithDifferentSalts() public { + vm.startPrank(user); + + address contract1 = factory.createContract( + "TestNFT1", + "TNFT1", + TokenStandard.ERC721, + payable(user), + erc721ImplId, + bytes32(uint256(1)) + ); + + address contract2 = factory.createContract( + "TestNFT2", + "TNFT2", + TokenStandard.ERC721, + payable(user), + erc721ImplId, + bytes32(uint256(2)) + ); + + assertTrue(contract1 != contract2); + + vm.stopPrank(); + } + + function testFailCreateContractWithInvalidImplementation() public { + uint256 invalidImplId = 999; + + vm.prank(user); + factory.createContract( + "TestNFT", + "TNFT", + TokenStandard.ERC721, + payable(user), + invalidImplId, + bytes32(0) + ); + } +} diff --git a/test/foundry/MagicDropTokenImplRegistryTest.t.sol b/test/foundry/MagicDropTokenImplRegistryTest.t.sol new file mode 100644 index 00000000..9396d03b --- /dev/null +++ b/test/foundry/MagicDropTokenImplRegistryTest.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "../../contracts/MagicDropTokenImplRegistry.sol"; +import {TokenStandard} from "../../contracts/common/Structs.sol"; + +contract MockImplementation {} + +contract MagicDropTokenImplRegistryTest is Test { + MagicDropTokenImplRegistry registry; + address owner = address(0x1); + address user = address(0x2); + MockImplementation mockImpl; + + function setUp() public { + vm.startPrank(owner); + registry = new MagicDropTokenImplRegistry(); + registry.initialize(); + vm.stopPrank(); + mockImpl = new MockImplementation(); + } + + function testRegisterImplementation() public { + vm.prank(owner); + uint256 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); + assertEq(implId, 1); + assertEq(registry.getImplementation(TokenStandard.ERC721, implId), address(mockImpl)); + } + + function testRegisterMultipleImplementations() public { + vm.startPrank(owner); + uint256 implId1 = registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); + uint256 implId2 = registry.registerImplementation(TokenStandard.ERC721, address(0x3)); + vm.stopPrank(); + + assertEq(implId1, 1); + assertEq(implId2, 2); + assertEq(registry.getImplementation(TokenStandard.ERC721, implId1), address(mockImpl)); + assertEq(registry.getImplementation(TokenStandard.ERC721, implId2), address(0x3)); + } + + function testUnregisterImplementation() public { + vm.startPrank(owner); + uint256 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); + registry.unregisterImplementation(TokenStandard.ERC721, implId); + vm.stopPrank(); + + assertEq(registry.getImplementation(TokenStandard.ERC721, implId), address(0)); + } + + function testFailUnregisterNonExistentImplementation() public { + vm.prank(owner); + registry.unregisterImplementation(TokenStandard.ERC721, 0); + } + + function testFailRegisterImplementationAsNonOwner() public { + vm.prank(user); + registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); + } + + function testFailUnregisterImplementationAsNonOwner() public { + vm.prank(owner); + uint256 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); + + vm.prank(user); + registry.unregisterImplementation(TokenStandard.ERC721, implId); + } +} From 541be9c561ca2ac89a527833d151895528f3a4e0 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 20 Sep 2024 11:52:27 -0400 Subject: [PATCH 020/145] cleanup Signed-off-by: Wolfy --- test/foundry/MagicDropCloneFactoryTest.t.sol | 29 ++++++++++--------- .../MagicDropTokenImplRegistryTest.t.sol | 17 +++++------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/test/foundry/MagicDropCloneFactoryTest.t.sol b/test/foundry/MagicDropCloneFactoryTest.t.sol index b29d6e31..356c95be 100644 --- a/test/foundry/MagicDropCloneFactoryTest.t.sol +++ b/test/foundry/MagicDropCloneFactoryTest.t.sol @@ -1,22 +1,23 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "forge-std/Test.sol"; -import "../../contracts/factory/MagicDropCloneFactory.sol"; -import "../../contracts/MagicDropTokenImplRegistry.sol"; -import "../../contracts/nft/MagicDropERC721Initializable.sol"; -import "../../contracts/nft/MagicDropERC1155Initializable.sol"; +import {Test} from "forge-std/Test.sol"; +import {MagicDropCloneFactory} from "../../contracts/factory/MagicDropCloneFactory.sol"; +import {MagicDropTokenImplRegistry} from "../../contracts/MagicDropTokenImplRegistry.sol"; +import {MagicDropERC721Initializable} from "../../contracts/nft/MagicDropERC721Initializable.sol"; +import {MagicDropERC1155Initializable} from "../../contracts/nft/MagicDropERC1155Initializable.sol"; import {TokenStandard} from "../../contracts/common/Structs.sol"; contract MagicDropCloneFactoryTest is Test { - MagicDropCloneFactory factory; - MagicDropTokenImplRegistry registry; - MagicDropERC721Initializable erc721Implementation; - MagicDropERC1155Initializable erc1155Implementation; - address owner = address(0x1); - address user = address(0x2); - - uint256 erc721ImplId; - uint256 erc1155ImplId; + MagicDropCloneFactory internal factory; + MagicDropTokenImplRegistry internal registry; + MagicDropERC721Initializable internal erc721Implementation; + MagicDropERC1155Initializable internal erc1155Implementation; + address internal owner = address(0x1); + address internal user = address(0x2); + + uint256 internal erc721ImplId; + uint256 internal erc1155ImplId; function setUp() public { vm.startPrank(owner); diff --git a/test/foundry/MagicDropTokenImplRegistryTest.t.sol b/test/foundry/MagicDropTokenImplRegistryTest.t.sol index 9396d03b..5df6986e 100644 --- a/test/foundry/MagicDropTokenImplRegistryTest.t.sol +++ b/test/foundry/MagicDropTokenImplRegistryTest.t.sol @@ -1,24 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "forge-std/Test.sol"; -import "../../contracts/MagicDropTokenImplRegistry.sol"; +import {Test} from "forge-std/Test.sol"; +import {MagicDropTokenImplRegistry} from "../../contracts/MagicDropTokenImplRegistry.sol"; import {TokenStandard} from "../../contracts/common/Structs.sol"; - -contract MockImplementation {} +import {MockERC721A} from "../../contracts/mocks/MockERC721A.sol"; contract MagicDropTokenImplRegistryTest is Test { - MagicDropTokenImplRegistry registry; - address owner = address(0x1); - address user = address(0x2); - MockImplementation mockImpl; + MagicDropTokenImplRegistry internal registry; + address internal owner = address(0x1); + address internal user = address(0x2); + MockERC721A internal mockImpl; function setUp() public { vm.startPrank(owner); registry = new MagicDropTokenImplRegistry(); registry.initialize(); vm.stopPrank(); - mockImpl = new MockImplementation(); + mockImpl = new MockERC721A(); } function testRegisterImplementation() public { From f2bc42706d461632733e36553f58375dc740b515 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 20 Sep 2024 12:53:03 -0400 Subject: [PATCH 021/145] project restructuring Signed-off-by: Wolfy --- .../interfaces/IInitializableToken.sol | 0 .../factory/ERC721CMRoyaltiesCloneFactory.sol | 2 +- contracts/factory/MagicDropCloneFactory.sol | 16 +++++++++------- .../interfaces/IMagicDropCloneFactory.sol | 2 +- contracts/mocks/ERC1155MTestReentrantExploit.sol | 2 +- contracts/mocks/TestReentrantExploit.sol | 2 +- contracts/nft/MagicDropERC1155Initializable.sol | 2 +- contracts/nft/MagicDropERC721Initializable.sol | 2 +- .../ERC721ACQueryable.sol | 0 .../ERC721ACQueryableInitializable.sol | 0 contracts/{ => nft/erc1155m}/ERC1155M.sol | 4 ++-- .../{ => nft/erc1155m/interfaces}/IERC1155M.sol | 0 contracts/{ => nft/erc721m}/ERC721CM.sol | 6 +++--- contracts/{ => nft/erc721m}/ERC721M.sol | 4 ++-- .../erc721m/extensions}/BucketAuction.sol | 2 +- .../extensions}/ERC721CMBasicRoyalties.sol | 2 +- .../extensions}/ERC721CMInitializable.sol | 8 ++++---- .../erc721m/extensions}/ERC721CMRoyalties.sol | 4 ++-- .../ERC721CMRoyaltiesInitializable.sol | 2 +- .../extensions}/ERC721MOperatorFilterer.sol | 2 +- .../erc721m/interfaces}/IBucketAuction.sol | 0 .../{ => nft/erc721m/interfaces}/IERC721M.sol | 0 .../interfaces}/IERC721MInitializable.sol | 0 .../MagicDropTokenImplRegistry.sol | 2 +- .../interfaces/IMagicDropTokenImplRegistry.sol | 2 +- test/foundry/MagicDropCloneFactoryTest.t.sol | 2 +- .../foundry/MagicDropTokenImplRegistryTest.t.sol | 2 +- 27 files changed, 36 insertions(+), 34 deletions(-) rename contracts/{ => common}/interfaces/IInitializableToken.sol (100%) rename contracts/{ => factory}/interfaces/IMagicDropCloneFactory.sol (92%) rename contracts/{ => nft}/creator-token-standards/ERC721ACQueryable.sol (100%) rename contracts/{ => nft}/creator-token-standards/ERC721ACQueryableInitializable.sol (100%) rename contracts/{ => nft/erc1155m}/ERC1155M.sol (99%) rename contracts/{ => nft/erc1155m/interfaces}/IERC1155M.sol (100%) rename contracts/{ => nft/erc721m}/ERC721CM.sol (99%) rename contracts/{ => nft/erc721m}/ERC721M.sol (99%) rename contracts/{auctions => nft/erc721m/extensions}/BucketAuction.sol (99%) rename contracts/{ => nft/erc721m/extensions}/ERC721CMBasicRoyalties.sol (98%) rename contracts/{ => nft/erc721m/extensions}/ERC721CMInitializable.sol (98%) rename contracts/{ => nft/erc721m/extensions}/ERC721CMRoyalties.sol (88%) rename contracts/{ => nft/erc721m/extensions}/ERC721CMRoyaltiesInitializable.sol (95%) rename contracts/{operator-filter => nft/erc721m/extensions}/ERC721MOperatorFilterer.sol (97%) rename contracts/{auctions => nft/erc721m/interfaces}/IBucketAuction.sol (100%) rename contracts/{ => nft/erc721m/interfaces}/IERC721M.sol (100%) rename contracts/{ => nft/erc721m/interfaces}/IERC721MInitializable.sol (100%) rename contracts/{ => registry}/MagicDropTokenImplRegistry.sol (98%) rename contracts/{ => registry}/interfaces/IMagicDropTokenImplRegistry.sol (91%) diff --git a/contracts/interfaces/IInitializableToken.sol b/contracts/common/interfaces/IInitializableToken.sol similarity index 100% rename from contracts/interfaces/IInitializableToken.sol rename to contracts/common/interfaces/IInitializableToken.sol diff --git a/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol b/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol index c8699c48..5851d28b 100644 --- a/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol +++ b/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.4; -import {ERC721CMRoyaltiesInitializable} from "../ERC721CMRoyaltiesInitializable.sol"; +import {ERC721CMRoyaltiesInitializable} from "../nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol"; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; /** diff --git a/contracts/factory/MagicDropCloneFactory.sol b/contracts/factory/MagicDropCloneFactory.sol index ea395271..5943f5ac 100644 --- a/contracts/factory/MagicDropCloneFactory.sol +++ b/contracts/factory/MagicDropCloneFactory.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import {IMagicDropCloneFactory} from "../interfaces/IMagicDropCloneFactory.sol"; -import {IMagicDropTokenImplRegistry} from "../interfaces/IMagicDropTokenImplRegistry.sol"; -import {IInitializableToken} from "../interfaces/IInitializableToken.sol"; +import {IMagicDropCloneFactory} from "./interfaces/IMagicDropCloneFactory.sol"; +import {IMagicDropTokenImplRegistry} from "../registry/interfaces/IMagicDropTokenImplRegistry.sol"; +import {IInitializableToken} from "../common/interfaces/IInitializableToken.sol"; import {TokenStandard} from "../common/Structs.sol"; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; @@ -14,7 +14,7 @@ contract MagicDropCloneFactory is Ownable2StepUpgradeable, UUPSUpgradeable { - IMagicDropTokenImplRegistry public immutable registry; + IMagicDropTokenImplRegistry public immutable REGISTRY; error ImplementationNotRegistered(); @@ -25,7 +25,7 @@ contract MagicDropCloneFactory is revert ConstructorRegistryAddressCannotBeZero(); } - registry = _registry; + REGISTRY = _registry; } function initialize() public initializer { @@ -43,7 +43,7 @@ contract MagicDropCloneFactory is uint256 implId, bytes32 salt ) external override returns (address) { - address impl = registry.getImplementation(standard, implId); + address impl = REGISTRY.getImplementation(standard, implId); if (impl == address(0)) { revert ImplementationNotRegistered(); @@ -73,5 +73,7 @@ contract MagicDropCloneFactory is function _authorizeUpgrade( address newImplementation - ) internal override onlyOwner {} + ) internal override onlyOwner { + // This function is left empty as the onlyOwner modifier handles the authorization + } } diff --git a/contracts/interfaces/IMagicDropCloneFactory.sol b/contracts/factory/interfaces/IMagicDropCloneFactory.sol similarity index 92% rename from contracts/interfaces/IMagicDropCloneFactory.sol rename to contracts/factory/interfaces/IMagicDropCloneFactory.sol index c50a5fed..3ad69d55 100644 --- a/contracts/interfaces/IMagicDropCloneFactory.sol +++ b/contracts/factory/interfaces/IMagicDropCloneFactory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {TokenStandard} from "../common/Structs.sol"; +import {TokenStandard} from "../../common/Structs.sol"; interface IMagicDropCloneFactory { error ConstructorRegistryAddressCannotBeZero(); diff --git a/contracts/mocks/ERC1155MTestReentrantExploit.sol b/contracts/mocks/ERC1155MTestReentrantExploit.sol index 3c7281b7..510be5d3 100644 --- a/contracts/mocks/ERC1155MTestReentrantExploit.sol +++ b/contracts/mocks/ERC1155MTestReentrantExploit.sol @@ -1,7 +1,7 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import "../ERC1155M.sol"; +import "../nft/erc1155m/ERC1155M.sol"; import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; contract ERC1155MTestReentrantExploit { diff --git a/contracts/mocks/TestReentrantExploit.sol b/contracts/mocks/TestReentrantExploit.sol index c9a9ccaa..e3188eda 100644 --- a/contracts/mocks/TestReentrantExploit.sol +++ b/contracts/mocks/TestReentrantExploit.sol @@ -1,7 +1,7 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import "../ERC721M.sol"; +import "../nft/erc721m/ERC721M.sol"; contract TestReentrantExploit { address private _targetContract; diff --git a/contracts/nft/MagicDropERC1155Initializable.sol b/contracts/nft/MagicDropERC1155Initializable.sol index cf6bdd24..a43253d7 100644 --- a/contracts/nft/MagicDropERC1155Initializable.sol +++ b/contracts/nft/MagicDropERC1155Initializable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import {ERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {IInitializableToken} from "../interfaces/IInitializableToken.sol"; +import {IInitializableToken} from "../common/interfaces/IInitializableToken.sol"; contract MagicDropERC1155Initializable is ERC1155Upgradeable, OwnableUpgradeable, IInitializableToken { string private _name; diff --git a/contracts/nft/MagicDropERC721Initializable.sol b/contracts/nft/MagicDropERC721Initializable.sol index 7a330e0c..1f9062c2 100644 --- a/contracts/nft/MagicDropERC721Initializable.sol +++ b/contracts/nft/MagicDropERC721Initializable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {IInitializableToken} from "../interfaces/IInitializableToken.sol"; +import {IInitializableToken} from "../common/interfaces/IInitializableToken.sol"; contract MagicDropERC721Initializable is ERC721Upgradeable, OwnableUpgradeable, IInitializableToken { string private baseURI = ""; diff --git a/contracts/creator-token-standards/ERC721ACQueryable.sol b/contracts/nft/creator-token-standards/ERC721ACQueryable.sol similarity index 100% rename from contracts/creator-token-standards/ERC721ACQueryable.sol rename to contracts/nft/creator-token-standards/ERC721ACQueryable.sol diff --git a/contracts/creator-token-standards/ERC721ACQueryableInitializable.sol b/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol similarity index 100% rename from contracts/creator-token-standards/ERC721ACQueryableInitializable.sol rename to contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol diff --git a/contracts/ERC1155M.sol b/contracts/nft/erc1155m/ERC1155M.sol similarity index 99% rename from contracts/ERC1155M.sol rename to contracts/nft/erc1155m/ERC1155M.sol index d574a53c..a9d4e7da 100644 --- a/contracts/ERC1155M.sol +++ b/contracts/nft/erc1155m/ERC1155M.sol @@ -10,10 +10,10 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "./utils/Constants.sol"; +import "../../utils/Constants.sol"; import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; -import "./IERC1155M.sol"; +import "./interfaces/IERC1155M.sol"; /** * @title ERC1155M diff --git a/contracts/IERC1155M.sol b/contracts/nft/erc1155m/interfaces/IERC1155M.sol similarity index 100% rename from contracts/IERC1155M.sol rename to contracts/nft/erc1155m/interfaces/IERC1155M.sol diff --git a/contracts/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol similarity index 99% rename from contracts/ERC721CM.sol rename to contracts/nft/erc721m/ERC721CM.sol index fde741b9..e65510c1 100644 --- a/contracts/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -10,9 +10,9 @@ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import "./creator-token-standards/ERC721ACQueryable.sol"; -import "./IERC721M.sol"; -import "./utils/Constants.sol"; +import "../creator-token-standards/ERC721ACQueryable.sol"; +import "./interfaces/IERC721M.sol"; +import "../../utils/Constants.sol"; /** * @title ERC721CM diff --git a/contracts/ERC721M.sol b/contracts/nft/erc721m/ERC721M.sol similarity index 99% rename from contracts/ERC721M.sol rename to contracts/nft/erc721m/ERC721M.sol index c0ca8e58..ff539476 100644 --- a/contracts/ERC721M.sol +++ b/contracts/nft/erc721m/ERC721M.sol @@ -10,8 +10,8 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "erc721a/contracts/extensions/ERC721AQueryable.sol"; -import "./IERC721M.sol"; -import "./utils/Constants.sol"; +import "./interfaces/IERC721M.sol"; +import "../../utils/Constants.sol"; /** * @title ERC721M diff --git a/contracts/auctions/BucketAuction.sol b/contracts/nft/erc721m/extensions/BucketAuction.sol similarity index 99% rename from contracts/auctions/BucketAuction.sol rename to contracts/nft/erc721m/extensions/BucketAuction.sol index 748ccc0f..49c583eb 100644 --- a/contracts/auctions/BucketAuction.sol +++ b/contracts/nft/erc721m/extensions/BucketAuction.sol @@ -8,7 +8,7 @@ import "@openzeppelin/contracts/token/common/ERC2981.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "erc721a/contracts/ERC721A.sol"; -import "./IBucketAuction.sol"; +import "../interfaces/IBucketAuction.sol"; import "../ERC721M.sol"; contract BucketAuction is IBucketAuction, ERC721M { diff --git a/contracts/ERC721CMBasicRoyalties.sol b/contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol similarity index 98% rename from contracts/ERC721CMBasicRoyalties.sol rename to contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol index 6e8de78c..4a3fbf1a 100644 --- a/contracts/ERC721CMBasicRoyalties.sol +++ b/contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.4; import "@limitbreak/creator-token-standards/src/programmable-royalties/BasicRoyalties.sol"; -import "./ERC721CM.sol"; +import "../ERC721CM.sol"; /** * @title ERC721CM with BasicRoyalties diff --git a/contracts/ERC721CMInitializable.sol b/contracts/nft/erc721m/extensions/ERC721CMInitializable.sol similarity index 98% rename from contracts/ERC721CMInitializable.sol rename to contracts/nft/erc721m/extensions/ERC721CMInitializable.sol index 97183e87..2809f4da 100644 --- a/contracts/ERC721CMInitializable.sol +++ b/contracts/nft/erc721m/extensions/ERC721CMInitializable.sol @@ -9,10 +9,10 @@ import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./creator-token-standards/ERC721ACQueryableInitializable.sol"; -import "./access/OwnableInitializable.sol"; -import "./IERC721MInitializable.sol"; -import "./utils/Constants.sol"; +import "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; +import "../../../access/OwnableInitializable.sol"; +import "../interfaces/IERC721MInitializable.sol"; +import "../../../utils/Constants.sol"; /** * @title ERC721CMInitializable diff --git a/contracts/ERC721CMRoyalties.sol b/contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol similarity index 88% rename from contracts/ERC721CMRoyalties.sol rename to contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol index 6a7ab1ad..202d9d92 100644 --- a/contracts/ERC721CMRoyalties.sol +++ b/contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.4; -import {ERC2981, UpdatableRoyalties} from "./royalties/UpdatableRoyalties.sol"; -import {ERC721CM, ERC721ACQueryable, IERC721A} from "./ERC721CM.sol"; +import {ERC2981, UpdatableRoyalties} from "../../../royalties/UpdatableRoyalties.sol"; +import {ERC721CM, ERC721ACQueryable, IERC721A} from "../ERC721CM.sol"; /** * @title ERC721CMRoyalties diff --git a/contracts/ERC721CMRoyaltiesInitializable.sol b/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol similarity index 95% rename from contracts/ERC721CMRoyaltiesInitializable.sol rename to contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol index 288ec402..bdaaadb1 100644 --- a/contracts/ERC721CMRoyaltiesInitializable.sol +++ b/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.4; -import {ERC2981, UpdatableRoyaltiesInitializable} from "./royalties/UpdatableRoyaltiesInitializable.sol"; +import {ERC2981, UpdatableRoyaltiesInitializable} from "../../../royalties/UpdatableRoyaltiesInitializable.sol"; import {ERC721ACQueryableInitializable, ERC721CMInitializable, IERC721AUpgradeable, OwnableInitializable} from "./ERC721CMInitializable.sol"; /** diff --git a/contracts/operator-filter/ERC721MOperatorFilterer.sol b/contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol similarity index 97% rename from contracts/operator-filter/ERC721MOperatorFilterer.sol rename to contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol index 36573440..c66a4c16 100644 --- a/contracts/operator-filter/ERC721MOperatorFilterer.sol +++ b/contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.4; import {ERC721A, ERC721M, IERC721A, Ownable} from "../ERC721M.sol"; import {UpdatableOperatorFilterer} from "operator-filter-registry/src/UpdatableOperatorFilterer.sol"; -import {CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS, ME_SUBSCRIPTION} from "../utils/Constants.sol"; +import {CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS, ME_SUBSCRIPTION} from "../../../utils/Constants.sol"; contract ERC721MOperatorFilterer is ERC721M, UpdatableOperatorFilterer { constructor( diff --git a/contracts/auctions/IBucketAuction.sol b/contracts/nft/erc721m/interfaces/IBucketAuction.sol similarity index 100% rename from contracts/auctions/IBucketAuction.sol rename to contracts/nft/erc721m/interfaces/IBucketAuction.sol diff --git a/contracts/IERC721M.sol b/contracts/nft/erc721m/interfaces/IERC721M.sol similarity index 100% rename from contracts/IERC721M.sol rename to contracts/nft/erc721m/interfaces/IERC721M.sol diff --git a/contracts/IERC721MInitializable.sol b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol similarity index 100% rename from contracts/IERC721MInitializable.sol rename to contracts/nft/erc721m/interfaces/IERC721MInitializable.sol diff --git a/contracts/MagicDropTokenImplRegistry.sol b/contracts/registry/MagicDropTokenImplRegistry.sol similarity index 98% rename from contracts/MagicDropTokenImplRegistry.sol rename to contracts/registry/MagicDropTokenImplRegistry.sol index 949328ce..941b6cad 100644 --- a/contracts/MagicDropTokenImplRegistry.sol +++ b/contracts/registry/MagicDropTokenImplRegistry.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import "./interfaces/IMagicDropTokenImplRegistry.sol"; -import {TokenStandard} from "./common/Structs.sol"; +import {TokenStandard} from "../common/Structs.sol"; /** * @title MagicDropTokenImplRegistry diff --git a/contracts/interfaces/IMagicDropTokenImplRegistry.sol b/contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol similarity index 91% rename from contracts/interfaces/IMagicDropTokenImplRegistry.sol rename to contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol index 775705a8..71d4149f 100644 --- a/contracts/interfaces/IMagicDropTokenImplRegistry.sol +++ b/contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {TokenStandard} from "../common/Structs.sol"; +import {TokenStandard} from "../../common/Structs.sol"; interface IMagicDropTokenImplRegistry { event ImplementationRegistered(TokenStandard standard, address impl, uint256 implId); diff --git a/test/foundry/MagicDropCloneFactoryTest.t.sol b/test/foundry/MagicDropCloneFactoryTest.t.sol index 356c95be..94acee91 100644 --- a/test/foundry/MagicDropCloneFactoryTest.t.sol +++ b/test/foundry/MagicDropCloneFactoryTest.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {MagicDropCloneFactory} from "../../contracts/factory/MagicDropCloneFactory.sol"; -import {MagicDropTokenImplRegistry} from "../../contracts/MagicDropTokenImplRegistry.sol"; +import {MagicDropTokenImplRegistry} from "../../contracts/registry/MagicDropTokenImplRegistry.sol"; import {MagicDropERC721Initializable} from "../../contracts/nft/MagicDropERC721Initializable.sol"; import {MagicDropERC1155Initializable} from "../../contracts/nft/MagicDropERC1155Initializable.sol"; import {TokenStandard} from "../../contracts/common/Structs.sol"; diff --git a/test/foundry/MagicDropTokenImplRegistryTest.t.sol b/test/foundry/MagicDropTokenImplRegistryTest.t.sol index 5df6986e..ea0473bb 100644 --- a/test/foundry/MagicDropTokenImplRegistryTest.t.sol +++ b/test/foundry/MagicDropTokenImplRegistryTest.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; -import {MagicDropTokenImplRegistry} from "../../contracts/MagicDropTokenImplRegistry.sol"; +import {MagicDropTokenImplRegistry} from "../../contracts/registry/MagicDropTokenImplRegistry.sol"; import {TokenStandard} from "../../contracts/common/Structs.sol"; import {MockERC721A} from "../../contracts/mocks/MockERC721A.sol"; From 4c2c2f953444eb683011d6f8f28ff2ef7ec9be55 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 20 Sep 2024 16:09:12 -0400 Subject: [PATCH 022/145] test initializable v2 Signed-off-by: Wolfy --- contracts/common/Structs.sol | 10 + contracts/nft/erc721m/ERC721CM.sol | 2 + .../ERC721CMInitializable.sol | 23 +- contracts/nft/erc721m/ERC721M.sol | 2 + .../ERC721CMRoyaltiesInitializable.sol | 15 +- .../interfaces/IERC721MInitializable.sol | 12 +- .../erc721m/v2/ERC721CMInitializable_V2.sol | 575 ++++++++++++++++++ .../ERC721CMInitializable_V2Test.t.sol | 197 ++++++ 8 files changed, 807 insertions(+), 29 deletions(-) rename contracts/nft/erc721m/{extensions => }/ERC721CMInitializable.sol (95%) create mode 100644 contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol create mode 100644 test/erc721m/ERC721CMInitializable_V2Test.t.sol diff --git a/contracts/common/Structs.sol b/contracts/common/Structs.sol index 616b714b..04525ebc 100644 --- a/contracts/common/Structs.sol +++ b/contracts/common/Structs.sol @@ -4,4 +4,14 @@ pragma solidity ^0.8.19; enum TokenStandard { ERC721, ERC1155 +} + +struct MintStageInfo { + uint80 price; + uint80 mintFee; + uint32 walletLimit; // 0 for unlimited + bytes32 merkleRoot; // 0x0 for no presale enforced + uint24 maxStageSupply; // 0 for unlimited + uint64 startTimeUnixSeconds; + uint64 endTimeUnixSeconds; } \ No newline at end of file diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index e65510c1..8324c702 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -77,6 +77,8 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard { // Authorized minters mapping(address => bool) private _authorizedMinters; + uint256 public constant VERSION = 1; + constructor( string memory collectionName, string memory collectionSymbol, diff --git a/contracts/nft/erc721m/extensions/ERC721CMInitializable.sol b/contracts/nft/erc721m/ERC721CMInitializable.sol similarity index 95% rename from contracts/nft/erc721m/extensions/ERC721CMInitializable.sol rename to contracts/nft/erc721m/ERC721CMInitializable.sol index 2809f4da..c03d48b3 100644 --- a/contracts/nft/erc721m/extensions/ERC721CMInitializable.sol +++ b/contracts/nft/erc721m/ERC721CMInitializable.sol @@ -9,10 +9,10 @@ import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; -import "../../../access/OwnableInitializable.sol"; -import "../interfaces/IERC721MInitializable.sol"; -import "../../../utils/Constants.sol"; +import "../creator-token-standards/ERC721ACQueryableInitializable.sol"; +import "../../access/OwnableInitializable.sol"; +import "./interfaces/IERC721MInitializable.sol"; +import "../../utils/Constants.sol"; /** * @title ERC721CMInitializable @@ -71,6 +71,8 @@ abstract contract ERC721CMInitializable is // Fund receiver address public FUND_RECEIVER; + uint256 public constant VERSION = 1; + function __ERC721CMInitializable_init( string memory collectionName, string memory collectionSymbol, @@ -571,4 +573,17 @@ abstract contract ERC721CMInitializable is { _checkOwner(); } + + /** + * @notice Returns the function selector for the transfer validator's validation function to be called + * @notice for transaction simulation. + */ + function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { + functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); + isViewFunction = true; + } + + function _tokenType() internal pure override returns(uint16) { + return uint16(721); + } } diff --git a/contracts/nft/erc721m/ERC721M.sol b/contracts/nft/erc721m/ERC721M.sol index ff539476..c6198e77 100644 --- a/contracts/nft/erc721m/ERC721M.sol +++ b/contracts/nft/erc721m/ERC721M.sol @@ -76,6 +76,8 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { // Authorized minters mapping(address => bool) private _authorizedMinters; + uint256 public constant VERSION = 1; + constructor( string memory collectionName, string memory collectionSymbol, diff --git a/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol b/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol index bdaaadb1..d33fcf34 100644 --- a/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol +++ b/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.4; import {ERC2981, UpdatableRoyaltiesInitializable} from "../../../royalties/UpdatableRoyaltiesInitializable.sol"; -import {ERC721ACQueryableInitializable, ERC721CMInitializable, IERC721AUpgradeable, OwnableInitializable} from "./ERC721CMInitializable.sol"; +import {ERC721ACQueryableInitializable, ERC721CMInitializable, IERC721AUpgradeable, OwnableInitializable} from "../ERC721CMInitializable.sol"; /** * @title ERC721CMRoyaltiesInitializable @@ -65,17 +65,4 @@ contract ERC721CMRoyaltiesInitializable is { _checkOwner(); } - - /** - * @notice Returns the function selector for the transfer validator's validation function to be called - * @notice for transaction simulation. - */ - function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { - functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); - isViewFunction = true; - } - - function _tokenType() internal pure override returns(uint16) { - return uint16(721); - } } diff --git a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol index 32ff96ae..bac6cbea 100644 --- a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol +++ b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.4; import "erc721a-upgradeable/contracts/extensions/IERC721AQueryableUpgradeable.sol"; - +import "../../../common/Structs.sol"; /** * @title IERC721MInitializable * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this @@ -33,16 +33,6 @@ interface IERC721MInitializable is IERC721AQueryableUpgradeable { error WrongMintCurrency(); error NotSupported(); - struct MintStageInfo { - uint80 price; - uint80 mintFee; - uint32 walletLimit; // 0 for unlimited - bytes32 merkleRoot; // 0x0 for no presale enforced - uint24 maxStageSupply; // 0 for unlimited - uint64 startTimeUnixSeconds; - uint64 endTimeUnixSeconds; - } - event UpdateStage( uint256 stage, uint80 price, diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol new file mode 100644 index 00000000..cafda983 --- /dev/null +++ b/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol @@ -0,0 +1,575 @@ +//SPDX-License-Identifier: MIT + +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts/token/common/ERC2981.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; +import "../../../access/OwnableInitializable.sol"; +import "../interfaces/IERC721MInitializable.sol"; +import "../../../utils/Constants.sol"; +import "../../../common/interfaces/IInitializableToken.sol"; + +/** + * @title ERC721CMInitializable + * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this + * contract is for use with EIP-1167 Minimal Proxies (Clones). + */ +contract ERC721CMInitializable_V2 is + IERC721MInitializable, + ERC721ACQueryableInitializable, + OwnableInitializable, + ReentrancyGuard, + IInitializableToken +{ + using ECDSA for bytes32; + using SafeERC20 for IERC20; + + // Whether this contract is mintable. + bool private _mintable; + + // Specify how long a signature from cosigner is valid for, recommend 300 seconds. + uint64 private _timestampExpirySeconds; + + // The crossmint address. Need to set if using crossmint. + address private _crossmintAddress; + + // The total mintable supply. + uint256 internal _maxMintableSupply; + + // Global wallet limit, across all stages. + uint256 private _globalWalletLimit; + + // Current base URI. + string private _currentBaseURI; + + // The suffix for the token URL, e.g. ".json". + string private _tokenURISuffix; + + // The uri for the storefront-level metadata for better indexing. e.g. "ipfs://UyNGgv3jx2HHfBjQX9RnKtxj2xv2xQDtbVXoRi5rJ31234" + string private _contractURI; + + // Mint stage infomation. See MintStageInfo for details. + MintStageInfo[] private _mintStages; + + // Minted count per stage per wallet. + mapping(uint256 => mapping(address => uint32)) + private _stageMintedCountsPerWallet; + + // Minted count per stage. + mapping(uint256 => uint256) private _stageMintedCounts; + + // Address of ERC-20 token used to pay for minting. If 0 address, use native currency. + address private _mintCurrency; + + // Total mint fee + uint256 private _totalMintFee; + + // Fund receiver + address public FUND_RECEIVER; + + uint256 public constant VERSION = 2; + + function initialize( + string calldata name, + string calldata symbol, + address payable initialOwner + ) external override initializerERC721A initializer { + __ERC721ACQueryableInitializable_init(name, symbol); + } + + function setup( + string calldata tokenURISuffix, + uint256 maxMintableSupply, + uint256 globalWalletLimit, + uint64 timestampExpirySeconds, + address mintCurrency, + address fundReceiver, + address crossmintAddress, + MintStageInfo[] calldata initialStages + ) external onlyOwner { + if (globalWalletLimit > maxMintableSupply) + revert GlobalWalletLimitOverflow(); + _mintable = true; + _maxMintableSupply = maxMintableSupply; + _globalWalletLimit = globalWalletLimit; + _tokenURISuffix = tokenURISuffix; + _timestampExpirySeconds = timestampExpirySeconds; + _mintCurrency = mintCurrency; + FUND_RECEIVER = fundReceiver; + + // Set crossmint address + _crossmintAddress = crossmintAddress; + emit SetCrossmintAddress(crossmintAddress); + + // Set initial stages + if (initialStages.length > 0) { + _setStages(initialStages); + } + } + + function _setStages(MintStageInfo[] calldata newStages) internal { + delete _mintStages; + + for (uint256 i = 0; i < newStages.length; ) { + if (i >= 1) { + if ( + newStages[i].startTimeUnixSeconds < + newStages[i - 1].endTimeUnixSeconds + + _timestampExpirySeconds + ) { + revert InsufficientStageTimeGap(); + } + } + _assertValidStartAndEndTimestamp( + newStages[i].startTimeUnixSeconds, + newStages[i].endTimeUnixSeconds + ); + _mintStages.push( + MintStageInfo({ + price: newStages[i].price, + mintFee: newStages[i].mintFee, + walletLimit: newStages[i].walletLimit, + merkleRoot: newStages[i].merkleRoot, + maxStageSupply: newStages[i].maxStageSupply, + startTimeUnixSeconds: newStages[i].startTimeUnixSeconds, + endTimeUnixSeconds: newStages[i].endTimeUnixSeconds + }) + ); + emit UpdateStage( + i, + newStages[i].price, + newStages[i].mintFee, + newStages[i].walletLimit, + newStages[i].merkleRoot, + newStages[i].maxStageSupply, + newStages[i].startTimeUnixSeconds, + newStages[i].endTimeUnixSeconds + ); + + unchecked { + ++i; + } + } + } + + function setStages(MintStageInfo[] calldata newStages) external onlyOwner { + _setStages(newStages); + } + + /** + * @dev Returns whether mintable. + */ + modifier canMint() { + if (!_mintable) revert NotMintable(); + _; + } + + /** + * @dev Returns whether it has enough supply for the given qty. + */ + modifier hasSupply(uint256 qty) { + if (totalSupply() + qty > _maxMintableSupply) revert NoSupplyLeft(); + _; + } + + /** + * @dev Returns cosign nonce. + */ + function getCosignNonce(address minter) public view returns (uint256) { + return _numberMinted(minter); + } + + /** + * @dev Gets whether mintable. + */ + function getMintable() external view returns (bool) { + return _mintable; + } + + /** + * @dev Sets mintable. + */ + function setMintable(bool mintable) external onlyOwner { + _mintable = mintable; + emit SetMintable(mintable); + } + + /** + * @dev Returns number of stages. + */ + function getNumberStages() external view override returns (uint256) { + return _mintStages.length; + } + + /** + * @dev Returns maximum mintable supply. + */ + function getMaxMintableSupply() external view override returns (uint256) { + return _maxMintableSupply; + } + + /** + * @dev Sets maximum mintable supply. + * + * New supply cannot be larger than the old. + */ + function setMaxMintableSupply( + uint256 maxMintableSupply + ) external virtual onlyOwner { + if (maxMintableSupply > _maxMintableSupply) { + revert CannotIncreaseMaxMintableSupply(); + } + _maxMintableSupply = maxMintableSupply; + emit SetMaxMintableSupply(maxMintableSupply); + } + + /** + * @dev Returns global wallet limit. This is the max number of tokens can be minted by one wallet. + */ + function getGlobalWalletLimit() external view override returns (uint256) { + return _globalWalletLimit; + } + + /** + * @dev Sets global wallet limit. + */ + function setGlobalWalletLimit( + uint256 globalWalletLimit + ) external onlyOwner { + if (globalWalletLimit > _maxMintableSupply) + revert GlobalWalletLimitOverflow(); + _globalWalletLimit = globalWalletLimit; + emit SetGlobalWalletLimit(globalWalletLimit); + } + + /** + * @dev Returns number of minted token for a given address. + */ + function totalMintedByAddress( + address a + ) external view virtual override returns (uint256) { + return _numberMinted(a); + } + + /** + * @dev Returns info for one stage specified by index (starting from 0). + */ + function getStageInfo( + uint256 index + ) external view override returns (MintStageInfo memory, uint32, uint256) { + if (index >= _mintStages.length) { + revert("InvalidStage"); + } + uint32 walletMinted = _stageMintedCountsPerWallet[index][msg.sender]; + uint256 stageMinted = _stageMintedCounts[index]; + return (_mintStages[index], walletMinted, stageMinted); + } + + /** + * @dev Returns mint currency address. + */ + function getMintCurrency() external view returns (address) { + return _mintCurrency; + } + + /** + * @dev Mints token(s). + * + * qty - number of tokens to mint + * proof - the merkle proof generated on client side. This applies if using whitelist. + * timestamp - the current timestamp + * signature - the signature from cosigner if using cosigner. + */ + function mint( + uint32 qty, + bytes32[] calldata proof, + uint64 timestamp, + bytes calldata signature + ) external payable virtual nonReentrant { + _mintInternal(qty, msg.sender, 0, proof); + } + + /** + * @dev Mints token(s) with limit. + * + * qty - number of tokens to mint + * limit - limit for the given minter + * proof - the merkle proof generated on client side. This applies if using whitelist. + * timestamp - the current timestamp + * signature - the signature from cosigner if using cosigner. + */ + function mintWithLimit( + uint32 qty, + uint32 limit, + bytes32[] calldata proof, + uint64 timestamp, + bytes calldata signature + ) external payable virtual nonReentrant { + _mintInternal(qty, msg.sender, limit, proof); + } + + /** + * @dev Mints token(s) through crossmint. This function is supposed to be called by crossmint. + * + * qty - number of tokens to mint + * to - the address to mint tokens to + * proof - the merkle proof generated on client side. This applies if using whitelist. + * timestamp - the current timestamp + * signature - the signature from cosigner if using cosigner. + */ + function crossmint( + uint32 qty, + address to, + bytes32[] calldata proof, + uint64 timestamp, + bytes calldata signature + ) external payable nonReentrant { + if (_crossmintAddress == address(0)) revert CrossmintAddressNotSet(); + + // Check the caller is Crossmint + if (msg.sender != _crossmintAddress) revert CrossmintOnly(); + + _mintInternal(qty, to, 0, proof); + } + + /** + * @dev Implementation of minting. + */ + function _mintInternal( + uint32 qty, + address to, + uint32 limit, + bytes32[] calldata proof + ) internal canMint hasSupply(qty) { + uint64 stageTimestamp = uint64(block.timestamp); + + MintStageInfo memory stage; + + uint256 activeStage = getActiveStageFromTimestamp(stageTimestamp); + + stage = _mintStages[activeStage]; + + // Check value if minting with ETH + if ( + _mintCurrency == address(0) && + msg.value < (stage.price + stage.mintFee) * qty + ) revert NotEnoughValue(); + + // Check stage supply if applicable + if (stage.maxStageSupply > 0) { + if (_stageMintedCounts[activeStage] + qty > stage.maxStageSupply) + revert StageSupplyExceeded(); + } + + // Check global wallet limit if applicable + if (_globalWalletLimit > 0) { + if (_numberMinted(to) + qty > _globalWalletLimit) + revert WalletGlobalLimitExceeded(); + } + + // Check wallet limit for stage if applicable, limit == 0 means no limit enforced + if (stage.walletLimit > 0) { + if ( + _stageMintedCountsPerWallet[activeStage][to] + qty > + stage.walletLimit + ) revert WalletStageLimitExceeded(); + } + + // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required + if (stage.merkleRoot != 0) { + if ( + MerkleProof.processProof( + proof, + keccak256(abi.encodePacked(to, limit)) + ) != stage.merkleRoot + ) revert InvalidProof(); + + // Verify merkle proof mint limit + if ( + limit > 0 && + _stageMintedCountsPerWallet[activeStage][to] + qty > limit + ) { + revert WalletStageLimitExceeded(); + } + } + + if (_mintCurrency != address(0)) { + IERC20(_mintCurrency).safeTransferFrom( + msg.sender, + address(this), + (stage.price + stage.mintFee) * qty + ); + } + + _totalMintFee += stage.mintFee * qty; + + _stageMintedCountsPerWallet[activeStage][to] += qty; + _stageMintedCounts[activeStage] += qty; + _safeMint(to, qty); + } + + /** + * @dev Mints token(s) by owner. + * + * NOTE: This function bypasses validations thus only available for owner. + * This is typically used for owner to pre-mint or mint the remaining of the supply. + */ + function ownerMint( + uint32 qty, + address to + ) external onlyOwner hasSupply(qty) { + _safeMint(to, qty); + } + + /** + * @dev Withdraws funds by owner. + */ + function withdraw() external onlyOwner { + (bool success, ) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); + if (!success) revert TransferFailed(); + _totalMintFee = 0; + + uint256 remainingValue = address(this).balance; + (success, ) = FUND_RECEIVER.call{value: remainingValue}(""); + if (!success) revert WithdrawFailed(); + + emit Withdraw(_totalMintFee + remainingValue); + } + + /** + * @dev Withdraws ERC-20 funds by owner. + */ + function withdrawERC20() external onlyOwner { + if (_mintCurrency == address(0)) revert WrongMintCurrency(); + + IERC20(_mintCurrency).safeTransfer(MINT_FEE_RECEIVER, _totalMintFee); + _totalMintFee = 0; + + uint256 remaining = IERC20(_mintCurrency).balanceOf(address(this)); + IERC20(_mintCurrency).safeTransfer(FUND_RECEIVER, remaining); + + emit WithdrawERC20(_mintCurrency, _totalMintFee + remaining); + } + + /** + * @dev Sets token base URI. + */ + function setBaseURI(string calldata baseURI) external onlyOwner { + _currentBaseURI = baseURI; + emit SetBaseURI(baseURI); + } + + /** + * @dev Sets token URI suffix. e.g. ".json". + */ + function setTokenURISuffix(string calldata suffix) external onlyOwner { + _tokenURISuffix = suffix; + } + + /** + * @dev Returns token URI for a given token id. + */ + function tokenURI( + uint256 tokenId + ) + public + view + override(ERC721AUpgradeable, IERC721AUpgradeable) + returns (string memory) + { + if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); + + string memory baseURI = _currentBaseURI; + return + bytes(baseURI).length != 0 + ? string( + abi.encodePacked( + baseURI, + _toString(tokenId), + _tokenURISuffix + ) + ) + : ""; + } + + /** + * @dev Returns URI for the collection-level metadata. + */ + function contractURI() public view returns (string memory) { + return _contractURI; + } + + /** + * @dev Set the URI for the collection-level metadata. + */ + function setContractURI(string calldata uri) external onlyOwner { + _contractURI = uri; + } + + /** + * @dev Returns the current active stage based on timestamp. + */ + function getActiveStageFromTimestamp( + uint64 timestamp + ) public view returns (uint256) { + for (uint256 i = 0; i < _mintStages.length; ) { + if ( + timestamp >= _mintStages[i].startTimeUnixSeconds && + timestamp < _mintStages[i].endTimeUnixSeconds + ) { + return i; + } + unchecked { + ++i; + } + } + revert InvalidStage(); + } + + /** + * @dev Validates the start timestamp is before end timestamp. Used when updating stages. + */ + function _assertValidStartAndEndTimestamp( + uint64 start, + uint64 end + ) internal pure { + if (start >= end) revert InvalidStartAndEndTimestamp(); + } + + /** + * @dev Returns chain id. + */ + function _chainID() private view returns (uint256 chainID) { + assembly { + chainID := chainid() + } + } + + function _requireCallerIsContractOwner() + internal + view + virtual + override(OwnableInitializable, OwnablePermissions) + { + _checkOwner(); + } + + /** + * @notice Returns the function selector for the transfer validator's validation function to be called + * @notice for transaction simulation. + */ + function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { + functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); + isViewFunction = true; + } + + function _tokenType() internal pure override returns(uint16) { + return uint16(721); + } +} diff --git a/test/erc721m/ERC721CMInitializable_V2Test.t.sol b/test/erc721m/ERC721CMInitializable_V2Test.t.sol new file mode 100644 index 00000000..382320d6 --- /dev/null +++ b/test/erc721m/ERC721CMInitializable_V2Test.t.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "erc721a/contracts/IERC721A.sol"; +import "forge-std/Test.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC721CMInitializable_V2} from "../../contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol"; +import {MintStageInfo} from "../../contracts/common/Structs.sol"; +import {IERC721M} from "../../contracts/nft/erc721m/interfaces/IERC721M.sol"; + +contract ERC721CMInitializable_V2Test is Test { + ERC721CMInitializable_V2 public nft; + address public owner; + address public minter; + address public fundReceiver; + address public readonly; + address public crossmintAddress; + uint256 public constant INITIAL_SUPPLY = 1000; + uint256 public constant GLOBAL_WALLET_LIMIT = 0; + uint64 public constant TIMESTAMP_EXPIRY_SECONDS = 60; + + function setUp() public { + owner = address(this); + fundReceiver = address(0x1); + readonly = address(0x2); + crossmintAddress = address(0x3); + minter = address(0x4); + + vm.deal(owner, 10 ether); + vm.deal(minter, 2 ether); + vm.deal(crossmintAddress, 1 ether); + + nft = new ERC721CMInitializable_V2(); + nft.initialize("Test", "TEST", payable(owner)); + nft.setup( + ".json", + INITIAL_SUPPLY, + GLOBAL_WALLET_LIMIT, + TIMESTAMP_EXPIRY_SECONDS, + address(0), + fundReceiver, + crossmintAddress, + new MintStageInfo[](0) + ); + } + + function testInitialState() public view { + assertEq(nft.name(), "Test"); + assertEq(nft.symbol(), "TEST"); + assertEq(nft.owner(), owner); + assertEq(nft.getMaxMintableSupply(), INITIAL_SUPPLY); + assertEq(nft.getGlobalWalletLimit(), GLOBAL_WALLET_LIMIT); + assertTrue(nft.getMintable()); + } + + function testSetMintable() public { + nft.setMintable(false); + assertFalse(nft.getMintable()); + + vm.prank(readonly); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, readonly)); + nft.setMintable(true); + } + + function testWithdraw() public { + // Send 100 wei to contract address for testing + vm.deal(address(nft), 100); + assertEq(address(nft).balance, 100); + + uint256 initialFundReceiverBalance = fundReceiver.balance; + nft.withdraw(); + assertEq(address(nft).balance, 0); + assertEq(fundReceiver.balance, initialFundReceiverBalance + 100); + + vm.prank(readonly); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, readonly)); + nft.withdraw(); + } + + function testSetStages() public { + MintStageInfo[] memory stages = new MintStageInfo[](2); + stages[0] = MintStageInfo({ + price: 0.5 ether, + mintFee: 0.1 ether, + walletLimit: 3, + merkleRoot: bytes32(uint256(1)), + maxStageSupply: 5, + startTimeUnixSeconds: 0, + endTimeUnixSeconds: 1 + }); + stages[1] = MintStageInfo({ + price: 0.6 ether, + mintFee: 0.1 ether, + walletLimit: 4, + merkleRoot: bytes32(uint256(2)), + maxStageSupply: 10, + startTimeUnixSeconds: 61, + endTimeUnixSeconds: 62 + }); + + nft.setStages(stages); + assertEq(nft.getNumberStages(), 2); + + vm.prank(readonly); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, readonly)); + nft.setStages(stages); + } + + function testMint() public { + MintStageInfo[] memory stages = new MintStageInfo[](1); + stages[0] = MintStageInfo({ + price: 0.5 ether, + mintFee: 0.1 ether, + walletLimit: 10, + merkleRoot: bytes32(0), + maxStageSupply: 5, + startTimeUnixSeconds: 0, + endTimeUnixSeconds: 1 + }); + nft.setStages(stages); + + vm.warp(0); + vm.prank(minter); + nft.mint{value: 0.6 ether}(1, new bytes32[](0), 0, ""); + assertEq(nft.balanceOf(minter), 1); + + vm.expectRevert(abi.encodeWithSelector(IERC721M.NotEnoughValue.selector)); + vm.prank(minter); + nft.mint{value: 0.5 ether}(1, new bytes32[](0), 0, ""); + } + + function testCrossmint() public { + MintStageInfo[] memory stages = new MintStageInfo[](1); + stages[0] = MintStageInfo({ + price: 0.5 ether, + mintFee: 0, + walletLimit: 0, + merkleRoot: bytes32(0), + maxStageSupply: 100, + startTimeUnixSeconds: 0, + endTimeUnixSeconds: 1000000 // Changed to a much later end time + }); + nft.setStages(stages); + + // Set the block timestamp to a time within the stage + vm.warp(500000); + + vm.prank(crossmintAddress); + nft.crossmint{value: 0.5 ether}(1, readonly, new bytes32[](0), 0, ""); + assertEq(nft.balanceOf(readonly), 1); + + vm.prank(minter); + vm.expectRevert(abi.encodeWithSelector(IERC721M.CrossmintOnly.selector)); + nft.crossmint{value: 0.5 ether}(1, readonly, new bytes32[](0), 0, ""); + } + + function testTokenURI() public { + nft.setBaseURI("base_uri_"); + nft.setTokenURISuffix(".json"); + + MintStageInfo[] memory stages = new MintStageInfo[](1); + stages[0] = MintStageInfo({ + price: 0.1 ether, + mintFee: 0, + walletLimit: 0, + merkleRoot: bytes32(0), + maxStageSupply: 0, + startTimeUnixSeconds: 0, + endTimeUnixSeconds: 1000000 + }); + nft.setStages(stages); + + // Set the block timestamp to a time within the stage + vm.warp(500000); + + vm.prank(minter); + nft.mint{value: 0.1 ether}(1, new bytes32[](0), 0, ""); + assertEq(nft.tokenURI(0), "base_uri_0.json"); + + vm.expectRevert(abi.encodeWithSelector(IERC721A.URIQueryForNonexistentToken.selector)); + nft.tokenURI(1); + } + + function testGlobalWalletLimit() public { + nft.setGlobalWalletLimit(2); + assertEq(nft.getGlobalWalletLimit(), 2); + + vm.expectRevert(abi.encodeWithSelector(IERC721M.GlobalWalletLimitOverflow.selector)); + nft.setGlobalWalletLimit(INITIAL_SUPPLY + 1); + } + + function testContractURI() public { + string memory uri = "ipfs://bafybeidntqfipbuvdhdjosntmpxvxyse2dkyfpa635u4g6txruvt5qf7y4"; + nft.setContractURI(uri); + assertEq(nft.contractURI(), uri); + } +} \ No newline at end of file From c514e1f9388b8a872f1f23883a398b5fe7ab106b Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 20 Sep 2024 16:49:24 -0400 Subject: [PATCH 023/145] consolidate errors and events Signed-off-by: Wolfy --- contracts/common/ErrorsAndEvents.sol | 41 ++++++++++++ contracts/common/Structs.sol | 10 +++ contracts/nft/erc1155m/ERC1155M.sol | 13 ++-- .../nft/erc1155m/ERC1155MErrorsAndEvents.sol | 25 ++++++++ .../nft/erc1155m/interfaces/IERC1155M.sol | 61 ++---------------- .../nft/erc721m/ERC721MErrorsAndEvents.sol | 20 ++++++ contracts/nft/erc721m/interfaces/IERC721M.sol | 62 +------------------ .../interfaces/IERC721MInitializable.sol | 50 +-------------- .../ERC721CMInitializable_V2Test.t.sol | 12 ++-- 9 files changed, 118 insertions(+), 176 deletions(-) create mode 100644 contracts/common/ErrorsAndEvents.sol create mode 100644 contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol create mode 100644 contracts/nft/erc721m/ERC721MErrorsAndEvents.sol diff --git a/contracts/common/ErrorsAndEvents.sol b/contracts/common/ErrorsAndEvents.sol new file mode 100644 index 00000000..61bd9e2a --- /dev/null +++ b/contracts/common/ErrorsAndEvents.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +interface ErrorsAndEvents { + error CannotIncreaseMaxMintableSupply(); + error CosignerNotSet(); + error CrossmintAddressNotSet(); + error CrossmintOnly(); + error GlobalWalletLimitOverflow(); + error InsufficientStageTimeGap(); + error InvalidCosignSignature(); + error InvalidProof(); + error InvalidStage(); + error InvalidStageArgsLength(); + error InvalidStartAndEndTimestamp(); + error NoSupplyLeft(); + error NotEnoughValue(); + error NotMintable(); + error Mintable(); + error StageSupplyExceeded(); + error TimestampExpired(); + error TransferFailed(); + error WalletGlobalLimitExceeded(); + error WalletStageLimitExceeded(); + error WithdrawFailed(); + error WrongMintCurrency(); + error NotSupported(); + error NotAuthorized(); + error NewSupplyLessThanTotalSupply(); + error NotTransferable(); + + event SetCosigner(address cosigner); + event SetCrossmintAddress(address crossmintAddress); + event SetMintable(bool mintable); + event SetActiveStage(uint256 activeStage); + event SetBaseURI(string baseURI); + event SetMintCurrency(address mintCurrency); + event Withdraw(uint256 value); + event WithdrawERC20(address mintCurrency, uint256 value); + event SetTimestampExpirySeconds(uint64 expiry); +} \ No newline at end of file diff --git a/contracts/common/Structs.sol b/contracts/common/Structs.sol index 04525ebc..90533fa7 100644 --- a/contracts/common/Structs.sol +++ b/contracts/common/Structs.sol @@ -14,4 +14,14 @@ struct MintStageInfo { uint24 maxStageSupply; // 0 for unlimited uint64 startTimeUnixSeconds; uint64 endTimeUnixSeconds; +} + +struct MintStageInfo1155 { + uint80[] price; + uint80[] mintFee; + uint32[] walletLimit; // 0 for unlimited + bytes32[] merkleRoot; // 0x0 for no presale enforced + uint24[] maxStageSupply; // 0 for unlimited + uint64 startTimeUnixSeconds; + uint64 endTimeUnixSeconds; } \ No newline at end of file diff --git a/contracts/nft/erc1155m/ERC1155M.sol b/contracts/nft/erc1155m/ERC1155M.sol index a9d4e7da..7c7257d3 100644 --- a/contracts/nft/erc1155m/ERC1155M.sol +++ b/contracts/nft/erc1155m/ERC1155M.sol @@ -14,6 +14,7 @@ import "../../utils/Constants.sol"; import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; import "./interfaces/IERC1155M.sol"; +import {MintStageInfo1155} from "../../common/Structs.sol"; /** * @title ERC1155M @@ -48,7 +49,7 @@ contract ERC1155M is uint256[] private _globalWalletLimit; // Mint stage information. See MintStageInfo for details. - MintStageInfo[] private _mintStages; + MintStageInfo1155[] private _mintStages; // Whether the token can be transferred. bool private _transferable; @@ -197,7 +198,7 @@ contract ERC1155M is * } * ] */ - function setStages(MintStageInfo[] calldata newStages) external onlyOwner { + function setStages(MintStageInfo1155[] calldata newStages) external onlyOwner { delete _mintStages; for (uint256 i = 0; i < newStages.length; i++) { @@ -217,7 +218,7 @@ contract ERC1155M is _assertValidStageArgsLength(newStages[i]); _mintStages.push( - MintStageInfo({ + MintStageInfo1155({ price: newStages[i].price, mintFee: newStages[i].mintFee, walletLimit: newStages[i].walletLimit, @@ -368,7 +369,7 @@ contract ERC1155M is external view override - returns (MintStageInfo memory, uint256[] memory, uint256[] memory) + returns (MintStageInfo1155 memory, uint256[] memory, uint256[] memory) { if (stage >= _mintStages.length) { revert InvalidStage(); @@ -484,7 +485,7 @@ contract ERC1155M is uint256 activeStage = getActiveStageFromTimestamp(stageTimestamp); - MintStageInfo memory stage = _mintStages[activeStage]; + MintStageInfo1155 memory stage = _mintStages[activeStage]; uint80 adjustedMintFee = waiveMintFee ? 0 : stage.mintFee[tokenId]; // Check value if minting with ETH @@ -773,7 +774,7 @@ contract ERC1155M is } function _assertValidStageArgsLength( - MintStageInfo calldata stageInfo + MintStageInfo1155 calldata stageInfo ) internal { if ( stageInfo.price.length != NUM_TOKENS || diff --git a/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol b/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol new file mode 100644 index 00000000..0789a2da --- /dev/null +++ b/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ErrorsAndEvents} from "../../common/ErrorsAndEvents.sol"; + +interface ERC1155MErrorsAndEvents is ErrorsAndEvents { + error InvalidLimitArgsLength(); + error InvalidTokenId(); + + event UpdateStage( + uint256 indexed stage, + uint80[] price, + uint80[] mintFee, + uint32[] walletLimit, + bytes32[] merkleRoot, + uint24[] maxStageSupply, + uint64 startTimeUnixSeconds, + uint64 endTimeUnixSeconds + ); + event SetMaxMintableSupply(uint256 indexed tokenId, uint256 maxMintableSupply); + event SetGlobalWalletLimit(uint256 indexed tokenId, uint256 globalWalletLimit); + event SetTransferable(bool transferable); + event DefaultRoyaltySet(address receiver, uint96 feeNumerator); + event TokenRoyaltySet(uint256 indexed tokenId, address receiver, uint96 feeNumerator); +} \ No newline at end of file diff --git a/contracts/nft/erc1155m/interfaces/IERC1155M.sol b/contracts/nft/erc1155m/interfaces/IERC1155M.sol index 83f1770b..94426d6b 100644 --- a/contracts/nft/erc1155m/interfaces/IERC1155M.sol +++ b/contracts/nft/erc1155m/interfaces/IERC1155M.sol @@ -1,63 +1,10 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +import {MintStageInfo1155} from "../../../common/Structs.sol"; +import {ERC1155MErrorsAndEvents} from "../ERC1155MErrorsAndEvents.sol"; -interface IERC1155M { - error CannotIncreaseMaxMintableSupply(); - error CosignerNotSet(); - error NewSupplyLessThanTotalSupply(); - error GlobalWalletLimitOverflow(); - error InsufficientStageTimeGap(); - error InvalidCosignSignature(); - error InvalidLimitArgsLength(); - error InvalidProof(); - error InvalidStage(); - error InvalidStageArgsLength(); - error InvalidTokenId(); - error InvalidStartAndEndTimestamp(); - error NoSupplyLeft(); - error NotAuthorized(); - error NotEnoughValue(); - error NotTransferable(); - error StageSupplyExceeded(); - error TimestampExpired(); - error TransferFailed(); - error WalletGlobalLimitExceeded(); - error WalletStageLimitExceeded(); - error WithdrawFailed(); - error WrongMintCurrency(); - error NotSupported(); - - struct MintStageInfo { - uint80[] price; - uint80[] mintFee; - uint32[] walletLimit; // 0 for unlimited - bytes32[] merkleRoot; // 0x0 for no presale enforced - uint24[] maxStageSupply; // 0 for unlimited - uint64 startTimeUnixSeconds; - uint64 endTimeUnixSeconds; - } - - event UpdateStage( - uint256 indexed stage, - uint80[] price, - uint80[] mintFee, - uint32[] walletLimit, - bytes32[] merkleRoot, - uint24[] maxStageSupply, - uint64 startTimeUnixSeconds, - uint64 endTimeUnixSeconds - ); - - event SetCosigner(address cosigner); - event SetMaxMintableSupply(uint256 indexed tokenId, uint256 maxMintableSupply); - event SetGlobalWalletLimit(uint256 indexed tokenId, uint256 globalWalletLimit); - event Withdraw(uint256 value); - event WithdrawERC20(address indexed mintCurrency, uint256 value); - event SetTransferable(bool transferable); - event DefaultRoyaltySet(address receiver, uint96 feeNumerator); - event TokenRoyaltySet(uint256 indexed tokenId, address receiver, uint96 feeNumerator); - +interface IERC1155M is ERC1155MErrorsAndEvents { function getNumberStages() external view returns (uint256); function getGlobalWalletLimit(uint256 tokenId) external view returns (uint256); @@ -66,7 +13,7 @@ interface IERC1155M { function totalMintedByAddress(address account) external view returns (uint256[] memory); - function getStageInfo(uint256 stage) external view returns (MintStageInfo memory, uint256[] memory, uint256[] memory); + function getStageInfo(uint256 stage) external view returns (MintStageInfo1155 memory, uint256[] memory, uint256[] memory); function mint( uint256 tokenId, diff --git a/contracts/nft/erc721m/ERC721MErrorsAndEvents.sol b/contracts/nft/erc721m/ERC721MErrorsAndEvents.sol new file mode 100644 index 00000000..08b3c6d8 --- /dev/null +++ b/contracts/nft/erc721m/ERC721MErrorsAndEvents.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ErrorsAndEvents} from "../../common/ErrorsAndEvents.sol"; + +interface ERC721MErrorsAndEvents is ErrorsAndEvents { + event UpdateStage( + uint256 stage, + uint80 price, + uint80 mintFee, + uint32 walletLimit, + bytes32 merkleRoot, + uint24 maxStageSupply, + uint64 startTimeUnixSeconds, + uint64 endTimeUnixSeconds + ); + + event SetMaxMintableSupply(uint256 maxMintableSupply); + event SetGlobalWalletLimit(uint256 globalWalletLimit); +} \ No newline at end of file diff --git a/contracts/nft/erc721m/interfaces/IERC721M.sol b/contracts/nft/erc721m/interfaces/IERC721M.sol index 95e4d5bd..e6bdd01c 100644 --- a/contracts/nft/erc721m/interfaces/IERC721M.sol +++ b/contracts/nft/erc721m/interfaces/IERC721M.sol @@ -1,67 +1,11 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.8.4; +import {MintStageInfo} from "../../../common/Structs.sol"; import "erc721a/contracts/extensions/IERC721AQueryable.sol"; +import {ERC721MErrorsAndEvents} from "../ERC721MErrorsAndEvents.sol"; -interface IERC721M is IERC721AQueryable { - error CannotIncreaseMaxMintableSupply(); - error CosignerNotSet(); - error CrossmintAddressNotSet(); - error CrossmintOnly(); - error GlobalWalletLimitOverflow(); - error InsufficientStageTimeGap(); - error InvalidCosignSignature(); - error InvalidProof(); - error InvalidStage(); - error InvalidStageArgsLength(); - error InvalidStartAndEndTimestamp(); - error NoSupplyLeft(); - error NotAuthorized(); - error NotEnoughValue(); - error NotMintable(); - error Mintable(); - error StageSupplyExceeded(); - error TimestampExpired(); - error TransferFailed(); - error WalletGlobalLimitExceeded(); - error WalletStageLimitExceeded(); - error WithdrawFailed(); - error WrongMintCurrency(); - error NotSupported(); - - struct MintStageInfo { - uint80 price; - uint80 mintFee; - uint32 walletLimit; // 0 for unlimited - bytes32 merkleRoot; // 0x0 for no presale enforced - uint24 maxStageSupply; // 0 for unlimited - uint64 startTimeUnixSeconds; - uint64 endTimeUnixSeconds; - } - - event UpdateStage( - uint256 stage, - uint80 price, - uint80 mintFee, - uint32 walletLimit, - bytes32 merkleRoot, - uint24 maxStageSupply, - uint64 startTimeUnixSeconds, - uint64 endTimeUnixSeconds - ); - - event SetCosigner(address cosigner); - event SetCrossmintAddress(address crossmintAddress); - event SetMintable(bool mintable); - event SetMaxMintableSupply(uint256 maxMintableSupply); - event SetGlobalWalletLimit(uint256 globalWalletLimit); - event SetActiveStage(uint256 activeStage); - event SetBaseURI(string baseURI); - event SetTimestampExpirySeconds(uint64 expiry); - event SetMintCurrency(address mintCurrency); - event Withdraw(uint256 value); - event WithdrawERC20(address mintCurrency, uint256 value); - +interface IERC721M is IERC721AQueryable, ERC721MErrorsAndEvents { function getNumberStages() external view returns (uint256); function getGlobalWalletLimit() external view returns (uint256); diff --git a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol index bac6cbea..85326519 100644 --- a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol +++ b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol @@ -3,58 +3,14 @@ pragma solidity ^0.8.4; import "erc721a-upgradeable/contracts/extensions/IERC721AQueryableUpgradeable.sol"; import "../../../common/Structs.sol"; +import {ERC721MErrorsAndEvents} from "../ERC721MErrorsAndEvents.sol"; + /** * @title IERC721MInitializable * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this * contract is for use with EIP-1167 Minimal Proxies (Clones). */ -interface IERC721MInitializable is IERC721AQueryableUpgradeable { - error CannotIncreaseMaxMintableSupply(); - error CosignerNotSet(); - error CrossmintAddressNotSet(); - error CrossmintOnly(); - error GlobalWalletLimitOverflow(); - error InsufficientStageTimeGap(); - error InvalidCosignSignature(); - error InvalidProof(); - error InvalidStage(); - error InvalidStageArgsLength(); - error InvalidStartAndEndTimestamp(); - error NoSupplyLeft(); - error NotEnoughValue(); - error NotMintable(); - error Mintable(); - error StageSupplyExceeded(); - error TimestampExpired(); - error TransferFailed(); - error WalletGlobalLimitExceeded(); - error WalletStageLimitExceeded(); - error WithdrawFailed(); - error WrongMintCurrency(); - error NotSupported(); - - event UpdateStage( - uint256 stage, - uint80 price, - uint80 mintFee, - uint32 walletLimit, - bytes32 merkleRoot, - uint24 maxStageSupply, - uint64 startTimeUnixSeconds, - uint64 endTimeUnixSeconds - ); - - event SetCosigner(address cosigner); - event SetCrossmintAddress(address crossmintAddress); - event SetMintable(bool mintable); - event SetMaxMintableSupply(uint256 maxMintableSupply); - event SetGlobalWalletLimit(uint256 globalWalletLimit); - event SetActiveStage(uint256 activeStage); - event SetBaseURI(string baseURI); - event SetMintCurrency(address mintCurrency); - event Withdraw(uint256 value); - event WithdrawERC20(address mintCurrency, uint256 value); - +interface IERC721MInitializable is IERC721AQueryableUpgradeable, ERC721MErrorsAndEvents { function getNumberStages() external view returns (uint256); function getGlobalWalletLimit() external view returns (uint256); diff --git a/test/erc721m/ERC721CMInitializable_V2Test.t.sol b/test/erc721m/ERC721CMInitializable_V2Test.t.sol index 382320d6..30410116 100644 --- a/test/erc721m/ERC721CMInitializable_V2Test.t.sol +++ b/test/erc721m/ERC721CMInitializable_V2Test.t.sol @@ -6,7 +6,7 @@ import "forge-std/Test.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import {ERC721CMInitializable_V2} from "../../contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol"; import {MintStageInfo} from "../../contracts/common/Structs.sol"; -import {IERC721M} from "../../contracts/nft/erc721m/interfaces/IERC721M.sol"; +import {ErrorsAndEvents} from "../../contracts/common/ErrorsAndEvents.sol"; contract ERC721CMInitializable_V2Test is Test { ERC721CMInitializable_V2 public nft; @@ -124,7 +124,7 @@ contract ERC721CMInitializable_V2Test is Test { nft.mint{value: 0.6 ether}(1, new bytes32[](0), 0, ""); assertEq(nft.balanceOf(minter), 1); - vm.expectRevert(abi.encodeWithSelector(IERC721M.NotEnoughValue.selector)); + vm.expectRevert(abi.encodeWithSelector(ErrorsAndEvents.NotEnoughValue.selector)); vm.prank(minter); nft.mint{value: 0.5 ether}(1, new bytes32[](0), 0, ""); } @@ -138,11 +138,10 @@ contract ERC721CMInitializable_V2Test is Test { merkleRoot: bytes32(0), maxStageSupply: 100, startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1000000 // Changed to a much later end time + endTimeUnixSeconds: 1000000 }); nft.setStages(stages); - // Set the block timestamp to a time within the stage vm.warp(500000); vm.prank(crossmintAddress); @@ -150,7 +149,7 @@ contract ERC721CMInitializable_V2Test is Test { assertEq(nft.balanceOf(readonly), 1); vm.prank(minter); - vm.expectRevert(abi.encodeWithSelector(IERC721M.CrossmintOnly.selector)); + vm.expectRevert(abi.encodeWithSelector(ErrorsAndEvents.CrossmintOnly.selector)); nft.crossmint{value: 0.5 ether}(1, readonly, new bytes32[](0), 0, ""); } @@ -170,7 +169,6 @@ contract ERC721CMInitializable_V2Test is Test { }); nft.setStages(stages); - // Set the block timestamp to a time within the stage vm.warp(500000); vm.prank(minter); @@ -185,7 +183,7 @@ contract ERC721CMInitializable_V2Test is Test { nft.setGlobalWalletLimit(2); assertEq(nft.getGlobalWalletLimit(), 2); - vm.expectRevert(abi.encodeWithSelector(IERC721M.GlobalWalletLimitOverflow.selector)); + vm.expectRevert(abi.encodeWithSelector(ErrorsAndEvents.GlobalWalletLimitOverflow.selector)); nft.setGlobalWalletLimit(INITIAL_SUPPLY + 1); } From b451da6c8c1b397820d612bb4a84f419d2241599 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Sat, 21 Sep 2024 19:12:38 -0400 Subject: [PATCH 024/145] organize tests Signed-off-by: Wolfy --- test/{ => erc1155m}/ERC1155M.test.ts | 2 +- test/{ => erc721m}/ERC721BatchTransfer.test.ts | 2 +- test/{ => erc721m}/ERC721CM.test.ts | 2 +- test/{ => erc721m}/ERC721M.test.ts | 2 +- test/{ => erc721m/extensions}/BucketAuction.test.ts | 2 +- test/{ => erc721m/extensions}/ERC721CMBasicRoyalties.test.ts | 2 +- test/{ => erc721m/extensions}/ERC721CMRoyalties.test.ts | 2 +- test/{ => erc721m/extensions}/ERC721CMRoyaltiesClone.test.ts | 2 +- .../extensions}/ERC721CMRoyaltiesCloneFactory.test.ts | 2 +- test/{ => erc721m}/mintCurrency.test.ts | 2 +- test/{foundry => factory}/MagicDropCloneFactoryTest.t.sol | 0 test/{foundry => factory}/MagicDropTokenImplRegistryTest.t.sol | 0 12 files changed, 10 insertions(+), 10 deletions(-) rename test/{ => erc1155m}/ERC1155M.test.ts (99%) rename test/{ => erc721m}/ERC721BatchTransfer.test.ts (98%) rename test/{ => erc721m}/ERC721CM.test.ts (99%) rename test/{ => erc721m}/ERC721M.test.ts (99%) rename test/{ => erc721m/extensions}/BucketAuction.test.ts (99%) rename test/{ => erc721m/extensions}/ERC721CMBasicRoyalties.test.ts (96%) rename test/{ => erc721m/extensions}/ERC721CMRoyalties.test.ts (98%) rename test/{ => erc721m/extensions}/ERC721CMRoyaltiesClone.test.ts (99%) rename test/{ => erc721m/extensions}/ERC721CMRoyaltiesCloneFactory.test.ts (97%) rename test/{ => erc721m}/mintCurrency.test.ts (99%) rename test/{foundry => factory}/MagicDropCloneFactoryTest.t.sol (100%) rename test/{foundry => factory}/MagicDropTokenImplRegistryTest.t.sol (100%) diff --git a/test/ERC1155M.test.ts b/test/erc1155m/ERC1155M.test.ts similarity index 99% rename from test/ERC1155M.test.ts rename to test/erc1155m/ERC1155M.test.ts index 6d3b04a9..23da5295 100644 --- a/test/ERC1155M.test.ts +++ b/test/erc1155m/ERC1155M.test.ts @@ -3,7 +3,7 @@ import chai, { assert, expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { ethers } from 'hardhat'; import { MerkleTree } from 'merkletreejs'; -import { ERC1155M } from '../typechain-types'; +import { ERC1155M } from '../../typechain-types'; import { BigNumber, Contract } from 'ethers'; const { getAddress, parseEther } = ethers.utils; diff --git a/test/ERC721BatchTransfer.test.ts b/test/erc721m/ERC721BatchTransfer.test.ts similarity index 98% rename from test/ERC721BatchTransfer.test.ts rename to test/erc721m/ERC721BatchTransfer.test.ts index b78b3187..f4ae8e91 100644 --- a/test/ERC721BatchTransfer.test.ts +++ b/test/erc721m/ERC721BatchTransfer.test.ts @@ -2,7 +2,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { ethers } from 'hardhat'; -import { ERC721BatchTransfer, MockERC721A } from '../typechain-types'; +import { ERC721BatchTransfer, MockERC721A } from '../../typechain-types'; chai.use(chaiAsPromised); diff --git a/test/ERC721CM.test.ts b/test/erc721m/ERC721CM.test.ts similarity index 99% rename from test/ERC721CM.test.ts rename to test/erc721m/ERC721CM.test.ts index 75426519..f8935db4 100644 --- a/test/ERC721CM.test.ts +++ b/test/erc721m/ERC721CM.test.ts @@ -3,7 +3,7 @@ import chai, { assert, expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { ethers } from 'hardhat'; import { MerkleTree } from 'merkletreejs'; -import { ERC721CM } from '../typechain-types'; +import { ERC721CM } from '../../typechain-types'; import { BigNumber } from 'ethers'; const { getAddress } = ethers.utils; diff --git a/test/ERC721M.test.ts b/test/erc721m/ERC721M.test.ts similarity index 99% rename from test/ERC721M.test.ts rename to test/erc721m/ERC721M.test.ts index 578d1b89..e0773ed6 100644 --- a/test/ERC721M.test.ts +++ b/test/erc721m/ERC721M.test.ts @@ -3,7 +3,7 @@ import chai, { assert, expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { ethers } from 'hardhat'; import { MerkleTree } from 'merkletreejs'; -import { ERC721M } from '../typechain-types'; +import { ERC721M } from '../../typechain-types'; import { BigNumber } from 'ethers'; const { keccak256, getAddress } = ethers.utils; diff --git a/test/BucketAuction.test.ts b/test/erc721m/extensions/BucketAuction.test.ts similarity index 99% rename from test/BucketAuction.test.ts rename to test/erc721m/extensions/BucketAuction.test.ts index 8a0a275b..0b2995c1 100644 --- a/test/BucketAuction.test.ts +++ b/test/erc721m/extensions/BucketAuction.test.ts @@ -2,7 +2,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { ethers } from 'hardhat'; -import { BucketAuction } from '../typechain-types'; +import { BucketAuction } from '../../../typechain-types'; chai.use(chaiAsPromised); diff --git a/test/ERC721CMBasicRoyalties.test.ts b/test/erc721m/extensions/ERC721CMBasicRoyalties.test.ts similarity index 96% rename from test/ERC721CMBasicRoyalties.test.ts rename to test/erc721m/extensions/ERC721CMBasicRoyalties.test.ts index 970229e2..bdc1bed3 100644 --- a/test/ERC721CMBasicRoyalties.test.ts +++ b/test/erc721m/extensions/ERC721CMBasicRoyalties.test.ts @@ -2,7 +2,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { ethers } from 'hardhat'; -import { ERC721CMBasicRoyalties } from '../typechain-types'; +import { ERC721CMBasicRoyalties } from '../../../typechain-types'; chai.use(chaiAsPromised); diff --git a/test/ERC721CMRoyalties.test.ts b/test/erc721m/extensions/ERC721CMRoyalties.test.ts similarity index 98% rename from test/ERC721CMRoyalties.test.ts rename to test/erc721m/extensions/ERC721CMRoyalties.test.ts index 5b78824e..3583efe0 100644 --- a/test/ERC721CMRoyalties.test.ts +++ b/test/erc721m/extensions/ERC721CMRoyalties.test.ts @@ -2,7 +2,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { ethers } from 'hardhat'; -import { ERC721CMRoyalties } from '../typechain-types'; +import { ERC721CMRoyalties } from '../../../typechain-types'; chai.use(chaiAsPromised); diff --git a/test/ERC721CMRoyaltiesClone.test.ts b/test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts similarity index 99% rename from test/ERC721CMRoyaltiesClone.test.ts rename to test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts index 7cffcd53..93f9f72a 100644 --- a/test/ERC721CMRoyaltiesClone.test.ts +++ b/test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts @@ -3,7 +3,7 @@ import chai, { assert, expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { ethers } from 'hardhat'; import { MerkleTree } from 'merkletreejs'; -import { ERC721CMRoyaltiesInitializable, ERC721CMRoyaltiesCloneFactory } from '../typechain-types'; +import { ERC721CMRoyaltiesInitializable, ERC721CMRoyaltiesCloneFactory } from '../../../typechain-types'; const { keccak256, getAddress } = ethers.utils; diff --git a/test/ERC721CMRoyaltiesCloneFactory.test.ts b/test/erc721m/extensions/ERC721CMRoyaltiesCloneFactory.test.ts similarity index 97% rename from test/ERC721CMRoyaltiesCloneFactory.test.ts rename to test/erc721m/extensions/ERC721CMRoyaltiesCloneFactory.test.ts index 73291daf..c9faee30 100644 --- a/test/ERC721CMRoyaltiesCloneFactory.test.ts +++ b/test/erc721m/extensions/ERC721CMRoyaltiesCloneFactory.test.ts @@ -2,7 +2,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { ethers } from 'hardhat'; -import { ERC721CMRoyaltiesCloneFactory } from '../typechain-types'; +import { ERC721CMRoyaltiesCloneFactory } from '../../../typechain-types'; import { isAddress } from 'ethers/lib/utils'; chai.use(chaiAsPromised); diff --git a/test/mintCurrency.test.ts b/test/erc721m/mintCurrency.test.ts similarity index 99% rename from test/mintCurrency.test.ts rename to test/erc721m/mintCurrency.test.ts index dee6e50b..c439a467 100644 --- a/test/mintCurrency.test.ts +++ b/test/erc721m/mintCurrency.test.ts @@ -1,4 +1,4 @@ -import { ERC721CM, ERC721M } from '../typechain-types'; +import { ERC721CM, ERC721M } from '../../typechain-types'; import { Contract, Signer } from 'ethers'; import { ethers } from 'hardhat'; import { expect } from 'chai'; diff --git a/test/foundry/MagicDropCloneFactoryTest.t.sol b/test/factory/MagicDropCloneFactoryTest.t.sol similarity index 100% rename from test/foundry/MagicDropCloneFactoryTest.t.sol rename to test/factory/MagicDropCloneFactoryTest.t.sol diff --git a/test/foundry/MagicDropTokenImplRegistryTest.t.sol b/test/factory/MagicDropTokenImplRegistryTest.t.sol similarity index 100% rename from test/foundry/MagicDropTokenImplRegistryTest.t.sol rename to test/factory/MagicDropTokenImplRegistryTest.t.sol From 5ec5ed1a24af30f306e94f7482b54dcd35c0ed9f Mon Sep 17 00:00:00 2001 From: Wolfy Date: Sat, 21 Sep 2024 19:25:01 -0400 Subject: [PATCH 025/145] organize scripts Signed-off-by: Wolfy --- hardhat.config.ts | 2 +- scripts/{ => admin}/freezeTrading.ts | 4 +- scripts/{ => admin}/thawTrading.ts | 4 +- scripts/{ => admin}/transferOwnership.ts | 2 +- scripts/{ => deploy}/deploy.ts | 24 ++++---- scripts/{ => deploy}/deploy1155.ts | 33 +++++++---- .../{dev => deploy}/deploy721BatchTransfer.ts | 0 scripts/{ => deploy}/deployBA.ts | 6 +- scripts/{ => deploy}/deployClone.ts | 11 +++- scripts/{ => deploy}/deployCloneFactory.ts | 13 ++--- scripts/{ => deploy}/deployOwnedRegistrant.ts | 0 scripts/index.ts | 58 +++++++++---------- scripts/{ => mint}/mint.ts | 2 +- scripts/{ => mint}/ownerMint.ts | 4 +- scripts/{ => mint}/ownerMint1155.ts | 18 ++++-- scripts/{ => setup}/set1155Stages.ts | 17 ++++-- scripts/{ => setup}/setBaseURI.ts | 4 +- scripts/{ => setup}/setCrossmintAddress.ts | 2 +- scripts/{ => setup}/setGlobalWalletLimit.ts | 2 +- scripts/{ => setup}/setMaxMintableSupply.ts | 0 .../{ => setup}/setMinContributionInWei.ts | 2 +- scripts/{ => setup}/setMintable.ts | 2 +- scripts/{ => setup}/setPrice.ts | 2 +- scripts/{ => setup}/setStages.ts | 13 ++++- .../setStartAndEndTimeUnixSeconds.ts | 2 +- .../{ => setup}/setTimestampExpirySeconds.ts | 2 +- scripts/{ => transfer}/send721Batch.ts | 4 +- scripts/{ => transfer}/sendRefund.ts | 2 +- scripts/{ => transfer}/sendRefundBatch.ts | 2 +- scripts/{ => transfer}/sendTokensAndRefund.ts | 2 +- .../sendTokensAndRefundBatch.ts | 2 +- scripts/{ => utils}/cleanWhitelist.ts | 2 +- .../ERC721CMInitializable_V2Test.t.sol | 6 +- 33 files changed, 141 insertions(+), 108 deletions(-) rename scripts/{ => admin}/freezeTrading.ts (95%) rename scripts/{ => admin}/thawTrading.ts (89%) rename scripts/{ => admin}/transferOwnership.ts (91%) rename scripts/{ => deploy}/deploy.ts (85%) rename scripts/{ => deploy}/deploy1155.ts (81%) rename scripts/{dev => deploy}/deploy721BatchTransfer.ts (100%) rename scripts/{ => deploy}/deployBA.ts (93%) rename scripts/{ => deploy}/deployClone.ts (88%) rename scripts/{ => deploy}/deployCloneFactory.ts (84%) rename scripts/{ => deploy}/deployOwnedRegistrant.ts (100%) rename scripts/{ => mint}/mint.ts (94%) rename scripts/{ => mint}/ownerMint.ts (93%) rename scripts/{ => mint}/ownerMint1155.ts (73%) rename scripts/{ => setup}/set1155Stages.ts (90%) rename scripts/{ => setup}/setBaseURI.ts (90%) rename scripts/{ => setup}/setCrossmintAddress.ts (93%) rename scripts/{ => setup}/setGlobalWalletLimit.ts (92%) rename scripts/{ => setup}/setMaxMintableSupply.ts (100%) rename scripts/{ => setup}/setMinContributionInWei.ts (93%) rename scripts/{ => setup}/setMintable.ts (92%) rename scripts/{ => setup}/setPrice.ts (91%) rename scripts/{ => setup}/setStages.ts (93%) rename scripts/{ => setup}/setStartAndEndTimeUnixSeconds.ts (93%) rename scripts/{ => setup}/setTimestampExpirySeconds.ts (93%) rename scripts/{ => transfer}/send721Batch.ts (96%) rename scripts/{ => transfer}/sendRefund.ts (91%) rename scripts/{ => transfer}/sendRefundBatch.ts (93%) rename scripts/{ => transfer}/sendTokensAndRefund.ts (92%) rename scripts/{ => transfer}/sendTokensAndRefundBatch.ts (93%) rename scripts/{ => utils}/cleanWhitelist.ts (95%) diff --git a/hardhat.config.ts b/hardhat.config.ts index aa0fd088..4ec9ba0f 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -44,7 +44,7 @@ import { cleanWhitelist, ownerMint1155, } from './scripts'; -import { deployCloneFactory } from './scripts/deployCloneFactory'; +import { deployCloneFactory } from './scripts/deploy/deployCloneFactory'; const config: HardhatUserConfig = { solidity: { diff --git a/scripts/freezeTrading.ts b/scripts/admin/freezeTrading.ts similarity index 95% rename from scripts/freezeTrading.ts rename to scripts/admin/freezeTrading.ts index 0661085b..5fa9e37f 100644 --- a/scripts/freezeTrading.ts +++ b/scripts/admin/freezeTrading.ts @@ -5,8 +5,8 @@ import { ERC721CV2_EMPTY_LIST, ERC721CV2_FREEZE_LEVEL, ERC721CV2_VALIDATOR, -} from './common/constants'; -import { estimateGas } from './utils/helper'; +} from '../common/constants'; +import { estimateGas } from '../utils/helper'; export interface IFreezeTrading { contract: string; diff --git a/scripts/thawTrading.ts b/scripts/admin/thawTrading.ts similarity index 89% rename from scripts/thawTrading.ts rename to scripts/admin/thawTrading.ts index b66b1f01..80186695 100644 --- a/scripts/thawTrading.ts +++ b/scripts/admin/thawTrading.ts @@ -1,7 +1,7 @@ import { confirm } from '@inquirer/prompts'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; -import { estimateGas } from './utils/helper'; +import { ContractDetails } from '../common/constants'; +import { estimateGas } from '../utils/helper'; export interface IThawTrading { contract: string; diff --git a/scripts/transferOwnership.ts b/scripts/admin/transferOwnership.ts similarity index 91% rename from scripts/transferOwnership.ts rename to scripts/admin/transferOwnership.ts index 5e366f6b..d6a94b65 100644 --- a/scripts/transferOwnership.ts +++ b/scripts/admin/transferOwnership.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; export interface ITransferOwnershipParams { owner: string; diff --git a/scripts/deploy.ts b/scripts/deploy/deploy.ts similarity index 85% rename from scripts/deploy.ts rename to scripts/deploy/deploy.ts index 262cba44..34ee6d63 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy/deploy.ts @@ -6,8 +6,12 @@ import { confirm } from '@inquirer/prompts'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails, RESERVOIR_RELAYER_MUTLICALLER, RESERVOIR_RELAYER_ROUTER } from './common/constants'; -import { checkCodeVersion, estimateGas } from './utils/helper'; +import { + ContractDetails, + RESERVOIR_RELAYER_MUTLICALLER, + RESERVOIR_RELAYER_ROUTER, +} from '../common/constants'; +import { checkCodeVersion, estimateGas } from '../utils/helper'; import { Overrides } from 'ethers'; interface IDeployParams { @@ -65,7 +69,10 @@ export const deploy = async ( } const [signer] = await hre.ethers.getSigners(); - const contractFactory = await hre.ethers.getContractFactory(contractName, signer); + const contractFactory = await hre.ethers.getContractFactory( + contractName, + signer, + ); const params = [ args.name, @@ -123,17 +130,6 @@ export const deploy = async ( `npx hardhat verify --network ${hre.network.name} ${contract.address} ${paramsStr}`, ); - // Set security policy to ME default - if (args.useerc721c) { - console.log('[ERC721CM] Setting security policy to ME default...'); - const ERC721CM = await hre.ethers.getContractFactory( - ContractDetails.ERC721CM.name, - ); - const erc721cm = ERC721CM.attach(contract.address); - const tx = await erc721cm.setToDefaultSecurityPolicy(); - console.log('[ERC721CM] Security policy set'); - } - // Add reservoir relay as authorized minter by default const ERC721CM = await hre.ethers.getContractFactory( ContractDetails.ERC721CM.name, diff --git a/scripts/deploy1155.ts b/scripts/deploy/deploy1155.ts similarity index 81% rename from scripts/deploy1155.ts rename to scripts/deploy/deploy1155.ts index 37423d22..68d2de83 100644 --- a/scripts/deploy1155.ts +++ b/scripts/deploy/deploy1155.ts @@ -1,7 +1,11 @@ import { confirm } from '@inquirer/prompts'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails, RESERVOIR_RELAYER_MUTLICALLER, RESERVOIR_RELAYER_ROUTER } from './common/constants'; -import { checkCodeVersion, estimateGas } from './utils/helper'; +import { + ContractDetails, + RESERVOIR_RELAYER_MUTLICALLER, + RESERVOIR_RELAYER_ROUTER, +} from '../common/constants'; +import { checkCodeVersion, estimateGas } from '../utils/helper'; import { Overrides } from 'ethers'; interface IDeploy1155Params { @@ -35,13 +39,17 @@ export const deploy1155 = async ( console.log(args); - const maxsupply = args.maxsupply.split(',').map(supply => - args.openedition? hre.ethers.BigNumber.from(0) : hre.ethers.BigNumber.from(supply.trim()) - ) + const maxsupply = args.maxsupply + .split(',') + .map((supply) => + args.openedition + ? hre.ethers.BigNumber.from(0) + : hre.ethers.BigNumber.from(supply.trim()), + ); - const globalwalletlimit = args.globalwalletlimit.split(',').map(limit => - hre.ethers.BigNumber.from(limit.trim()) - ); + const globalwalletlimit = args.globalwalletlimit + .split(',') + .map((limit) => hre.ethers.BigNumber.from(limit.trim())); const overrides: Overrides = {}; if (args.gaspricegwei) { @@ -52,7 +60,10 @@ export const deploy1155 = async ( } const [signer] = await hre.ethers.getSigners(); - const contractFactory = await hre.ethers.getContractFactory(contractName, signer); + const contractFactory = await hre.ethers.getContractFactory( + contractName, + signer, + ); const params = [ args.name, @@ -65,7 +76,7 @@ export const deploy1155 = async ( args.mintcurrency ?? hre.ethers.constants.AddressZero, args.fundreceiver ?? signer.address, args.erc2198royaltyreceiver, - args.erc2198royaltyfeenumerator + args.erc2198royaltyfeenumerator, ] as any[]; console.log( @@ -82,7 +93,7 @@ export const deploy1155 = async ( ) { return; } - + if (!(await confirm({ message: 'Continue to deploy?' }))) return; const contract = await contractFactory.deploy(...params, overrides); diff --git a/scripts/dev/deploy721BatchTransfer.ts b/scripts/deploy/deploy721BatchTransfer.ts similarity index 100% rename from scripts/dev/deploy721BatchTransfer.ts rename to scripts/deploy/deploy721BatchTransfer.ts diff --git a/scripts/deployBA.ts b/scripts/deploy/deployBA.ts similarity index 93% rename from scripts/deployBA.ts rename to scripts/deploy/deployBA.ts index 3c8abedf..df9788e4 100644 --- a/scripts/deployBA.ts +++ b/scripts/deploy/deployBA.ts @@ -5,9 +5,9 @@ // Runtime Environment's members available in the global scope. import { confirm } from '@inquirer/prompts'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { estimateGas } from './utils/helper'; +import { estimateGas } from '../utils/helper'; interface IDeployParams { name: string; @@ -19,6 +19,7 @@ interface IDeployParams { mincontributioninwei: number; auctionstarttime: string; auctionendtime: string; + fundReceiver: string; } export const deployBA = async ( @@ -49,6 +50,7 @@ export const deployBA = async ( hre.ethers.BigNumber.from(args.mincontributioninwei), Math.floor(new Date(args.auctionstarttime).getTime() / 1000), Math.floor(new Date(args.auctionendtime).getTime() / 1000), + args.fundReceiver, ] as const; console.log( diff --git a/scripts/deployClone.ts b/scripts/deploy/deployClone.ts similarity index 88% rename from scripts/deployClone.ts rename to scripts/deploy/deployClone.ts index e7167d16..0bff8910 100644 --- a/scripts/deployClone.ts +++ b/scripts/deploy/deployClone.ts @@ -1,7 +1,10 @@ import { confirm } from '@inquirer/prompts'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails, ERC721CMRoyaltiesCloneFactoryContract } from './common/constants'; -import { estimateGas } from './utils/helper'; +import { + ContractDetails, + ERC721CMRoyaltiesCloneFactoryContract, +} from '../common/constants'; +import { estimateGas } from '../utils/helper'; import { Overrides } from 'ethers'; export interface IDeployCloneParams { @@ -25,7 +28,9 @@ export const deployClone = async ( hre: HardhatRuntimeEnvironment, ) => { const { ethers } = hre; - const factory = await ethers.getContractFactory(ContractDetails.ERC721CMRoyaltiesCloneFactory.name); + const factory = await ethers.getContractFactory( + ContractDetails.ERC721CMRoyaltiesCloneFactory.name, + ); const factoryContract = factory.attach(ERC721CMRoyaltiesCloneFactoryContract); if (args.openedition) { diff --git a/scripts/deployCloneFactory.ts b/scripts/deploy/deployCloneFactory.ts similarity index 84% rename from scripts/deployCloneFactory.ts rename to scripts/deploy/deployCloneFactory.ts index 7b89fd2c..e225e741 100644 --- a/scripts/deployCloneFactory.ts +++ b/scripts/deploy/deployCloneFactory.ts @@ -6,8 +6,8 @@ import { confirm } from '@inquirer/prompts'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; -import { checkCodeVersion, estimateGas } from './utils/helper'; +import { ContractDetails } from '../common/constants'; +import { checkCodeVersion, estimateGas } from '../utils/helper'; import { Overrides } from 'ethers'; interface IDeployCloneFactoryParams { @@ -25,7 +25,8 @@ export const deployCloneFactory = async ( // Compile again in case we have a coverage build (binary too large to deploy) await hre.run('compile'); - const contractName: string = ContractDetails.ERC721CMRoyaltiesCloneFactory.name; + const contractName: string = + ContractDetails.ERC721CMRoyaltiesCloneFactory.name; const contractFactory = await hre.ethers.getContractFactory(contractName); const overrides: Overrides = {}; @@ -39,11 +40,7 @@ export const deployCloneFactory = async ( console.log(`Going to deploy ${contractName}.`); if ( - !(await estimateGas( - hre, - contractFactory.getDeployTransaction(), - overrides, - )) + !(await estimateGas(hre, contractFactory.getDeployTransaction(), overrides)) ) { return; } diff --git a/scripts/deployOwnedRegistrant.ts b/scripts/deploy/deployOwnedRegistrant.ts similarity index 100% rename from scripts/deployOwnedRegistrant.ts rename to scripts/deploy/deployOwnedRegistrant.ts diff --git a/scripts/index.ts b/scripts/index.ts index 0fb927eb..5383476b 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -1,34 +1,34 @@ -export * from './deploy'; -export * from './deploy1155'; -export * from './deployBA'; -export * from './deployClone'; -export * from './mint'; -export * from './ownerMint'; -export * from './ownerMint1155'; -export * from './setBaseURI'; -export * from './setCrossmintAddress'; -export * from './setGlobalWalletLimit'; -export * from './setMaxMintableSupply'; -export * from './setMintable'; -export * from './setStages'; -export * from './set1155Stages'; -export * from './setTimestampExpirySeconds'; -export * from './transferOwnership'; -export * from './setStartAndEndTimeUnixSeconds'; -export * from './setMinContributionInWei'; -export * from './sendRefund'; -export * from './sendRefundBatch'; -export * from './sendTokensAndRefund'; -export * from './sendTokensAndRefundBatch'; -export * from './setPrice'; +export * from './deploy/deploy'; +export * from './deploy/deploy1155'; +export * from './deploy/deployBA'; +export * from './deploy/deployClone'; +export * from './mint/mint'; +export * from './mint/ownerMint'; +export * from './mint/ownerMint1155'; +export * from './setup/setBaseURI'; +export * from './setup/setCrossmintAddress'; +export * from './setup/setGlobalWalletLimit'; +export * from './setup/setMaxMintableSupply'; +export * from './setup/setMintable'; +export * from './setup/setStages'; +export * from './setup/set1155Stages'; +export * from './setup/setTimestampExpirySeconds'; +export * from './admin/transferOwnership'; +export * from './setup/setStartAndEndTimeUnixSeconds'; +export * from './setup/setMinContributionInWei'; +export * from './transfer/sendRefund'; +export * from './transfer/sendRefundBatch'; +export * from './transfer/sendTokensAndRefund'; +export * from './transfer/sendTokensAndRefundBatch'; +export * from './setup/setPrice'; export * from './dev/getPrice'; export * from './dev/getStartTimeBA'; export * from './dev/getEndTimeBA'; export * from './dev/getMinContributionInWei'; -export * from './deployOwnedRegistrant'; +export * from './deploy/deployOwnedRegistrant'; export * from './dev/getContractCodehash'; -export * from './dev/deploy721BatchTransfer'; -export * from './send721Batch'; -export * from './freezeTrading'; -export * from './thawTrading'; -export * from './cleanWhitelist'; +export * from './deploy/deploy721BatchTransfer'; +export * from './transfer/send721Batch'; +export * from './admin/freezeTrading'; +export * from './admin/thawTrading'; +export * from './utils/cleanWhitelist'; diff --git a/scripts/mint.ts b/scripts/mint/mint.ts similarity index 94% rename from scripts/mint.ts rename to scripts/mint/mint.ts index 8592e84d..105245cf 100644 --- a/scripts/mint.ts +++ b/scripts/mint/mint.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; export interface IMintParams { contract: string; diff --git a/scripts/ownerMint.ts b/scripts/mint/ownerMint.ts similarity index 93% rename from scripts/ownerMint.ts rename to scripts/mint/ownerMint.ts index fd7919e6..4a7d5a37 100644 --- a/scripts/ownerMint.ts +++ b/scripts/mint/ownerMint.ts @@ -1,7 +1,7 @@ import { confirm } from '@inquirer/prompts'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; -import { estimateGas } from './utils/helper'; +import { ContractDetails } from '../common/constants'; +import { estimateGas } from '../utils/helper'; import { Overrides } from 'ethers'; export interface IOwnerMintParams { diff --git a/scripts/ownerMint1155.ts b/scripts/mint/ownerMint1155.ts similarity index 73% rename from scripts/ownerMint1155.ts rename to scripts/mint/ownerMint1155.ts index 7b09d756..781ded37 100644 --- a/scripts/ownerMint1155.ts +++ b/scripts/mint/ownerMint1155.ts @@ -1,13 +1,13 @@ import { confirm } from '@inquirer/prompts'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; -import { estimateGas } from './utils/helper'; +import { ContractDetails } from '../common/constants'; +import { estimateGas } from '../utils/helper'; import { Overrides } from 'ethers'; export interface IOwnerMint1155Params { contract: string; to?: string; - id: string; + id: string; qty: string; gaspricegwei?: number; gaslimit?: number; @@ -18,7 +18,9 @@ export const ownerMint1155 = async ( hre: HardhatRuntimeEnvironment, ) => { const { ethers } = hre; - const factory = await ethers.getContractFactory(ContractDetails.ERC1155M.name); + const factory = await ethers.getContractFactory( + ContractDetails.ERC1155M.name, + ); const contract = factory.attach(args.contract); const tokenId = ethers.BigNumber.from(args.id); const qty = ethers.BigNumber.from(args.qty); @@ -33,12 +35,16 @@ export const ownerMint1155 = async ( const tx = await contract.populateTransaction.ownerMint(to, tokenId, qty); if (!(await estimateGas(hre, tx, overrides))) return; - console.log(`Going to mint ${qty.toNumber()} token(s) with tokenId = ${tokenId.toNumber()} to ${to}`); + console.log( + `Going to mint ${qty.toNumber()} token(s) with tokenId = ${tokenId.toNumber()} to ${to}`, + ); if (!(await confirm({ message: 'Continue?' }))) return; const submittedTx = await contract.ownerMint(to, tokenId, qty, overrides); console.log(`Submitted tx ${submittedTx.hash}`); await submittedTx.wait(); - console.log(`Minted ${qty.toNumber()} token(s) with tokenId = ${tokenId.toNumber()} to ${to}`); + console.log( + `Minted ${qty.toNumber()} token(s) with tokenId = ${tokenId.toNumber()} to ${to}`, + ); }; diff --git a/scripts/set1155Stages.ts b/scripts/setup/set1155Stages.ts similarity index 90% rename from scripts/set1155Stages.ts rename to scripts/setup/set1155Stages.ts index bb123574..6949c765 100644 --- a/scripts/set1155Stages.ts +++ b/scripts/setup/set1155Stages.ts @@ -2,8 +2,12 @@ import { confirm } from '@inquirer/prompts'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { MerkleTree } from 'merkletreejs'; import fs from 'fs'; -import { ContractDetails } from './common/constants'; -import { cleanVariableWalletLimit, cleanWhitelist, estimateGas } from './utils/helper'; +import { ContractDetails } from '../common/constants'; +import { + cleanVariableWalletLimit, + cleanWhitelist, + estimateGas, +} from '../utils/helper'; import { Overrides } from 'ethers'; export interface ISet1155StagesParams { @@ -33,7 +37,9 @@ export const set1155Stages = async ( fs.readFileSync(args.stages, 'utf-8'), ) as StageConfig[]; - const factory = await ethers.getContractFactory(ContractDetails.ERC1155M.name); + const factory = await ethers.getContractFactory( + ContractDetails.ERC1155M.name, + ); const contract = factory.attach(args.contract); const overrides: Overrides = {}; @@ -70,7 +76,10 @@ export const set1155Stages = async ( ); return mt.getHexRoot(); } else if (stage.variableWalletLimitPath) { - const filteredMap = await cleanVariableWalletLimit(stage.variableWalletLimitPath, true); + const filteredMap = await cleanVariableWalletLimit( + stage.variableWalletLimitPath, + true, + ); const leaves: any[] = []; for (const [address, limit] of filteredMap.entries()) { const digest = ethers.utils.solidityKeccak256( diff --git a/scripts/setBaseURI.ts b/scripts/setup/setBaseURI.ts similarity index 90% rename from scripts/setBaseURI.ts rename to scripts/setup/setBaseURI.ts index d739c775..c84d75ba 100644 --- a/scripts/setBaseURI.ts +++ b/scripts/setup/setBaseURI.ts @@ -1,7 +1,7 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; import { Overrides } from 'ethers'; -import { estimateGas } from './utils/helper'; +import { estimateGas } from '../utils/helper'; interface ISetBaseURIParams { uri: string; diff --git a/scripts/setCrossmintAddress.ts b/scripts/setup/setCrossmintAddress.ts similarity index 93% rename from scripts/setCrossmintAddress.ts rename to scripts/setup/setCrossmintAddress.ts index a3dbef34..240652cc 100644 --- a/scripts/setCrossmintAddress.ts +++ b/scripts/setup/setCrossmintAddress.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; export interface ISetCrossmintAddress { crossmintaddress: string; diff --git a/scripts/setGlobalWalletLimit.ts b/scripts/setup/setGlobalWalletLimit.ts similarity index 92% rename from scripts/setGlobalWalletLimit.ts rename to scripts/setup/setGlobalWalletLimit.ts index 272c438d..d329639a 100644 --- a/scripts/setGlobalWalletLimit.ts +++ b/scripts/setup/setGlobalWalletLimit.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; interface ISetBaseURIParams { limit: string; diff --git a/scripts/setMaxMintableSupply.ts b/scripts/setup/setMaxMintableSupply.ts similarity index 100% rename from scripts/setMaxMintableSupply.ts rename to scripts/setup/setMaxMintableSupply.ts diff --git a/scripts/setMinContributionInWei.ts b/scripts/setup/setMinContributionInWei.ts similarity index 93% rename from scripts/setMinContributionInWei.ts rename to scripts/setup/setMinContributionInWei.ts index 9be5acc1..1d4cf2f3 100644 --- a/scripts/setMinContributionInWei.ts +++ b/scripts/setup/setMinContributionInWei.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; export interface ISetMinContributionInWeiParams { contract: string; diff --git a/scripts/setMintable.ts b/scripts/setup/setMintable.ts similarity index 92% rename from scripts/setMintable.ts rename to scripts/setup/setMintable.ts index ba295367..73f2031e 100644 --- a/scripts/setMintable.ts +++ b/scripts/setup/setMintable.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; export interface ISetMintableParams { mintable: boolean; diff --git a/scripts/setPrice.ts b/scripts/setup/setPrice.ts similarity index 91% rename from scripts/setPrice.ts rename to scripts/setup/setPrice.ts index e36cc1b6..c21eca01 100644 --- a/scripts/setPrice.ts +++ b/scripts/setup/setPrice.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; export interface ISetPriceParams { contract: string; diff --git a/scripts/setStages.ts b/scripts/setup/setStages.ts similarity index 93% rename from scripts/setStages.ts rename to scripts/setup/setStages.ts index 8ed4b201..0c41d117 100644 --- a/scripts/setStages.ts +++ b/scripts/setup/setStages.ts @@ -2,8 +2,12 @@ import { confirm } from '@inquirer/prompts'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { MerkleTree } from 'merkletreejs'; import fs from 'fs'; -import { ContractDetails } from './common/constants'; -import { cleanVariableWalletLimit, cleanWhitelist, estimateGas } from './utils/helper'; +import { ContractDetails } from '../common/constants'; +import { + cleanVariableWalletLimit, + cleanWhitelist, + estimateGas, +} from '../utils/helper'; import { Overrides } from 'ethers'; export interface ISetStagesParams { @@ -70,7 +74,10 @@ export const setStages = async ( ); return mt.getHexRoot(); } else if (stage.variableWalletLimitPath) { - const filteredMap = await cleanVariableWalletLimit(stage.variableWalletLimitPath, true); + const filteredMap = await cleanVariableWalletLimit( + stage.variableWalletLimitPath, + true, + ); const leaves: any[] = []; for (const [address, limit] of filteredMap.entries()) { const digest = ethers.utils.solidityKeccak256( diff --git a/scripts/setStartAndEndTimeUnixSeconds.ts b/scripts/setup/setStartAndEndTimeUnixSeconds.ts similarity index 93% rename from scripts/setStartAndEndTimeUnixSeconds.ts rename to scripts/setup/setStartAndEndTimeUnixSeconds.ts index 81d2317c..67e0d945 100644 --- a/scripts/setStartAndEndTimeUnixSeconds.ts +++ b/scripts/setup/setStartAndEndTimeUnixSeconds.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; export interface ISetStartAndEndTimeUnixSecondsParams { contract: string; diff --git a/scripts/setTimestampExpirySeconds.ts b/scripts/setup/setTimestampExpirySeconds.ts similarity index 93% rename from scripts/setTimestampExpirySeconds.ts rename to scripts/setup/setTimestampExpirySeconds.ts index ce05ec31..f18d2a5c 100644 --- a/scripts/setTimestampExpirySeconds.ts +++ b/scripts/setup/setTimestampExpirySeconds.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; export interface ISetTimestampExpirySeconds { timestampexpiryseconds: string; diff --git a/scripts/send721Batch.ts b/scripts/transfer/send721Batch.ts similarity index 96% rename from scripts/send721Batch.ts rename to scripts/transfer/send721Batch.ts index 1719aa07..ef17adec 100644 --- a/scripts/send721Batch.ts +++ b/scripts/transfer/send721Batch.ts @@ -1,8 +1,8 @@ import { confirm } from '@inquirer/prompts'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ERC721BatchTransferContract } from './common/constants'; +import { ERC721BatchTransferContract } from '../common/constants'; import fs from 'fs'; -import { estimateGas } from './utils/helper'; +import { estimateGas } from '../utils/helper'; export interface ISend721BatchParams { contract: string; diff --git a/scripts/sendRefund.ts b/scripts/transfer/sendRefund.ts similarity index 91% rename from scripts/sendRefund.ts rename to scripts/transfer/sendRefund.ts index b76cde1e..e181a52f 100644 --- a/scripts/sendRefund.ts +++ b/scripts/transfer/sendRefund.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; export interface ISendRefundParams { contract: string; diff --git a/scripts/sendRefundBatch.ts b/scripts/transfer/sendRefundBatch.ts similarity index 93% rename from scripts/sendRefundBatch.ts rename to scripts/transfer/sendRefundBatch.ts index 3432ee11..a3a93796 100644 --- a/scripts/sendRefundBatch.ts +++ b/scripts/transfer/sendRefundBatch.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; import fs from 'fs'; export interface ISendRefundBatchParams { diff --git a/scripts/sendTokensAndRefund.ts b/scripts/transfer/sendTokensAndRefund.ts similarity index 92% rename from scripts/sendTokensAndRefund.ts rename to scripts/transfer/sendTokensAndRefund.ts index f91bc053..5a0e1f53 100644 --- a/scripts/sendTokensAndRefund.ts +++ b/scripts/transfer/sendTokensAndRefund.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; export interface ISendTokensRefundParams { contract: string; diff --git a/scripts/sendTokensAndRefundBatch.ts b/scripts/transfer/sendTokensAndRefundBatch.ts similarity index 93% rename from scripts/sendTokensAndRefundBatch.ts rename to scripts/transfer/sendTokensAndRefundBatch.ts index 028631c8..803689d1 100644 --- a/scripts/sendTokensAndRefundBatch.ts +++ b/scripts/transfer/sendTokensAndRefundBatch.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { ContractDetails } from './common/constants'; +import { ContractDetails } from '../common/constants'; import fs from 'fs'; export interface ISendTokensAndRefundBatchParams { diff --git a/scripts/cleanWhitelist.ts b/scripts/utils/cleanWhitelist.ts similarity index 95% rename from scripts/cleanWhitelist.ts rename to scripts/utils/cleanWhitelist.ts index 22add6d5..1eb28538 100644 --- a/scripts/cleanWhitelist.ts +++ b/scripts/utils/cleanWhitelist.ts @@ -1,5 +1,5 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { cleanWhitelist as cleanWL, cleanVariableWalletLimit } from './utils/helper'; +import { cleanWhitelist as cleanWL, cleanVariableWalletLimit } from './helper'; export interface ICleanWhitelistParams { whitelistpath: string; diff --git a/test/erc721m/ERC721CMInitializable_V2Test.t.sol b/test/erc721m/ERC721CMInitializable_V2Test.t.sol index 30410116..01cb13fa 100644 --- a/test/erc721m/ERC721CMInitializable_V2Test.t.sol +++ b/test/erc721m/ERC721CMInitializable_V2Test.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "erc721a/contracts/IERC721A.sol"; -import "forge-std/Test.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +import {IERC721A} from "erc721a/contracts/IERC721A.sol"; +import {Test} from "forge-std/Test.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC721CMInitializable_V2} from "../../contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol"; import {MintStageInfo} from "../../contracts/common/Structs.sol"; import {ErrorsAndEvents} from "../../contracts/common/ErrorsAndEvents.sol"; From 0ac96d6e6f69cc4479def57b9b1f7ec7a247eee6 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Sat, 21 Sep 2024 20:26:19 -0400 Subject: [PATCH 026/145] replace OwnableInitializable Signed-off-by: Wolfy --- contracts/access/OwnableInitializable.sol | 40 ---------------- .../factory/ERC721CMRoyaltiesCloneFactory.sol | 2 +- .../nft/erc721m/ERC721CMInitializable.sol | 2 +- .../ERC721CMRoyaltiesInitializable.sol | 7 ++- .../erc721m/v2/ERC721CMInitializable_V2.sol | 46 +++++++++---------- .../UpdatableRoyaltiesInitializable.sol | 2 +- contracts/utils/Constants.sol | 1 - .../ERC721CMInitializable_V2Test.t.sol | 2 +- .../extensions/ERC721CMRoyaltiesClone.test.ts | 2 +- 9 files changed, 31 insertions(+), 73 deletions(-) delete mode 100644 contracts/access/OwnableInitializable.sol diff --git a/contracts/access/OwnableInitializable.sol b/contracts/access/OwnableInitializable.sol deleted file mode 100644 index e92dbdaf..00000000 --- a/contracts/access/OwnableInitializable.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -import "@limitbreak/creator-token-standards/src/access/OwnablePermissions.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -/** - * @title OwnableInitializable - * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this - * contract is for use with EIP-1167 Minimal Proxies (Clones). - */ -abstract contract OwnableInitializable is OwnablePermissions, Ownable { - error OwnableInitializable__OwnerAlreadyInitialized(); - - bool private _ownerInitialized; - - /** - * @dev Leave the default constructor as it's required by Ownable. - */ - constructor() Ownable(msg.sender) {} - - /** - * @dev When EIP-1167 is used to clone a contract that inherits Ownable permissions, - * this is required to assign the initial contract owner, as the constructor is - * not called during the cloning process. - */ - function initializeOwner(address owner_) public { - if (owner() != address(0) || _ownerInitialized) { - revert OwnableInitializable__OwnerAlreadyInitialized(); - } - - _transferOwnership(owner_); - _ownerInitialized = true; - } - - function _requireCallerIsContractOwner() internal view virtual override { - _checkOwner(); - } -} diff --git a/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol b/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol index 5851d28b..32eb4c76 100644 --- a/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol +++ b/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol @@ -15,7 +15,7 @@ contract ERC721CMRoyaltiesCloneFactory { address private immutable IMPLEMENTATION; constructor() { - IMPLEMENTATION = address(new ERC721CMRoyaltiesInitializable()); + IMPLEMENTATION = address(new ERC721CMRoyaltiesInitializable(msg.sender)); } function create( diff --git a/contracts/nft/erc721m/ERC721CMInitializable.sol b/contracts/nft/erc721m/ERC721CMInitializable.sol index c03d48b3..f47dac34 100644 --- a/contracts/nft/erc721m/ERC721CMInitializable.sol +++ b/contracts/nft/erc721m/ERC721CMInitializable.sol @@ -9,8 +9,8 @@ import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {OwnableInitializable} from "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; import "../creator-token-standards/ERC721ACQueryableInitializable.sol"; -import "../../access/OwnableInitializable.sol"; import "./interfaces/IERC721MInitializable.sol"; import "../../utils/Constants.sol"; diff --git a/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol b/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol index d33fcf34..0fc15f32 100644 --- a/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol +++ b/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.4; import {ERC2981, UpdatableRoyaltiesInitializable} from "../../../royalties/UpdatableRoyaltiesInitializable.sol"; -import {ERC721ACQueryableInitializable, ERC721CMInitializable, IERC721AUpgradeable, OwnableInitializable} from "../ERC721CMInitializable.sol"; +import {ERC721ACQueryableInitializable, ERC721CMInitializable, IERC721AUpgradeable} from "../ERC721CMInitializable.sol"; +import "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; /** * @title ERC721CMRoyaltiesInitializable @@ -14,9 +15,7 @@ contract ERC721CMRoyaltiesInitializable is ERC721CMInitializable, UpdatableRoyaltiesInitializable { - constructor() { - _disableInitializers(); - } + constructor(address initialOwner) Ownable(initialOwner) {} function initialize( string memory collectionName, diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol index cafda983..aaa23cc6 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol @@ -2,18 +2,16 @@ pragma solidity ^0.8.4; -import "@openzeppelin/contracts/token/common/ERC2981.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; -import "../../../access/OwnableInitializable.sol"; -import "../interfaces/IERC721MInitializable.sol"; -import "../../../utils/Constants.sol"; -import "../../../common/interfaces/IInitializableToken.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +// import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {OwnableInitializable, Ownable, OwnablePermissions} from "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; +import {ERC721ACQueryableInitializable, ERC721AUpgradeable, IERC721AUpgradeable} from "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; +import {IERC721MInitializable, MintStageInfo} from "../interfaces/IERC721MInitializable.sol"; +import {MINT_FEE_RECEIVER} from "../../../utils/Constants.sol"; +import {IInitializableToken} from "../../../common/interfaces/IInitializableToken.sol"; /** * @title ERC721CMInitializable @@ -71,10 +69,12 @@ contract ERC721CMInitializable_V2 is uint256 private _totalMintFee; // Fund receiver - address public FUND_RECEIVER; + address private _fundReceiver; uint256 public constant VERSION = 2; + constructor(address initialOwner) Ownable(initialOwner) {} + function initialize( string calldata name, string calldata symbol, @@ -101,7 +101,7 @@ contract ERC721CMInitializable_V2 is _tokenURISuffix = tokenURISuffix; _timestampExpirySeconds = timestampExpirySeconds; _mintCurrency = mintCurrency; - FUND_RECEIVER = fundReceiver; + _fundReceiver = fundReceiver; // Set crossmint address _crossmintAddress = crossmintAddress; @@ -264,7 +264,7 @@ contract ERC721CMInitializable_V2 is uint256 index ) external view override returns (MintStageInfo memory, uint32, uint256) { if (index >= _mintStages.length) { - revert("InvalidStage"); + revert InvalidStage(); } uint32 walletMinted = _stageMintedCountsPerWallet[index][msg.sender]; uint256 stageMinted = _stageMintedCounts[index]; @@ -289,8 +289,8 @@ contract ERC721CMInitializable_V2 is function mint( uint32 qty, bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature + uint64, + bytes calldata ) external payable virtual nonReentrant { _mintInternal(qty, msg.sender, 0, proof); } @@ -308,8 +308,8 @@ contract ERC721CMInitializable_V2 is uint32 qty, uint32 limit, bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature + uint64, + bytes calldata ) external payable virtual nonReentrant { _mintInternal(qty, msg.sender, limit, proof); } @@ -327,8 +327,8 @@ contract ERC721CMInitializable_V2 is uint32 qty, address to, bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature + uint64, + bytes calldata ) external payable nonReentrant { if (_crossmintAddress == address(0)) revert CrossmintAddressNotSet(); @@ -436,7 +436,7 @@ contract ERC721CMInitializable_V2 is _totalMintFee = 0; uint256 remainingValue = address(this).balance; - (success, ) = FUND_RECEIVER.call{value: remainingValue}(""); + (success, ) = _fundReceiver.call{value: remainingValue}(""); if (!success) revert WithdrawFailed(); emit Withdraw(_totalMintFee + remainingValue); @@ -452,7 +452,7 @@ contract ERC721CMInitializable_V2 is _totalMintFee = 0; uint256 remaining = IERC20(_mintCurrency).balanceOf(address(this)); - IERC20(_mintCurrency).safeTransfer(FUND_RECEIVER, remaining); + IERC20(_mintCurrency).safeTransfer(_fundReceiver, remaining); emit WithdrawERC20(_mintCurrency, _totalMintFee + remaining); } diff --git a/contracts/royalties/UpdatableRoyaltiesInitializable.sol b/contracts/royalties/UpdatableRoyaltiesInitializable.sol index 1e74d89b..c1115bcf 100644 --- a/contracts/royalties/UpdatableRoyaltiesInitializable.sol +++ b/contracts/royalties/UpdatableRoyaltiesInitializable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import {OwnableInitializable} from "../access/OwnableInitializable.sol"; +import {OwnableInitializable} from "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; diff --git a/contracts/utils/Constants.sol b/contracts/utils/Constants.sol index 9e767eb6..c8f87646 100644 --- a/contracts/utils/Constants.sol +++ b/contracts/utils/Constants.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.4; address constant CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS = 0x000000000000AAeB6D7670E522A718067333cd4E; address constant ME_SUBSCRIPTION = 0x0403c10721Ff2936EfF684Bbb57CD792Fd4b1B6c; - address constant MINT_FEE_RECEIVER = 0x0B98151bEdeE73f9Ba5F2C7b72dEa02D38Ce49Fc; uint64 constant TIMESTAMP_EXPIRY_SECONDS = 300; diff --git a/test/erc721m/ERC721CMInitializable_V2Test.t.sol b/test/erc721m/ERC721CMInitializable_V2Test.t.sol index 01cb13fa..f124ecdf 100644 --- a/test/erc721m/ERC721CMInitializable_V2Test.t.sol +++ b/test/erc721m/ERC721CMInitializable_V2Test.t.sol @@ -30,7 +30,7 @@ contract ERC721CMInitializable_V2Test is Test { vm.deal(minter, 2 ether); vm.deal(crossmintAddress, 1 ether); - nft = new ERC721CMInitializable_V2(); + nft = new ERC721CMInitializable_V2(owner); nft.initialize("Test", "TEST", payable(owner)); nft.setup( ".json", diff --git a/test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts b/test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts index 93f9f72a..72d1b4cb 100644 --- a/test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts +++ b/test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts @@ -111,7 +111,7 @@ describe('ERC721CMIRoyaltiesClone', function () { readonlyContract.setStages([ { price: ethers.utils.parseEther('0.5'), - mintFee: 0, + mintFee: 0, walletLimit: 3, merkleRoot: ethers.utils.hexZeroPad('0x1', 32), maxStageSupply: 5, From a85822d1d560e1d4771a66365636db3dc16694e9 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 27 Sep 2024 13:17:42 -0400 Subject: [PATCH 027/145] cleanup Signed-off-by: Wolfy --- .../erc721m/v2/ERC721CMInitializable_V2.sol | 49 +++++++++---------- .../ERC721CMInitializable_V2Test.t.sol | 4 +- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol index aaa23cc6..adaf37e1 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol @@ -1,29 +1,31 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -// import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {OwnableInitializable, Ownable, OwnablePermissions} from "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; + +import {Ownable} from "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; + import {ERC721ACQueryableInitializable, ERC721AUpgradeable, IERC721AUpgradeable} from "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; -import {IERC721MInitializable, MintStageInfo} from "../interfaces/IERC721MInitializable.sol"; import {MINT_FEE_RECEIVER} from "../../../utils/Constants.sol"; import {IInitializableToken} from "../../../common/interfaces/IInitializableToken.sol"; - +import {UpdatableRoyaltiesInitializable, ERC2981} from "../../../royalties/UpdatableRoyaltiesInitializable.sol"; +import {MintStageInfo} from "../../../common/Structs.sol"; +import {IERC721MInitializable} from "../interfaces/IERC721MInitializable.sol"; /** - * @title ERC721CMInitializable + * @title ERC721CMInitializable_V2 * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this * contract is for use with EIP-1167 Minimal Proxies (Clones). */ contract ERC721CMInitializable_V2 is + IInitializableToken, IERC721MInitializable, ERC721ACQueryableInitializable, - OwnableInitializable, - ReentrancyGuard, - IInitializableToken + UpdatableRoyaltiesInitializable, + ReentrancyGuard { using ECDSA for bytes32; using SafeERC20 for IERC20; @@ -73,7 +75,7 @@ contract ERC721CMInitializable_V2 is uint256 public constant VERSION = 2; - constructor(address initialOwner) Ownable(initialOwner) {} + constructor(address initialOwner) Ownable(initialOwner) {} function initialize( string calldata name, @@ -91,7 +93,9 @@ contract ERC721CMInitializable_V2 is address mintCurrency, address fundReceiver, address crossmintAddress, - MintStageInfo[] calldata initialStages + MintStageInfo[] calldata initialStages, + address defaultRoyaltyReceiver, + uint96 defaultRoyaltyFeeNumerator ) external onlyOwner { if (globalWalletLimit > maxMintableSupply) revert GlobalWalletLimitOverflow(); @@ -102,15 +106,13 @@ contract ERC721CMInitializable_V2 is _timestampExpirySeconds = timestampExpirySeconds; _mintCurrency = mintCurrency; _fundReceiver = fundReceiver; - - // Set crossmint address _crossmintAddress = crossmintAddress; - emit SetCrossmintAddress(crossmintAddress); - // Set initial stages if (initialStages.length > 0) { _setStages(initialStages); } + + setDefaultRoyalty(defaultRoyaltyReceiver, defaultRoyaltyFeeNumerator); } function _setStages(MintStageInfo[] calldata newStages) internal { @@ -551,15 +553,6 @@ contract ERC721CMInitializable_V2 is } } - function _requireCallerIsContractOwner() - internal - view - virtual - override(OwnableInitializable, OwnablePermissions) - { - _checkOwner(); - } - /** * @notice Returns the function selector for the transfer validator's validation function to be called * @notice for transaction simulation. @@ -572,4 +565,10 @@ contract ERC721CMInitializable_V2 is function _tokenType() internal pure override returns(uint16) { return uint16(721); } + + function supportsInterface(bytes4 interfaceId) public view override(ERC2981, IERC721AUpgradeable, ERC721ACQueryableInitializable) returns (bool) { + return super.supportsInterface(interfaceId) || + ERC2981.supportsInterface(interfaceId) || + ERC721ACQueryableInitializable.supportsInterface(interfaceId); + } } diff --git a/test/erc721m/ERC721CMInitializable_V2Test.t.sol b/test/erc721m/ERC721CMInitializable_V2Test.t.sol index f124ecdf..e75112d3 100644 --- a/test/erc721m/ERC721CMInitializable_V2Test.t.sol +++ b/test/erc721m/ERC721CMInitializable_V2Test.t.sol @@ -40,7 +40,9 @@ contract ERC721CMInitializable_V2Test is Test { address(0), fundReceiver, crossmintAddress, - new MintStageInfo[](0) + new MintStageInfo[](0), + address(this), + 0 ); } From 8021d0267d06a905b4368370ebf7a85a96a2436d Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 27 Sep 2024 13:35:36 -0400 Subject: [PATCH 028/145] remove old factory, and add authorized minters Signed-off-by: Wolfy --- .../factory/ERC721CMRoyaltiesCloneFactory.sol | 53 - .../ERC721CMRoyaltiesInitializable.sol | 67 - .../interfaces/IERC721MInitializable.sol | 9 + .../erc721m/v2/ERC721CMInitializable_V2.sol | 196 ++- scripts/common/constants.ts | 5 - .../extensions/ERC721CMRoyaltiesClone.test.ts | 1411 ----------------- .../ERC721CMRoyaltiesCloneFactory.test.ts | 95 -- 7 files changed, 189 insertions(+), 1647 deletions(-) delete mode 100644 contracts/factory/ERC721CMRoyaltiesCloneFactory.sol delete mode 100644 contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol delete mode 100644 test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts delete mode 100644 test/erc721m/extensions/ERC721CMRoyaltiesCloneFactory.test.ts diff --git a/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol b/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol deleted file mode 100644 index 32eb4c76..00000000 --- a/contracts/factory/ERC721CMRoyaltiesCloneFactory.sol +++ /dev/null @@ -1,53 +0,0 @@ -//SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -import {ERC721CMRoyaltiesInitializable} from "../nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol"; -import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; - -/** - * @title ERC721CMRoyaltiesCloneFactory - * @dev The factory contract that creates EIP-1167 Minimal Proxies (Clones) of ERC721CMRoyaltiesInitializable. - */ -contract ERC721CMRoyaltiesCloneFactory { - event CreateClone(address clone); - - address private immutable IMPLEMENTATION; - - constructor() { - IMPLEMENTATION = address(new ERC721CMRoyaltiesInitializable(msg.sender)); - } - - function create( - string memory collectionName, - string memory collectionSymbol, - string memory tokenURISuffix, - uint256 maxMintableSupply, - uint256 globalWalletLimit, - uint64 timestampExpirySeconds, - address mintCurrency, - address fundReceiver, - address royaltyReceiver, - uint96 royaltyFeeNumerator - ) external returns (address) { - address clone = Clones.clone(IMPLEMENTATION); - ERC721CMRoyaltiesInitializable(clone).initialize( - collectionName, - collectionSymbol, - tokenURISuffix, - maxMintableSupply, - globalWalletLimit, - timestampExpirySeconds, - mintCurrency, - fundReceiver, - royaltyReceiver, - royaltyFeeNumerator - ); - - // Transfer the ownership to msg sender - ERC721CMRoyaltiesInitializable(clone).transferOwnership(msg.sender); - - emit CreateClone(clone); - return clone; - } -} diff --git a/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol b/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol deleted file mode 100644 index 0fc15f32..00000000 --- a/contracts/nft/erc721m/extensions/ERC721CMRoyaltiesInitializable.sol +++ /dev/null @@ -1,67 +0,0 @@ -//SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -import {ERC2981, UpdatableRoyaltiesInitializable} from "../../../royalties/UpdatableRoyaltiesInitializable.sol"; -import {ERC721ACQueryableInitializable, ERC721CMInitializable, IERC721AUpgradeable} from "../ERC721CMInitializable.sol"; -import "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; - -/** - * @title ERC721CMRoyaltiesInitializable - * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this - * contract is for use with EIP-1167 Minimal Proxies (Clones). - */ -contract ERC721CMRoyaltiesInitializable is - ERC721CMInitializable, - UpdatableRoyaltiesInitializable -{ - constructor(address initialOwner) Ownable(initialOwner) {} - - function initialize( - string memory collectionName, - string memory collectionSymbol, - string memory tokenURISuffix, - uint256 maxMintableSupply, - uint256 globalWalletLimit, - uint64 timestampExpirySeconds, - address mintCurrency, - address fundReceiver, - address royaltyReceiver, - uint96 royaltyFeeNumerator - ) public initializerERC721A initializer { - __ERC721CMInitializable_init( - collectionName, - collectionSymbol, - tokenURISuffix, - maxMintableSupply, - globalWalletLimit, - timestampExpirySeconds, - mintCurrency, - fundReceiver - ); - setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); - } - - function supportsInterface( - bytes4 interfaceId - ) - public - view - virtual - override(ERC2981, ERC721ACQueryableInitializable, IERC721AUpgradeable) - returns (bool) - { - return - ERC721ACQueryableInitializable.supportsInterface(interfaceId) || - ERC2981.supportsInterface(interfaceId); - } - - function _requireCallerIsContractOwner() - internal - view - virtual - override(ERC721CMInitializable, OwnableInitializable) - { - _checkOwner(); - } -} diff --git a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol index 85326519..4e93c8a3 100644 --- a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol +++ b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol @@ -45,4 +45,13 @@ interface IERC721MInitializable is IERC721AQueryableUpgradeable, ERC721MErrorsAn uint64 timestamp, bytes calldata signature ) external payable; + + function authorizedMint( + uint32 qty, + address to, + uint32 limit, + bytes32[] calldata proof, + uint64 timestamp, + bytes calldata signature + ) external payable; } diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol index adaf37e1..6b908388 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol @@ -3,9 +3,11 @@ pragma solidity ^0.8.20; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof"; +import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {Ownable} from "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; @@ -36,6 +38,9 @@ contract ERC721CMInitializable_V2 is // Specify how long a signature from cosigner is valid for, recommend 300 seconds. uint64 private _timestampExpirySeconds; + // The address of the cosigner server. + address private _cosigner; + // The crossmint address. Need to set if using crossmint. address private _crossmintAddress; @@ -73,6 +78,9 @@ contract ERC721CMInitializable_V2 is // Fund receiver address private _fundReceiver; + // Authorized minters + mapping(address => bool) private _authorizedMinters; + uint256 public constant VERSION = 2; constructor(address initialOwner) Ownable(initialOwner) {} @@ -180,6 +188,14 @@ contract ERC721CMInitializable_V2 is _; } + /** + * @dev Returns whether the msg sender is authorized to mint. + */ + modifier onlyAuthorizedMinter() { + if (_authorizedMinters[_msgSender()] != true) revert NotAuthorized(); + _; + } + /** * @dev Returns cosign nonce. */ @@ -187,6 +203,44 @@ contract ERC721CMInitializable_V2 is return _numberMinted(minter); } + /** + * @dev Sets cosigner. + */ + function setCosigner(address cosigner) external onlyOwner { + _cosigner = cosigner; + emit SetCosigner(cosigner); + } + + /** + * @dev Sets expiry in seconds. This timestamp specifies how long a signature from cosigner is valid for. + */ + function setTimestampExpirySeconds(uint64 expiry) external onlyOwner { + _timestampExpirySeconds = expiry; + emit SetTimestampExpirySeconds(expiry); + } + + /** + * @dev Sets crossmint address if using crossmint. This allows the specified address to call `crossmint`. + */ + function setCrossmintAddress(address crossmintAddress) external onlyOwner { + _crossmintAddress = crossmintAddress; + emit SetCrossmintAddress(crossmintAddress); + } + + /** + * @dev Add authorized minter. Can only be called by contract owner. + */ + function addAuthorizedMinter(address minter) external onlyOwner { + _authorizedMinters[minter] = true; + } + + /** + * @dev Remove authorized minter. Can only be called by contract owner. + */ + function removeAuthorizedMinter(address minter) external onlyOwner { + _authorizedMinters[minter] = false; + } + /** * @dev Gets whether mintable. */ @@ -280,6 +334,7 @@ contract ERC721CMInitializable_V2 is return _mintCurrency; } + /** * @dev Mints token(s). * @@ -291,10 +346,10 @@ contract ERC721CMInitializable_V2 is function mint( uint32 qty, bytes32[] calldata proof, - uint64, - bytes calldata + uint64 timestamp, + bytes calldata signature ) external payable virtual nonReentrant { - _mintInternal(qty, msg.sender, 0, proof); + _mintInternal(qty, msg.sender, 0, proof, timestamp, signature); } /** @@ -310,10 +365,10 @@ contract ERC721CMInitializable_V2 is uint32 qty, uint32 limit, bytes32[] calldata proof, - uint64, - bytes calldata + uint64 timestamp, + bytes calldata signature ) external payable virtual nonReentrant { - _mintInternal(qty, msg.sender, limit, proof); + _mintInternal(qty, msg.sender, limit, proof, timestamp, signature); } /** @@ -329,15 +384,36 @@ contract ERC721CMInitializable_V2 is uint32 qty, address to, bytes32[] calldata proof, - uint64, - bytes calldata + uint64 timestamp, + bytes calldata signature ) external payable nonReentrant { if (_crossmintAddress == address(0)) revert CrossmintAddressNotSet(); // Check the caller is Crossmint if (msg.sender != _crossmintAddress) revert CrossmintOnly(); - _mintInternal(qty, to, 0, proof); + _mintInternal(qty, to, 0, proof, timestamp, signature); + } + + /** + * @dev Authorized mints token(s) with limit + * + * qty - number of tokens to mint + * to - the address to mint tokens to + * limit - limit for the given minter + * proof - the merkle proof generated on client side. This applies if using whitelist. + * timestamp - the current timestamp + * signature - the signature from cosigner if using cosigner. + */ + function authorizedMint( + uint32 qty, + address to, + uint32 limit, + bytes32[] calldata proof, + uint64 timestamp, + bytes calldata signature + ) external payable onlyAuthorizedMinter { + _mintInternal(qty, to, limit, proof, timestamp, signature); } /** @@ -347,20 +423,33 @@ contract ERC721CMInitializable_V2 is uint32 qty, address to, uint32 limit, - bytes32[] calldata proof + bytes32[] calldata proof, + uint64 timestamp, + bytes calldata signature ) internal canMint hasSupply(qty) { uint64 stageTimestamp = uint64(block.timestamp); + bool waiveMintFee = false; - MintStageInfo memory stage; + if (_cosigner != address(0)) { + waiveMintFee = assertValidCosign( + msg.sender, + qty, + timestamp, + signature + ); + _assertValidTimestamp(timestamp); + stageTimestamp = timestamp; + } uint256 activeStage = getActiveStageFromTimestamp(stageTimestamp); + MintStageInfo memory stage = _mintStages[activeStage]; - stage = _mintStages[activeStage]; + uint80 adjustedMintFee = waiveMintFee ? 0 : stage.mintFee; // Check value if minting with ETH if ( _mintCurrency == address(0) && - msg.value < (stage.price + stage.mintFee) * qty + msg.value < (stage.price + adjustedMintFee) * qty ) revert NotEnoughValue(); // Check stage supply if applicable @@ -402,14 +491,15 @@ contract ERC721CMInitializable_V2 is } if (_mintCurrency != address(0)) { + // ERC20 mint payment IERC20(_mintCurrency).safeTransferFrom( msg.sender, address(this), - (stage.price + stage.mintFee) * qty + (stage.price + adjustedMintFee) * qty ); } - _totalMintFee += stage.mintFee * qty; + _totalMintFee += adjustedMintFee * qty; _stageMintedCountsPerWallet[activeStage][to] += qty; _stageMintedCounts[activeStage] += qty; @@ -500,6 +590,33 @@ contract ERC721CMInitializable_V2 is : ""; } + /** + * @dev Returns data hash for the given minter, qty, waiveMintFee and timestamp. + */ + function getCosignDigest( + address minter, + uint32 qty, + bool waiveMintFee, + uint64 timestamp + ) public view returns (bytes32) { + if (_cosigner == address(0)) revert CosignerNotSet(); + return + MessageHashUtils.toEthSignedMessageHash( + keccak256( + abi.encodePacked( + address(this), + minter, + qty, + waiveMintFee, + _cosigner, + timestamp, + _chainID(), + getCosignNonce(minter) + ) + ) + ); + } + /** * @dev Returns URI for the collection-level metadata. */ @@ -514,6 +631,45 @@ contract ERC721CMInitializable_V2 is _contractURI = uri; } + function assertValidCosign( + address minter, + uint32 qty, + uint64 timestamp, + bytes memory signature + ) public view returns (bool) { + if ( + SignatureChecker.isValidSignatureNow( + _cosigner, + getCosignDigest( + minter, + qty, + /* waiveMintFee= */ true, + timestamp + ), + signature + ) + ) { + return true; + } + + if ( + SignatureChecker.isValidSignatureNow( + _cosigner, + getCosignDigest( + minter, + qty, + /* waiveMintFee= */ false, + timestamp + ), + signature + ) + ) { + return false; + } + + revert InvalidCosignSignature(); + } + /** * @dev Returns the current active stage based on timestamp. */ @@ -534,6 +690,14 @@ contract ERC721CMInitializable_V2 is revert InvalidStage(); } + /** + * @dev Validates the timestamp is not expired. + */ + function _assertValidTimestamp(uint64 timestamp) internal view { + if (timestamp < block.timestamp - _timestampExpirySeconds) + revert TimestampExpired(); + } + /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ diff --git a/scripts/common/constants.ts b/scripts/common/constants.ts index 1716889b..3b99390a 100644 --- a/scripts/common/constants.ts +++ b/scripts/common/constants.ts @@ -4,10 +4,8 @@ export const ContractDetails = { ERC721CM: { name: 'ERC721CM' }, // ERC721M on ERC721C v2 ERC721CMBasicRoyalties: { name: 'ERC721CMBasicRoyalties' }, // ERC721M on ERC721C v2 with basic royalties ERC721CMRoyalties: { name: 'ERC721CMRoyalties' }, // ERC721M on ERC721C v2 with updatable royalties - ERC721CMRoyaltiesInitializable: { name: 'ERC721CMRoyaltiesInitializable' }, // ERC721M on ERC721C v2 with updatable royalties and compatible with EIP-1167 clone ERC721M: { name: 'ERC721M' }, // The contract of direct sales ERC721MOperatorFilterer: { name: 'ERC721MOperatorFilterer' }, // ERC721M with operator filterer - ERC721CMRoyaltiesCloneFactory: { name: 'ERC721CMRoyaltiesCloneFactory' }, // ERC721CMRoyaltiesCloneFactory BucketAuction: { name: 'BucketAuction' }, // The contract of bucket auctions ERC1155M: { name: 'ERC1155M' }, // ERC1155M } as const; @@ -19,8 +17,5 @@ export const ERC721CV2_VALIDATOR = '0x721C00182a990771244d7A71B9FA2ea789A3b433'; export const ERC721CV2_FREEZE_LEVEL = 4; export const ERC721CV2_EMPTY_LIST = 4; -// Mainnet, Polygon, Base -export const ERC721CMRoyaltiesCloneFactoryContract = '0x7cEEd7215D71393d56966dA48C5727851326e101'; - export const RESERVOIR_RELAYER_MUTLICALLER = '0xb90ed4c123843cbFD66b11411Ee7694eF37E6E72'; export const RESERVOIR_RELAYER_ROUTER = '0x2f5d6b76bf8086797e1bd0b28bb4dd5583476cc9'; diff --git a/test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts b/test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts deleted file mode 100644 index 72d1b4cb..00000000 --- a/test/erc721m/extensions/ERC721CMRoyaltiesClone.test.ts +++ /dev/null @@ -1,1411 +0,0 @@ -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import chai, { assert, expect } from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import { ethers } from 'hardhat'; -import { MerkleTree } from 'merkletreejs'; -import { ERC721CMRoyaltiesInitializable, ERC721CMRoyaltiesCloneFactory } from '../../../typechain-types'; - -const { keccak256, getAddress } = ethers.utils; - -chai.use(chaiAsPromised); - -const ONE_HUNDRED_ETH = '0x56BC75E2D63100000'; -const WALLET_1 = '0x0764844ac95ABCa4F6306E592c7D9C9f3615f590'; -const WALLET_2 = '0xef59F379B48f2E92aBD94ADcBf714D170967925D'; - -describe('ERC721CMIRoyaltiesClone', function () { - let cloneFactory: ERC721CMRoyaltiesCloneFactory; - let contract: ERC721CMRoyaltiesInitializable; - let readonlyContract: ERC721CMRoyaltiesInitializable; - let owner: SignerWithAddress; - let readonly: SignerWithAddress; - let fundReceiver: SignerWithAddress; - let chainId: number; - - this.beforeAll(async () => { - const contractFactory = - await ethers.getContractFactory('ERC721CMRoyaltiesCloneFactory'); - cloneFactory = await contractFactory.deploy(); - await cloneFactory.deployed(); - }) - - beforeEach(async () => { - [owner, readonly, fundReceiver] = await ethers.getSigners(); - cloneFactory = cloneFactory.connect(owner); - - const tx = await cloneFactory.create( - 'TestCollection', - 'TestSymbol', - '.json', - 1000, - 0, - 60, - '0x0000000000000000000000000000000000000000', - await fundReceiver.getAddress(), - WALLET_1, - 10, - ); - - const receipt = await tx.wait(); - const event = receipt.events!.find(event => event.event === 'CreateClone')!; - const cloneAddress = event.args![0]; - - const contractFactory = - await ethers.getContractFactory('ERC721CMRoyaltiesInitializable'); - const erc721Clone = contractFactory.attach(cloneAddress); - - contract = erc721Clone.connect(owner); - readonlyContract = erc721Clone.connect(readonly); - chainId = await ethers.provider.getNetwork().then((n) => n.chainId); - }); - - it('Supports the right interfaces', async () => { - expect(await contract.supportsInterface('0x01ffc9a7')).to.be.true; // IERC165 - expect(await contract.supportsInterface('0x80ac58cd')).to.be.true; // IERC721 - expect(await contract.supportsInterface('0x5b5e139f')).to.be.true; // IERC721Metadata - expect(await contract.supportsInterface('0x2a55205a')).to.be.true; // IERC2981 - }); - - it('Contract can be paused/unpaused', async () => { - // starts unpaused - expect(await contract.getMintable()).to.be.true; - - // we should assert that the correct event is emitted - await expect(contract.setMintable(false)) - .to.emit(contract, 'SetMintable') - .withArgs(false); - expect(await contract.getMintable()).to.be.false; - - // readonlyContract should not be able to setMintable - await expect(readonlyContract.setMintable(true)).to.be.revertedWith( - 'Ownable', - ); - }); - - it('withdraws balance by owner', async () => { - // Send 100 wei to contract address for testing. - await ethers.provider.send('hardhat_setBalance', [ - contract.address, - '0x64', // 100 wei - ]); - expect( - (await contract.provider.getBalance(contract.address)).toNumber(), - ).to.equal(100); - - await expect(() => contract.withdraw()).to.changeEtherBalances( - [contract, owner, fundReceiver], - [-100, 0, 100], - ); - - expect( - (await contract.provider.getBalance(contract.address)).toNumber(), - ).to.equal(0); - - // readonlyContract should not be able to withdraw - await expect(readonlyContract.withdraw()).to.be.revertedWith('Ownable'); - }); - - describe('Stages', function () { - it('cannot set stages with readonly address', async () => { - await expect( - readonlyContract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 3, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 5, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 4, - merkleRoot: ethers.utils.hexZeroPad('0x2', 32), - maxStageSupply: 10, - startTimeUnixSeconds: 61, - endTimeUnixSeconds: 62, - }, - ]), - ).to.be.revertedWith('Ownable'); - }); - - it('cannot set stages with insufficient gap', async () => { - await expect( - contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 3, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 5, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 4, - merkleRoot: ethers.utils.hexZeroPad('0x2', 32), - maxStageSupply: 10, - startTimeUnixSeconds: 60, - endTimeUnixSeconds: 62, - }, - ]), - ).to.be.revertedWith('InsufficientStageTimeGap'); - }); - - it('cannot set stages due to startTimeUnixSeconds is not smaller than endTimeUnixSeconds', async () => { - await expect( - contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 3, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 5, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 0, - }, - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 4, - merkleRoot: ethers.utils.hexZeroPad('0x2', 32), - maxStageSupply: 10, - startTimeUnixSeconds: 61, - endTimeUnixSeconds: 61, - }, - ]), - ).to.be.revertedWith('InvalidStartAndEndTimestamp'); - - await expect( - contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 3, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 5, - startTimeUnixSeconds: 1, - endTimeUnixSeconds: 0, - }, - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 4, - merkleRoot: ethers.utils.hexZeroPad('0x2', 32), - maxStageSupply: 10, - startTimeUnixSeconds: 62, - endTimeUnixSeconds: 61, - }, - ]), - ).to.be.revertedWith('InvalidStartAndEndTimestamp'); - }); - - it('can set / reset stages', async () => { - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 3, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 5, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 4, - merkleRoot: ethers.utils.hexZeroPad('0x2', 32), - maxStageSupply: 10, - startTimeUnixSeconds: 61, - endTimeUnixSeconds: 62, - }, - ]); - - expect(await contract.getNumberStages()).to.equal(2); - - let [stageInfo, walletMintedCount] = await contract.getStageInfo(0); - expect(stageInfo.price).to.equal(ethers.utils.parseEther('0.5')); - expect(stageInfo.walletLimit).to.equal(3); - expect(stageInfo.maxStageSupply).to.equal(5); - expect(stageInfo.merkleRoot).to.equal(ethers.utils.hexZeroPad('0x1', 32)); - expect(walletMintedCount).to.equal(0); - - [stageInfo, walletMintedCount] = await contract.getStageInfo(1); - expect(stageInfo.price).to.equal(ethers.utils.parseEther('0.6')); - expect(stageInfo.walletLimit).to.equal(4); - expect(stageInfo.maxStageSupply).to.equal(10); - expect(stageInfo.merkleRoot).to.equal(ethers.utils.hexZeroPad('0x2', 32)); - expect(walletMintedCount).to.equal(0); - - // Update to one stage - await contract.setStages([ - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 4, - merkleRoot: ethers.utils.hexZeroPad('0x3', 32), - maxStageSupply: 0, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - ]); - - expect(await contract.getNumberStages()).to.equal(1); - [stageInfo, walletMintedCount] = await contract.getStageInfo(0); - expect(stageInfo.price).to.equal(ethers.utils.parseEther('0.6')); - expect(stageInfo.walletLimit).to.equal(4); - expect(stageInfo.maxStageSupply).to.equal(0); - expect(stageInfo.merkleRoot).to.equal(ethers.utils.hexZeroPad('0x3', 32)); - expect(walletMintedCount).to.equal(0); - - // Add another stage - await contract.setStages([ - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 4, - merkleRoot: ethers.utils.hexZeroPad('0x3', 32), - maxStageSupply: 0, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - { - price: ethers.utils.parseEther('0.7'), - mintFee: 0, - walletLimit: 5, - merkleRoot: ethers.utils.hexZeroPad('0x4', 32), - maxStageSupply: 5, - startTimeUnixSeconds: 61, - endTimeUnixSeconds: 62, - }, - ]); - expect(await contract.getNumberStages()).to.equal(2); - [stageInfo, walletMintedCount] = await contract.getStageInfo(1); - expect(stageInfo.price).to.equal(ethers.utils.parseEther('0.7')); - expect(stageInfo.walletLimit).to.equal(5); - expect(stageInfo.maxStageSupply).to.equal(5); - expect(stageInfo.merkleRoot).to.equal(ethers.utils.hexZeroPad('0x4', 32)); - expect(walletMintedCount).to.equal(0); - }); - - it('gets stage info', async () => { - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 3, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 5, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - ]); - - expect(await contract.getNumberStages()).to.equal(1); - - const [stageInfo, walletMintedCount] = await contract.getStageInfo(0); - expect(stageInfo.price).to.equal(ethers.utils.parseEther('0.5')); - expect(stageInfo.walletLimit).to.equal(3); - expect(stageInfo.maxStageSupply).to.equal(5); - expect(stageInfo.merkleRoot).to.equal(ethers.utils.hexZeroPad('0x1', 32)); - expect(walletMintedCount).to.equal(0); - }); - - it('gets stage info reverts for non-existent stage', async () => { - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 3, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 5, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - ]); - - const getStageInfo = readonlyContract.getStageInfo(1); - await expect(getStageInfo).to.be.revertedWith('InvalidStage'); - }); - - it('can find active stage', async () => { - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 3, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 5, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 4, - merkleRoot: ethers.utils.hexZeroPad('0x2', 32), - maxStageSupply: 10, - startTimeUnixSeconds: 61, - endTimeUnixSeconds: 62, - }, - ]); - - expect(await contract.getNumberStages()).to.equal(2); - expect(await contract.getActiveStageFromTimestamp(0)).to.equal(0); - - expect(await contract.getActiveStageFromTimestamp(61)).to.equal(1); - - const setActiveStage = contract.getActiveStageFromTimestamp(70); - await expect(setActiveStage).to.be.revertedWith('InvalidStage'); - }); - }); - - describe('Minting', function () { - it('revert if incorrect (less) amount sent', async () => { - // Get an estimated stage start time - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 10, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 5, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 2, - }, - ]); - await contract.setMintable(true); - - // Setup the test context: block.timestamp should comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - let mint; - mint = contract.mint(5, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('2.499'), - }); - await expect(mint).to.be.revertedWith('NotEnoughValue'); - - mint = contract.mint(1, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('0.499999'), - }); - await expect(mint).to.be.revertedWith('NotEnoughValue'); - }); - - it('revert on reentrancy', async () => { - const reentrancyFactory = await ethers.getContractFactory( - 'TestReentrantExploit', - ); - const reentrancyExploiter = await reentrancyFactory.deploy( - contract.address, - ); - await reentrancyExploiter.deployed(); - - // Get an estimated timestamp for the stage start - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.1'), - mintFee: 0, - walletLimit: 0, - merkleRoot: ethers.utils.hexZeroPad('0x', 32), - maxStageSupply: 0, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 100000, - }, - ]); - await contract.setMintable(true); - - // Setup the test context: block.timestamp should comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - await expect( - reentrancyExploiter.exploit(1, [], stageStart, '0x', { - value: ethers.utils.parseEther('0.2'), - }), - ).to.be.revertedWith('ReentrancyGuardReentrantCall'); - }); - - it('can set max mintable supply', async () => { - await contract.setMaxMintableSupply(99); - expect(await contract.getMaxMintableSupply()).to.equal(99); - - // can set the mintable supply again with the same value - await contract.setMaxMintableSupply(99); - expect(await contract.getMaxMintableSupply()).to.equal(99); - - // can set the mintable supply again with the lower value - await contract.setMaxMintableSupply(98); - expect(await contract.getMaxMintableSupply()).to.equal(98); - - // can not set the mintable supply with higher value - await expect(contract.setMaxMintableSupply(100)).to.be.rejectedWith( - 'CannotIncreaseMaxMintableSupply', - ); - - // readonlyContract should not be able to set max mintable supply - await expect( - readonlyContract.setMaxMintableSupply(99), - ).to.be.revertedWith('Ownable'); - }); - - it('enforces max mintable supply', async () => { - await contract.setMaxMintableSupply(99); - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 10, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 5, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 10, - merkleRoot: ethers.utils.hexZeroPad('0x2', 32), - maxStageSupply: 10, - startTimeUnixSeconds: 61, - endTimeUnixSeconds: 62, - }, - ]); - await contract.setMintable(true); - - // Mint 100 tokens (1 over MaxMintableSupply) - const mint = contract.mint( - 100, - [ethers.utils.hexZeroPad('0x', 32)], - 0, - '0x00', - { - value: ethers.utils.parseEther('2.5'), - }, - ); - await expect(mint).to.be.revertedWith('NoSupplyLeft'); - }); - - it('mint with unlimited stage limit', async () => { - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 100, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 0, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 2, - }, - ]); - await contract.setMaxMintableSupply(999); - await contract.setMintable(true); - - // Setup the test context: block.timestamp should comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - // Mint 100 tokens - wallet limit - await contract.mint(100, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('50'), - }); - - // Mint one more should fail - const mint = contract.mint( - 1, - [ethers.utils.hexZeroPad('0x', 32)], - 0, - '0x00', - { - value: ethers.utils.parseEther('0.5'), - }, - ); - - await expect(mint).to.be.revertedWith('WalletStageLimitExceeded'); - }); - - it('mint with unlimited wallet limit', async () => { - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 0, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 100, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 2, - }, - ]); - await contract.setMaxMintableSupply(999); - await contract.setMintable(true); - - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - // Mint 100 tokens - stage limit - await contract.mint(100, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('50'), - }); - - // Mint one more should fail - const mint = contract.mint( - 1, - [ethers.utils.hexZeroPad('0x', 32)], - 0, - '0x00', - { - value: ethers.utils.parseEther('0.5'), - }, - ); - - await expect(mint).to.be.revertedWith('StageSupplyExceeded'); - }); - - it('mint with free stage', async () => { - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0'), - mintFee: 0, - walletLimit: 0, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 100, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 1, - }, - ]); - await contract.setMintable(true); - - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - // Mint 100 tokens - stage limit - await readonlyContract.mint( - 1, - [ethers.utils.hexZeroPad('0x', 32)], - 0, - '0x00', - { - value: ethers.utils.parseEther('0'), - }, - ); - const [stageInfo, walletMintedCount, stagedMintedCount] = - await readonlyContract.getStageInfo(0); - expect(stageInfo.maxStageSupply).to.equal(100); - expect(walletMintedCount).to.equal(1); - expect(stagedMintedCount.toNumber()).to.equal(1); - }); - - it('enforces stage supply', async () => { - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 10, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 5, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 3, - }, - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 10, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 10, - startTimeUnixSeconds: stageStart + 63, - endTimeUnixSeconds: stageStart + 66, - }, - ]); - await contract.setMintable(true); - - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - // Mint 5 tokens - await expect( - contract.mint(5, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('2.5'), - }), - ).to.emit(contract, 'Transfer'); - - let [stageInfo, walletMintedCount, stagedMintedCount] = - await contract.getStageInfo(0); - - expect(stageInfo.maxStageSupply).to.equal(5); - expect(walletMintedCount).to.equal(5); - expect(stagedMintedCount.toNumber()).to.equal(5); - - // Mint another 1 should fail since the stage limit has been reached. - let mint = contract.mint( - 1, - [ethers.utils.hexZeroPad('0x', 32)], - 0, - '0x00', - { - value: ethers.utils.parseEther('0.5'), - }, - ); - await expect(mint).to.be.revertedWith('StageSupplyExceeded'); - - // Mint another 5 should fail since the stage limit has been reached. - mint = contract.mint(5, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('2.5'), - }); - await expect(mint).to.be.revertedWith('StageSupplyExceeded'); - - // Setup the test context: Update the block.timestamp to activate the 2nd stage - await ethers.provider.send('evm_mine', [stageStart + 62]); - - await contract.mint(8, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('4.8'), - }); - [stageInfo, walletMintedCount, stagedMintedCount] = - await contract.getStageInfo(1); - expect(stageInfo.maxStageSupply).to.equal(10); - expect(walletMintedCount).to.equal(8); - expect(stagedMintedCount.toNumber()).to.equal(8); - - await assert.isRejected( - contract.mint(3, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('1.8'), - }), - /StageSupplyExceeded/, - "Minting more than the stage's supply should fail", - ); - - await contract.mint(2, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('1.2'), - }); - - [stageInfo, walletMintedCount, stagedMintedCount] = - await contract.getStageInfo(1); - expect(walletMintedCount).to.equal(10); - expect(stagedMintedCount.toNumber()).to.equal(10); - - [stageInfo, walletMintedCount, stagedMintedCount] = - await contract.getStageInfo(0); - expect(walletMintedCount).to.equal(5); - expect(stagedMintedCount.toNumber()).to.equal(5); - - const [address] = await ethers.getSigners(); - const totalMinted = await contract.totalMintedByAddress( - await address.getAddress(), - ); - expect(totalMinted.toNumber()).to.equal(15); - }); - - it('enforces Merkle proof if required', async () => { - const accounts = (await ethers.getSigners()).map((signer) => - getAddress(signer.address).toLowerCase().trim(), - ); - const leaves = accounts.map((account) => - ethers.utils.solidityKeccak256(['address', 'uint32'], [account, 0]), - ); - const signerAddress = await ethers.provider.getSigner().getAddress(); - const merkleTree = new MerkleTree(leaves, ethers.utils.keccak256, { - sortPairs: true, - hashLeaves: false, - }); - const root = merkleTree.getHexRoot(); - - const leaf = ethers.utils.solidityKeccak256( - ['address', 'uint32'], - [signerAddress.toLowerCase().trim(), 0], - ); - const proof = merkleTree.getHexProof(leaf); - - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.1'), - mintFee: 0, - walletLimit: 10, - merkleRoot: root, - maxStageSupply: 5, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 3, - }, - ]); - await contract.setMintable(true); - - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - // Mint 1 token with valid proof - await contract.mint(1, proof, 0, '0x00', { - value: ethers.utils.parseEther('0.1'), - }); - const totalMinted = await contract.totalMintedByAddress(signerAddress); - expect(totalMinted.toNumber()).to.equal(1); - - // Mint 1 token with someone's else proof should be reverted - await expect( - readonlyContract.mint(1, proof, 0, '0x00', { - value: ethers.utils.parseEther('0.1'), - }), - ).to.be.rejectedWith('InvalidProof'); - }); - - it('reverts on invalid Merkle proof', async () => { - const root = ethers.utils.hexZeroPad('0x1', 32); - const proof = [ethers.utils.hexZeroPad('0x1', 32)]; - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 10, - merkleRoot: root, - maxStageSupply: 5, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 1, - }, - ]); - await contract.setMintable(true); - - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - // Mint 1 token with invalid proof - const mint = contract.mint(1, proof, 0, '0x00', { - value: ethers.utils.parseEther('0.5'), - }); - await expect(mint).to.be.revertedWith('InvalidProof'); - }); - - it('mint with limit', async () => { - const ownerAddress = await owner.getAddress(); - const readerAddress = await readonly.getAddress(); - const leaves = [ - ethers.utils.solidityKeccak256( - ['address', 'uint32'], - [ownerAddress, 2], - ), - ethers.utils.solidityKeccak256( - ['address', 'uint32'], - [readerAddress, 5], - ), - ]; - - const merkleTree = new MerkleTree(leaves, ethers.utils.keccak256, { - sortPairs: true, - hashLeaves: false, - }); - const root = merkleTree.getHexRoot(); - const ownerLeaf = ethers.utils.solidityKeccak256( - ['address', 'uint32'], - [ownerAddress, 2], - ); - const readerLeaf = ethers.utils.solidityKeccak256( - ['address', 'uint32'], - [readerAddress, 5], - ); - const ownerProof = merkleTree.getHexProof(ownerLeaf); - const readerProof = merkleTree.getHexProof(readerLeaf); - - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.1'), - mintFee: 0, - walletLimit: 10, - merkleRoot: root, - maxStageSupply: 100, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 100, - }, - ]); - await contract.setMintable(true); - - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - - // Owner mints 1 token with valid proof - await contract.mintWithLimit(1, 2, ownerProof, 0, '0x00', { - value: ethers.utils.parseEther('0.1'), - }); - expect( - (await contract.totalMintedByAddress(owner.getAddress())).toNumber(), - ).to.equal(1); - - // Owner mints 1 token with wrong limit and should be reverted. - await expect( - contract.mintWithLimit(1, 3, ownerProof, 0, '0x00', { - value: ethers.utils.parseEther('0.1'), - }), - ).to.be.rejectedWith('InvalidProof'); - - // Owner mints 2 tokens with valid proof and reverts. - await expect( - contract.mintWithLimit(2, 2, ownerProof, 0, '0x00', { - value: ethers.utils.parseEther('0.2'), - }), - ).to.be.rejectedWith('WalletStageLimitExceeded'); - - // Owner mints 1 token with valid proof. Now owner reaches the limit. - await contract.mintWithLimit(1, 2, ownerProof, 0, '0x00', { - value: ethers.utils.parseEther('0.1'), - }); - expect( - (await contract.totalMintedByAddress(owner.getAddress())).toNumber(), - ).to.equal(2); - - // Owner tries to mint more and reverts. - await expect( - contract.mintWithLimit(1, 2, ownerProof, 0, '0x00', { - value: ethers.utils.parseEther('0.1'), - }), - ).to.be.rejectedWith('WalletStageLimitExceeded'); - - // Reader mints 6 tokens with valid proof and reverts. - await expect( - readonlyContract.mintWithLimit(6, 5, readerProof, 0, '0x00', { - value: ethers.utils.parseEther('0.6'), - }), - ).to.be.rejectedWith('WalletStageLimitExceeded'); - - // Reader mints 5 tokens with valid proof. - await readonlyContract.mintWithLimit(5, 5, readerProof, 0, '0x00', { - value: ethers.utils.parseEther('0.5'), - }); - - // Reader mints 1 token with valid proof and reverts. - await expect( - readonlyContract.mintWithLimit(1, 5, readerProof, 0, '0x00', { - value: ethers.utils.parseEther('0.1'), - }), - ).to.be.rejectedWith('WalletStageLimitExceeded'); - }); - - it('crossmint', async () => { - const crossmintAddressStr = '0xdAb1a1854214684acE522439684a145E62505233'; - [owner, readonly] = await ethers.getSigners(); - - cloneFactory = cloneFactory.connect(owner); - const tx = await cloneFactory.create( - 'TestCollection', - 'TestSymbol', - '.json', - 1000, - 0, - 60, - '0x0000000000000000000000000000000000000000', - '0xef59F379B48f2E92aBD94ADcBf714D170967925D', - '0xef59F379B48f2E92aBD94ADcBf714D170967925D', - 100, - ); - - const receipt = await tx.wait(); - const event = receipt.events!.find(event => event.event === 'CreateClone')!; - const cloneAddress = event.args![0]; - - const contractFactory = - await ethers.getContractFactory('ERC721CMRoyaltiesInitializable'); - const erc721Clone = contractFactory.attach(cloneAddress); - - const ownerConn = erc721Clone.connect(owner); - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await ownerConn.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 0, - merkleRoot: ethers.utils.hexZeroPad('0x', 32), - maxStageSupply: 100, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 1, - }, - ]); - await ownerConn.setMintable(true); - await ownerConn.setCrossmintAddress(crossmintAddressStr); - - // Impersonate Crossmint wallet - const crossmintSigner = - await ethers.getImpersonatedSigner(crossmintAddressStr); - const crossmintAddress = await crossmintSigner.getAddress(); - - // Send some wei to impersonated account - await ethers.provider.send('hardhat_setBalance', [ - crossmintAddress, - '0xFFFFFFFFFFFFFFFF', - ]); - - const crossMintConn = erc721Clone.connect(crossmintSigner); - - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - await crossMintConn.crossmint( - 1, - readonly.address, - [ethers.utils.hexZeroPad('0x', 32)], - 0, - '0x00', - { - value: ethers.utils.parseEther('0.5'), - }, - ); - - const [stageInfo, walletMintedCount, stagedMintedCount] = - await ownerConn.getStageInfo(0); - expect(stageInfo.maxStageSupply).to.equal(100); - expect(walletMintedCount).to.equal(0); - expect(stagedMintedCount.toNumber()).to.equal(1); - }); - - it('crossmint reverts if crossmint address not set', async () => { - const accounts = (await ethers.getSigners()).map((signer) => - getAddress(signer.address), - ); - const [_, recipient] = await ethers.getSigners(); - - const merkleTree = new MerkleTree(accounts, keccak256, { - sortPairs: true, - hashLeaves: true, - }); - const root = merkleTree.getHexRoot(); - const proof = merkleTree.getHexProof(keccak256(recipient.address)); - - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 10, - merkleRoot: root, - maxStageSupply: 7, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - ]); - await contract.setMintable(true); - - const crossmint = contract.crossmint( - 7, - recipient.address, - proof, - 0, - '0x00', - { - value: ethers.utils.parseEther('3.5'), - }, - ); - - await expect(crossmint).to.be.revertedWith('CrossmintAddressNotSet'); - }); - - it('crossmint reverts on non-Crossmint sender', async () => { - const accounts = (await ethers.getSigners()).map((signer) => - getAddress(signer.address), - ); - const [_, recipient] = await ethers.getSigners(); - - const merkleTree = new MerkleTree(accounts, keccak256, { - sortPairs: true, - hashLeaves: true, - }); - const root = merkleTree.getHexRoot(); - const proof = merkleTree.getHexProof(keccak256(recipient.address)); - - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 10, - merkleRoot: root, - maxStageSupply: 7, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - ]); - await contract.setMintable(true); - await contract.setCrossmintAddress( - '0xdAb1a1854214684acE522439684a145E62505233', - ); - - const crossmint = contract.crossmint( - 7, - recipient.address, - proof, - 0, - '0x00', - { - value: ethers.utils.parseEther('3.5'), - }, - ); - - await expect(crossmint).to.be.revertedWith('CrossmintOnly'); - }); - - it('mints by owner', async () => { - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 1, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 1, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - ]); - await contract.setMintable(true); - - const [owner, address1] = await ethers.getSigners(); - - await contract.ownerMint(5, owner.address); - - const [, walletMintedCount, stagedMintedCount] = - await contract.getStageInfo(0); - expect(walletMintedCount).to.equal(0); - expect(stagedMintedCount.toNumber()).to.equal(0); - const ownerBalance = await contract.balanceOf(owner.address); - expect(ownerBalance.toNumber()).to.equal(5); - - await contract.ownerMint(5, address1.address); - const [, address1Minted] = await readonlyContract.getStageInfo(0, { - from: address1.address, - }); - expect(address1Minted).to.equal(0); - - const address1Balance = await contract.balanceOf(address1.address); - expect(address1Balance.toNumber()).to.equal(5); - - expect((await contract.totalSupply()).toNumber()).to.equal(10); - }); - - it('mints by owner - invalid cases', async () => { - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 1, - merkleRoot: ethers.utils.hexZeroPad('0x1', 32), - maxStageSupply: 1, - startTimeUnixSeconds: 0, - endTimeUnixSeconds: 1, - }, - ]); - await contract.setMintable(true); - await expect( - contract.ownerMint(1001, readonly.address), - ).to.be.revertedWith('NoSupplyLeft'); - }); - }); - - describe('Token URI', function () { - it('Reverts for nonexistent token', async () => { - await expect(contract.tokenURI(0)).to.be.revertedWith( - 'URIQueryForNonexistentToken', - ); - }); - - it('Returns empty tokenURI on empty baseURI', async () => { - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 10, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 5, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 1, - }, - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 10, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 10, - startTimeUnixSeconds: stageStart + 61, - endTimeUnixSeconds: stageStart + 62, - }, - ]); - await contract.setMintable(true); - - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - await contract.mint(2, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('2.5'), - }); - - expect(await contract.tokenURI(0)).to.equal(''); - expect(await contract.tokenURI(1)).to.equal(''); - - await expect(contract.tokenURI(2)).to.be.revertedWith( - 'URIQueryForNonexistentToken', - ); - }); - - it('Returns non-empty tokenURI on non-empty baseURI', async () => { - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.5'), - mintFee: 0, - walletLimit: 10, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 5, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 1, - }, - { - price: ethers.utils.parseEther('0.6'), - mintFee: 0, - walletLimit: 10, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 10, - startTimeUnixSeconds: stageStart + 61, - endTimeUnixSeconds: stageStart + 62, - }, - ]); - - await contract.setBaseURI('base_uri_'); - await contract.setMintable(true); - - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - await contract.mint(2, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('2.5'), - }); - - expect(await contract.tokenURI(0)).to.equal('base_uri_0.json'); - expect(await contract.tokenURI(1)).to.equal('base_uri_1.json'); - - await expect(contract.tokenURI(2)).to.be.revertedWith( - 'URIQueryForNonexistentToken', - ); - }); - }); - - describe('Global wallet limit', function () { - it('validates global wallet limit in constructor', async () => { - [owner] = await ethers.getSigners(); - cloneFactory = cloneFactory.connect(owner); - - await expect(cloneFactory.create( - 'TestCollection', - 'TestSymbol', - '.json', - 1000, - 1001, - 60, - '0x0000000000000000000000000000000000000000', - '0xef59F379B48f2E92aBD94ADcBf714D170967925D', - '0xef59F379B48f2E92aBD94ADcBf714D170967925D', - 100, - )).to.be.revertedWith('GlobalWalletLimitOverflow'); - }); - - it('sets global wallet limit', async () => { - await contract.setGlobalWalletLimit(2); - expect((await contract.getGlobalWalletLimit()).toNumber()).to.equal(2); - - await expect(contract.setGlobalWalletLimit(1001)).to.be.revertedWith( - 'GlobalWalletLimitOverflow', - ); - }); - - it('enforces global wallet limit', async () => { - await contract.setGlobalWalletLimit(2); - expect((await contract.getGlobalWalletLimit()).toNumber()).to.equal(2); - - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.1'), - mintFee: 0, - walletLimit: 0, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 100, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 2, - }, - ]); - await contract.setMintable(true); - - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - await contract.mint(2, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('0.2'), - }); - - await expect( - contract.mint(1, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('0.1'), - }), - ).to.be.revertedWith('WalletGlobalLimitExceeded'); - }); - }); - - describe('Token URI suffix', () => { - it('can set tokenURI suffix', async () => { - await contract.setMintable(true); - await contract.setTokenURISuffix('.json'); - await contract.setBaseURI( - 'ipfs://bafybeidntqfipbuvdhdjosntmpxvxyse2dkyfpa635u4g6txruvt5qf7y4/', - ); - - const block = await ethers.provider.getBlock( - await ethers.provider.getBlockNumber(), - ); - // +10 is a number bigger than the count of transactions up to mint - const stageStart = block.timestamp + 10; - // Set stages - await contract.setStages([ - { - price: ethers.utils.parseEther('0.1'), - mintFee: 0, - walletLimit: 0, - merkleRoot: ethers.utils.hexZeroPad('0x0', 32), - maxStageSupply: 0, - startTimeUnixSeconds: stageStart, - endTimeUnixSeconds: stageStart + 1, - }, - ]); - // Setup the test context: Update block.timestamp to comply to the stage being active - await ethers.provider.send('evm_mine', [stageStart - 1]); - // Mint and verify - await contract.mint(1, [ethers.utils.hexZeroPad('0x', 32)], 0, '0x00', { - value: ethers.utils.parseEther('0.1'), - }); - - const tokenUri = await contract.tokenURI(0); - expect(tokenUri).to.equal( - 'ipfs://bafybeidntqfipbuvdhdjosntmpxvxyse2dkyfpa635u4g6txruvt5qf7y4/0.json', - ); - }); - }); - - describe('Royalties', function () { - it('Set default royalty', async () => { - let royaltyInfo = await contract.royaltyInfo(0, 1000); - expect(royaltyInfo[0]).to.equal(WALLET_1); - expect(royaltyInfo[1].toNumber()).to.equal(1); - - royaltyInfo = await contract.royaltyInfo(1, 9999); - expect(royaltyInfo[0]).to.equal(WALLET_1); - expect(royaltyInfo[1].toNumber()).to.equal(9); - - royaltyInfo = await contract.royaltyInfo(1111, 9999999999); - expect(royaltyInfo[0]).to.equal(WALLET_1); - expect(royaltyInfo[1].toNumber()).to.equal(9999999); - - await contract.setDefaultRoyalty(WALLET_2, 0); - - royaltyInfo = await contract.royaltyInfo(0, 1000); - expect(royaltyInfo[0]).to.equal(WALLET_2); - expect(royaltyInfo[1].toNumber()).to.equal(0); - - royaltyInfo = await contract.royaltyInfo(1, 9999); - expect(royaltyInfo[0]).to.equal(WALLET_2); - expect(royaltyInfo[1].toNumber()).to.equal(0); - - royaltyInfo = await contract.royaltyInfo(1111, 9999999999); - expect(royaltyInfo[0]).to.equal(WALLET_2); - expect(royaltyInfo[1].toNumber()).to.equal(0); - }); - - it('Set token royalty', async () => { - let royaltyInfo = await contract.royaltyInfo(0, 1000); - expect(royaltyInfo[0]).to.equal(WALLET_1); - expect(royaltyInfo[1].toNumber()).to.equal(1); - - royaltyInfo = await contract.royaltyInfo(1, 9999); - expect(royaltyInfo[0]).to.equal(WALLET_1); - expect(royaltyInfo[1].toNumber()).to.equal(9); - - royaltyInfo = await contract.royaltyInfo(1111, 9999999999); - expect(royaltyInfo[0]).to.equal(WALLET_1); - expect(royaltyInfo[1].toNumber()).to.equal(9999999); - - await contract.setTokenRoyalty(1, WALLET_2, 100); - - royaltyInfo = await contract.royaltyInfo(0, 1000); - expect(royaltyInfo[0]).to.equal(WALLET_1); - expect(royaltyInfo[1].toNumber()).to.equal(1); - - royaltyInfo = await contract.royaltyInfo(1, 9999); - expect(royaltyInfo[0]).to.equal(WALLET_2); - expect(royaltyInfo[1].toNumber()).to.equal(99); - - royaltyInfo = await contract.royaltyInfo(1111, 9999999999); - expect(royaltyInfo[0]).to.equal(WALLET_1); - expect(royaltyInfo[1].toNumber()).to.equal(9999999); - }); - - it('Non-owner update reverts', async () => { - await expect( - readonlyContract.setTokenRoyalty(1, WALLET_2, 100), - ).to.be.revertedWith('OwnableUnauthorizedAccount'); - - await expect( - readonlyContract.setDefaultRoyalty(WALLET_2, 0), - ).to.be.revertedWith('OwnableUnauthorizedAccount'); - }); - }); -}); diff --git a/test/erc721m/extensions/ERC721CMRoyaltiesCloneFactory.test.ts b/test/erc721m/extensions/ERC721CMRoyaltiesCloneFactory.test.ts deleted file mode 100644 index c9faee30..00000000 --- a/test/erc721m/extensions/ERC721CMRoyaltiesCloneFactory.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import chai, { expect } from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import { ethers } from 'hardhat'; -import { ERC721CMRoyaltiesCloneFactory } from '../../../typechain-types'; -import { isAddress } from 'ethers/lib/utils'; - -chai.use(chaiAsPromised); - -describe('ERC721CMRoyaltiesCloneFactory', function () { - let cloneFactory: ERC721CMRoyaltiesCloneFactory; - let owner: SignerWithAddress; - - beforeEach(async () => { - const contractFactory = - await ethers.getContractFactory('ERC721CMRoyaltiesCloneFactory'); - cloneFactory = await contractFactory.deploy(); - await cloneFactory.deployed(); - - [owner] = await ethers.getSigners(); - cloneFactory.connect(owner); - }); - - it('creates clone', async () => { - await expect(createClone(cloneFactory)).to.emit(cloneFactory, 'CreateClone'); - }); - - it('creates multiple clones', async () => { - const clones = new Set(); - for (let i = 0; i < 10; i++) { - const tx = await createClone(cloneFactory); - - const receipt = await tx.wait(); - const event = receipt.events!.find(event => event.event === 'CreateClone')!; - const cloneAddress = event.args![0]; - expect(isAddress(cloneAddress)).to.be.true; - clones.add(cloneAddress); - } - expect(clones.size).to.be.equal(10); - }); - - it('can mint on clone', async () => { - const tx = await createClone(cloneFactory); - - const receipt = await tx.wait(); - const event = receipt.events!.find(event => event.event === 'CreateClone')!; - const cloneAddress = event.args![0]; - - const contractFactory = - await ethers.getContractFactory('ERC721CMRoyaltiesInitializable'); - const erc721Clone = contractFactory.attach(cloneAddress); - - expect(await erc721Clone.totalSupply()).to.equal(0); - await erc721Clone.ownerMint(3, owner.address); - expect(await erc721Clone.totalSupply()).to.equal(3); - }); - - it('can mint on multiple clones', async () => { - const clones = []; - for (let i = 0; i < 10; i++) { - const tx = await createClone(cloneFactory); - - const receipt = await tx.wait(); - const event = receipt.events!.find(event => event.event === 'CreateClone')!; - const cloneAddress = event.args![0]; - expect(isAddress(cloneAddress)).to.be.true; - clones.push(cloneAddress); - } - - for (let i = 0; i < clones.length; i++) { - const contractFactory = - await ethers.getContractFactory('ERC721CMRoyaltiesInitializable'); - const erc721Clone = contractFactory.attach(clones[i]); - - expect(await erc721Clone.totalSupply()).to.equal(0); - await erc721Clone.ownerMint(i + 1, owner.address); - expect(await erc721Clone.totalSupply()).to.equal(i + 1); - } - }); -}); - -function createClone(factory: ERC721CMRoyaltiesCloneFactory) { - return factory.create( - 'TestCollection', - 'TestSymbol', - '.json', - 1000, - 0, - '0x0000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000', - '0xef59F379B48f2E92aBD94ADcBf714D170967925D', - '0xef59F379B48f2E92aBD94ADcBf714D170967925D', - 100, - ); -} From 923f6136677670671afe1c6893e3e942178f5218 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 27 Sep 2024 14:00:47 -0400 Subject: [PATCH 029/145] rename Signed-off-by: Wolfy --- .../nft/erc721m/ERC721CMInitializable.sol | 2 +- ...ble_V2.sol => ERC721CMInitializableV2.sol} | 19 ++++--------------- ....sol => ERC721CMInitializableV2Test.t.sol} | 14 ++++++-------- 3 files changed, 11 insertions(+), 24 deletions(-) rename contracts/nft/erc721m/v2/{ERC721CMInitializable_V2.sol => ERC721CMInitializableV2.sol} (97%) rename test/erc721m/{ERC721CMInitializable_V2Test.t.sol => ERC721CMInitializableV2Test.t.sol} (93%) diff --git a/contracts/nft/erc721m/ERC721CMInitializable.sol b/contracts/nft/erc721m/ERC721CMInitializable.sol index f47dac34..196b3bc2 100644 --- a/contracts/nft/erc721m/ERC721CMInitializable.sol +++ b/contracts/nft/erc721m/ERC721CMInitializable.sol @@ -9,7 +9,7 @@ import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {OwnableInitializable} from "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; +import "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; import "../creator-token-standards/ERC721ACQueryableInitializable.sol"; import "./interfaces/IERC721MInitializable.sol"; import "../../utils/Constants.sol"; diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol similarity index 97% rename from contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol rename to contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol index 6b908388..40f95277 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol @@ -8,8 +8,7 @@ import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; - -import {Ownable} from "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC721ACQueryableInitializable, ERC721AUpgradeable, IERC721AUpgradeable} from "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; import {MINT_FEE_RECEIVER} from "../../../utils/Constants.sol"; @@ -18,11 +17,11 @@ import {UpdatableRoyaltiesInitializable, ERC2981} from "../../../royalties/Updat import {MintStageInfo} from "../../../common/Structs.sol"; import {IERC721MInitializable} from "../interfaces/IERC721MInitializable.sol"; /** - * @title ERC721CMInitializable_V2 + * @title ERC721CMInitializableV2 * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this * contract is for use with EIP-1167 Minimal Proxies (Clones). */ -contract ERC721CMInitializable_V2 is +contract ERC721CMInitializableV2 is IInitializableToken, IERC721MInitializable, ERC721ACQueryableInitializable, @@ -36,7 +35,7 @@ contract ERC721CMInitializable_V2 is bool private _mintable; // Specify how long a signature from cosigner is valid for, recommend 300 seconds. - uint64 private _timestampExpirySeconds; + uint64 private immutable _timestampExpirySeconds = 300; // The address of the cosigner server. address private _cosigner; @@ -97,7 +96,6 @@ contract ERC721CMInitializable_V2 is string calldata tokenURISuffix, uint256 maxMintableSupply, uint256 globalWalletLimit, - uint64 timestampExpirySeconds, address mintCurrency, address fundReceiver, address crossmintAddress, @@ -111,7 +109,6 @@ contract ERC721CMInitializable_V2 is _maxMintableSupply = maxMintableSupply; _globalWalletLimit = globalWalletLimit; _tokenURISuffix = tokenURISuffix; - _timestampExpirySeconds = timestampExpirySeconds; _mintCurrency = mintCurrency; _fundReceiver = fundReceiver; _crossmintAddress = crossmintAddress; @@ -211,14 +208,6 @@ contract ERC721CMInitializable_V2 is emit SetCosigner(cosigner); } - /** - * @dev Sets expiry in seconds. This timestamp specifies how long a signature from cosigner is valid for. - */ - function setTimestampExpirySeconds(uint64 expiry) external onlyOwner { - _timestampExpirySeconds = expiry; - emit SetTimestampExpirySeconds(expiry); - } - /** * @dev Sets crossmint address if using crossmint. This allows the specified address to call `crossmint`. */ diff --git a/test/erc721m/ERC721CMInitializable_V2Test.t.sol b/test/erc721m/ERC721CMInitializableV2Test.t.sol similarity index 93% rename from test/erc721m/ERC721CMInitializable_V2Test.t.sol rename to test/erc721m/ERC721CMInitializableV2Test.t.sol index e75112d3..e266410e 100644 --- a/test/erc721m/ERC721CMInitializable_V2Test.t.sol +++ b/test/erc721m/ERC721CMInitializableV2Test.t.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.20; import {IERC721A} from "erc721a/contracts/IERC721A.sol"; import {Test} from "forge-std/Test.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC721CMInitializable_V2} from "../../contracts/nft/erc721m/v2/ERC721CMInitializable_V2.sol"; +import {ERC721CMInitializableV2} from "../../contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol"; import {MintStageInfo} from "../../contracts/common/Structs.sol"; import {ErrorsAndEvents} from "../../contracts/common/ErrorsAndEvents.sol"; -contract ERC721CMInitializable_V2Test is Test { - ERC721CMInitializable_V2 public nft; +contract ERC721CMInitializableV2Test is Test { + ERC721CMInitializableV2 public nft; address public owner; address public minter; address public fundReceiver; @@ -17,7 +17,6 @@ contract ERC721CMInitializable_V2Test is Test { address public crossmintAddress; uint256 public constant INITIAL_SUPPLY = 1000; uint256 public constant GLOBAL_WALLET_LIMIT = 0; - uint64 public constant TIMESTAMP_EXPIRY_SECONDS = 60; function setUp() public { owner = address(this); @@ -30,13 +29,12 @@ contract ERC721CMInitializable_V2Test is Test { vm.deal(minter, 2 ether); vm.deal(crossmintAddress, 1 ether); - nft = new ERC721CMInitializable_V2(owner); + nft = new ERC721CMInitializableV2(owner); nft.initialize("Test", "TEST", payable(owner)); nft.setup( ".json", INITIAL_SUPPLY, GLOBAL_WALLET_LIMIT, - TIMESTAMP_EXPIRY_SECONDS, address(0), fundReceiver, crossmintAddress, @@ -96,8 +94,8 @@ contract ERC721CMInitializable_V2Test is Test { walletLimit: 4, merkleRoot: bytes32(uint256(2)), maxStageSupply: 10, - startTimeUnixSeconds: 61, - endTimeUnixSeconds: 62 + startTimeUnixSeconds: 301, + endTimeUnixSeconds: 602 }); nft.setStages(stages); From 27e709dd58dab311d993f97a30e06729408a8013 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 27 Sep 2024 15:14:20 -0400 Subject: [PATCH 030/145] add erc1155 initializable Signed-off-by: Wolfy --- contracts/nft/erc1155m/ERC1155M.sol | 2 +- .../nft/erc1155m/ERC1155MErrorsAndEvents.sol | 9 +- .../nft/erc1155m/ERC1155MInitializable.sol | 771 ++++++++++++++++++ .../interfaces/IERC721MInitializable.sol | 2 +- .../erc721m/v2/ERC721CMInitializableV2.sol | 7 +- .../erc721m/ERC721CMInitializableV2Test.t.sol | 2 +- 6 files changed, 783 insertions(+), 10 deletions(-) create mode 100644 contracts/nft/erc1155m/ERC1155MInitializable.sol diff --git a/contracts/nft/erc1155m/ERC1155M.sol b/contracts/nft/erc1155m/ERC1155M.sol index 7c7257d3..c73d2de8 100644 --- a/contracts/nft/erc1155m/ERC1155M.sol +++ b/contracts/nft/erc1155m/ERC1155M.sol @@ -82,7 +82,7 @@ contract ERC1155M is // Authorized minters mapping(address => bool) private _authorizedMinters; - + constructor( string memory collectionName, string memory collectionSymbol, diff --git a/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol b/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol index 0789a2da..12224eed 100644 --- a/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol +++ b/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol @@ -20,6 +20,11 @@ interface ERC1155MErrorsAndEvents is ErrorsAndEvents { event SetMaxMintableSupply(uint256 indexed tokenId, uint256 maxMintableSupply); event SetGlobalWalletLimit(uint256 indexed tokenId, uint256 globalWalletLimit); event SetTransferable(bool transferable); - event DefaultRoyaltySet(address receiver, uint96 feeNumerator); - event TokenRoyaltySet(uint256 indexed tokenId, address receiver, uint96 feeNumerator); + + event DefaultRoyaltySet(address indexed receiver, uint96 feeNumerator); + event TokenRoyaltySet( + uint256 indexed tokenId, + address indexed receiver, + uint96 feeNumerator + ); } \ No newline at end of file diff --git a/contracts/nft/erc1155m/ERC1155MInitializable.sol b/contracts/nft/erc1155m/ERC1155MInitializable.sol new file mode 100644 index 00000000..59165b89 --- /dev/null +++ b/contracts/nft/erc1155m/ERC1155MInitializable.sol @@ -0,0 +1,771 @@ +//SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {ERC1155SupplyUpgradeable, ERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155SupplyUpgradeable.sol"; +import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import {MintStageInfo1155} from "../../common/Structs.sol"; +import {IERC1155M} from "./interfaces/IERC1155M.sol"; +import {MINT_FEE_RECEIVER} from "../../utils/Constants.sol"; + + +/** + * @title ERC1155MInitializable + * + * @dev OpenZeppelin's ERC1155 subclass with MagicEden launchpad features including + * - multi token minting + * - multi minting stages with time-based auto stage switch + * - global and stage wallet-level minting limit + * - whitelist + * - variable wallet limit + */ +contract ERC1155MInitializable is + IERC1155M, + ERC1155SupplyUpgradeable, + OwnableUpgradeable, + ReentrancyGuard, + ERC2981 +{ + using ECDSA for bytes32; + using SafeERC20 for IERC20; + + // Mint stage information. See MintStageInfo for details. + MintStageInfo1155[] private _mintStages; + + string public name; + string public symbol; + bool private _transferable; // Whether the token can be transferred. + uint256[] private _maxMintableSupply; // The total mintable supply per token. + uint256[] private _globalWalletLimit; // Global wallet limit, across all stages, per token. + uint256 private _totalMintFee; + uint64 private immutable _timestampExpirySeconds = 300; + address private _cosigner; // The address of the cosigner server. + address private _mintCurrency; // If 0 address, use native currency. + uint256 private _numTokens; + address private _fundReceiver; + + mapping(uint256 => mapping(uint256 => mapping(address => uint32))) + private _stageMintedCountsPerTokenPerWallet; + mapping(uint256 => mapping(uint256 => uint256)) + private _stageMintedCountsPerToken; + mapping(address => bool) private _authorizedMinters; + + function initialize( + string calldata name_, + string calldata symbol_, + string calldata uri_, + address initialOwner + ) external initializer { + __ERC1155_init(uri_); + name = name_; + symbol = symbol_; + __Ownable_init(initialOwner); + } + + + function setup( + string memory collectionName, + string memory collectionSymbol, + uint256[] memory maxMintableSupply, + uint256[] memory globalWalletLimit, + address cosigner, + address mintCurrency, + address fundReceiver, + address royaltyReceiver, + uint96 royaltyFeeNumerator + ) external onlyOwner { + if (maxMintableSupply.length != globalWalletLimit.length) { + revert InvalidLimitArgsLength(); + } + + for (uint256 i = 0; i < globalWalletLimit.length; i++) { + if ( + maxMintableSupply[i] > 0 && + globalWalletLimit[i] > maxMintableSupply[i] + ) { + revert GlobalWalletLimitOverflow(); + } + } + + name = collectionName; + symbol = collectionSymbol; + _numTokens = globalWalletLimit.length; + _maxMintableSupply = maxMintableSupply; + _globalWalletLimit = globalWalletLimit; + _cosigner = cosigner; + _transferable = true; + + _mintCurrency = mintCurrency; + _fundReceiver = fundReceiver; + + setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); + } + + /** + * @dev Returns whether it has enough supply for the given qty. + */ + modifier hasSupply(uint256 tokenId, uint256 qty) { + if ( + _maxMintableSupply[tokenId] > 0 && + totalSupply(tokenId) + qty > _maxMintableSupply[tokenId] + ) revert NoSupplyLeft(); + _; + } + + /** + * @dev Returns whether the msg sender is authorized to mint. + */ + modifier onlyAuthorizedMinter() { + if (_authorizedMinters[_msgSender()] != true) revert NotAuthorized(); + _; + } + + /** + * @dev Add authorized minter. Can only be called by contract owner. + */ + function addAuthorizedMinter(address minter) external onlyOwner { + _authorizedMinters[minter] = true; + } + + /** + * @dev Remove authorized minter. Can only be called by contract owner. + */ + function removeAuthorizedMinter(address minter) external onlyOwner { + _authorizedMinters[minter] = false; + } + + /** + * @dev Returns cosign nonce. + */ + function getCosignNonce( + address minter, + uint256 tokenId + ) public view returns (uint256) { + return totalMintedByAddress(minter)[tokenId]; + } + + /** + * @dev Sets cosigner. + */ + function setCosigner(address cosigner) external onlyOwner { + _cosigner = cosigner; + emit SetCosigner(cosigner); + } + + /** + * @dev Sets stages in the format of an array of `MintStageInfo`. + * + * Following is an example of launch with two stages. The first stage is exclusive for whitelisted wallets + * specified by merkle root. + * [{ + * price: 10000000000000000000, + * maxStageSupply: 2000, + * walletLimit: 1, + * merkleRoot: 0x559fadeb887449800b7b320bf1e92d309f329b9641ac238bebdb74e15c0a5218, + * startTimeUnixSeconds: 1667768000, + * endTimeUnixSeconds: 1667771600, + * }, + * { + * price: 20000000000000000000, + * maxStageSupply: 3000, + * walletLimit: 2, + * merkleRoot: 0, + * startTimeUnixSeconds: 1667771600, + * endTimeUnixSeconds: 1667775200, + * } + * ] + */ + function setStages(MintStageInfo1155[] calldata newStages) external onlyOwner { + delete _mintStages; + + for (uint256 i = 0; i < newStages.length; i++) { + if (i >= 1) { + if ( + newStages[i].startTimeUnixSeconds < + newStages[i - 1].endTimeUnixSeconds + + _timestampExpirySeconds + ) { + revert InsufficientStageTimeGap(); + } + } + _assertValidStartAndEndTimestamp( + newStages[i].startTimeUnixSeconds, + newStages[i].endTimeUnixSeconds + ); + _assertValidStageArgsLength(newStages[i]); + + _mintStages.push( + MintStageInfo1155({ + price: newStages[i].price, + mintFee: newStages[i].mintFee, + walletLimit: newStages[i].walletLimit, + merkleRoot: newStages[i].merkleRoot, + maxStageSupply: newStages[i].maxStageSupply, + startTimeUnixSeconds: newStages[i].startTimeUnixSeconds, + endTimeUnixSeconds: newStages[i].endTimeUnixSeconds + }) + ); + emit UpdateStage( + i, + newStages[i].price, + newStages[i].mintFee, + newStages[i].walletLimit, + newStages[i].merkleRoot, + newStages[i].maxStageSupply, + newStages[i].startTimeUnixSeconds, + newStages[i].endTimeUnixSeconds + ); + } + } + + /** + * @dev Returns maximum mintable supply per token. + */ + function getMaxMintableSupply( + uint256 tokenId + ) external view override returns (uint256) { + return _maxMintableSupply[tokenId]; + } + + /** + * @dev Sets maximum mintable supply. New supply cannot be larger than the old or the supply alraedy minted. + */ + function setMaxMintableSupply( + uint256 tokenId, + uint256 maxMintableSupply + ) external virtual onlyOwner { + if (tokenId >= _numTokens) { + revert InvalidTokenId(); + } + if ( + _maxMintableSupply[tokenId] != 0 && + maxMintableSupply > _maxMintableSupply[tokenId] + ) { + revert CannotIncreaseMaxMintableSupply(); + } + if (maxMintableSupply < totalSupply(tokenId)) { + revert NewSupplyLessThanTotalSupply(); + } + _maxMintableSupply[tokenId] = maxMintableSupply; + emit SetMaxMintableSupply(tokenId, maxMintableSupply); + } + + /** + * @dev Returns global wallet limit. This is the max number of tokens can be minted by one wallet. + */ + function getGlobalWalletLimit( + uint256 tokenId + ) external view override returns (uint256) { + return _globalWalletLimit[tokenId]; + } + + /** + * @dev Sets global wallet limit. + */ + function setGlobalWalletLimit( + uint256 tokenId, + uint256 globalWalletLimit + ) external onlyOwner { + if (tokenId >= _numTokens) { + revert InvalidTokenId(); + } + if ( + _maxMintableSupply[tokenId] > 0 && + globalWalletLimit > _maxMintableSupply[tokenId] + ) { + revert GlobalWalletLimitOverflow(); + } + _globalWalletLimit[tokenId] = globalWalletLimit; + emit SetGlobalWalletLimit(tokenId, globalWalletLimit); + } + + /** + * @dev Returns number of minted tokens for a given address. + */ + function totalMintedByAddress( + address account + ) public view virtual override returns (uint256[] memory) { + uint256[] memory totalMinted = new uint256[](_numTokens); + uint256 numStages = _mintStages.length; + for (uint256 token = 0; token < _numTokens; token++) { + for (uint256 stage = 0; stage < numStages; stage++) { + totalMinted[token] += _stageMintedCountsPerTokenPerWallet[ + stage + ][token][account]; + } + } + return totalMinted; + } + + /** + * @dev Returns number of minted token for a given token and address. + */ + function _totalMintedByTokenByAddress( + address account, + uint256 tokenId + ) internal view virtual returns (uint256) { + uint256 totalMinted = 0; + uint256 numStages = _mintStages.length; + for (uint256 i = 0; i < numStages; i++) { + totalMinted += _stageMintedCountsPerTokenPerWallet[i][tokenId][ + account + ]; + } + return totalMinted; + } + + /** + * @dev Returns number of minted tokens for a given stage and address. + */ + function _totalMintedByStageByAddress( + uint256 stage, + address account + ) internal view virtual returns (uint256[] memory) { + uint256[] memory totalMinted = new uint256[](_numTokens); + for (uint256 token = 0; token < _numTokens; token++) { + totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][ + token + ][account]; + } + return totalMinted; + } + + /** + * @dev Returns number of stages. + */ + function getNumberStages() external view override returns (uint256) { + return _mintStages.length; + } + + /** + * @dev Returns info for one stage specified by stage index (starting from 0). + */ + function getStageInfo( + uint256 stage + ) + external + view + override + returns (MintStageInfo1155 memory, uint256[] memory, uint256[] memory) + { + if (stage >= _mintStages.length) { + revert InvalidStage(); + } + uint256[] memory walletMinted = totalMintedByAddress(msg.sender); + uint256[] memory stageMinted = _totalMintedByStageByAddress( + stage, + msg.sender + ); + return (_mintStages[stage], walletMinted, stageMinted); + } + + /** + * @dev Returns mint currency address. + */ + function getMintCurrency() external view returns (address) { + return _mintCurrency; + } + + /** + * @dev Mints token(s). + * + * tokenId - token id + * qty - number of tokens to mint + * proof - the merkle proof generated on client side. This applies if using whitelist + * timestamp - the current timestamp + * signature - the signature from cosigner if using cosigner + */ + function mint( + uint256 tokenId, + uint32 qty, + bytes32[] calldata proof, + uint64 timestamp, + bytes calldata signature + ) external payable virtual nonReentrant { + _mintInternal(msg.sender, tokenId, qty, 0, proof, timestamp, signature); + } + + /** + * @dev Mints token(s) with limit. + * + * tokenId - token id + * qty - number of tokens to mint + * limit - limit for the given minter + * proof - the merkle proof generated on client side. This applies if using whitelist + * timestamp - the current timestamp + * signature - the signature from cosigner if using cosigner + */ + function mintWithLimit( + uint256 tokenId, + uint32 qty, + uint32 limit, + bytes32[] calldata proof, + uint64 timestamp, + bytes calldata signature + ) external payable virtual nonReentrant { + _mintInternal( + msg.sender, + tokenId, + qty, + limit, + proof, + timestamp, + signature + ); + } + + /** + * @dev Authorized mints token(s) with limit + * + * to - the token recipient + * tokenId - token id + * qty - number of tokens to mint + * limit - limit for the given minter + * proof - the merkle proof generated on client side. This applies if using whitelist + */ + function authorizedMint( + address to, + uint256 tokenId, + uint32 qty, + uint32 limit, + bytes32[] calldata proof + ) external payable onlyAuthorizedMinter { + _mintInternal(to, tokenId, qty, limit, proof, 0, bytes("0")); + } + + /** + * @dev Implementation of minting. + */ + function _mintInternal( + address to, + uint256 tokenId, + uint32 qty, + uint32 limit, + bytes32[] calldata proof, + uint64 timestamp, + bytes memory signature + ) internal hasSupply(tokenId, qty) { + uint64 stageTimestamp = uint64(block.timestamp); + bool waiveMintFee = false; + + if (_cosigner != address(0)) { + waiveMintFee = assertValidCosign( + msg.sender, + tokenId, + qty, + timestamp, + signature + ); + _assertValidTimestamp(timestamp); + stageTimestamp = timestamp; + } + + uint256 activeStage = getActiveStageFromTimestamp(stageTimestamp); + + MintStageInfo1155 memory stage = _mintStages[activeStage]; + uint80 adjustedMintFee = waiveMintFee ? 0 : stage.mintFee[tokenId]; + + // Check value if minting with ETH + if ( + _mintCurrency == address(0) && + msg.value < (stage.price[tokenId] + adjustedMintFee) * qty + ) revert NotEnoughValue(); + + // Check stage supply if applicable + if (stage.maxStageSupply[tokenId] > 0) { + if ( + _stageMintedCountsPerToken[activeStage][tokenId] + qty > + stage.maxStageSupply[tokenId] + ) revert StageSupplyExceeded(); + } + + // Check global wallet limit if applicable + if (_globalWalletLimit[tokenId] > 0) { + if ( + _totalMintedByTokenByAddress(to, tokenId) + qty > + _globalWalletLimit[tokenId] + ) revert WalletGlobalLimitExceeded(); + } + + // Check wallet limit for stage if applicable, limit == 0 means no limit enforced + if (stage.walletLimit[tokenId] > 0) { + if ( + _stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] + + qty > + stage.walletLimit[tokenId] + ) revert WalletStageLimitExceeded(); + } + + // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required + if (stage.merkleRoot[tokenId] != 0) { + if ( + MerkleProof.processProof( + proof, + keccak256(abi.encodePacked(to, limit)) + ) != stage.merkleRoot[tokenId] + ) revert InvalidProof(); + + // Verify merkle proof mint limit + if ( + limit > 0 && + _stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] + + qty > + limit + ) { + revert WalletStageLimitExceeded(); + } + } + + if (_mintCurrency != address(0)) { + // ERC20 mint payment + IERC20(_mintCurrency).safeTransferFrom( + msg.sender, + address(this), + (stage.price[tokenId] + adjustedMintFee) * qty + ); + } + + _totalMintFee += adjustedMintFee * qty; + _stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] += qty; + _stageMintedCountsPerToken[activeStage][tokenId] += qty; + _mint(to, tokenId, qty, ""); + } + + /** + * @dev Mints token(s) by owner. + * + * NOTE: This function bypasses validations thus only available for owner. + * This is typically used for owner to pre-mint or mint the remaining of the supply. + */ + function ownerMint( + address to, + uint256 tokenId, + uint32 qty + ) external onlyOwner hasSupply(tokenId, qty) { + _mint(to, tokenId, qty, ""); + } + + /** + * @dev Withdraws funds by owner. + */ + function withdraw() external onlyOwner { + (bool success, ) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); + if (!success) revert TransferFailed(); + _totalMintFee = 0; + + uint256 remainingValue = address(this).balance; + (success, ) = _fundReceiver.call{value: remainingValue}(""); + if (!success) revert WithdrawFailed(); + + emit Withdraw(_totalMintFee + remainingValue); + } + + /** + * @dev Withdraws ERC-20 funds by owner. + */ + function withdrawERC20() external onlyOwner { + if (_mintCurrency == address(0)) revert WrongMintCurrency(); + + IERC20(_mintCurrency).safeTransfer(MINT_FEE_RECEIVER, _totalMintFee); + _totalMintFee = 0; + + uint256 remaining = IERC20(_mintCurrency).balanceOf(address(this)); + IERC20(_mintCurrency).safeTransfer(_fundReceiver, remaining); + + emit WithdrawERC20(_mintCurrency, _totalMintFee + remaining); + } + + /** + * @dev Sets a new URI for all token types. The URI relies on token type ID + * substitution mechanism. + */ + function setURI(string calldata newURI) external onlyOwner { + _setURI(newURI); + } + + /** + * @dev Sets transferable of the tokens. + */ + function setTransferable(bool transferable) external onlyOwner { + _transferable = transferable; + emit SetTransferable(transferable); + } + + /** + * @dev Returns the current active stage based on timestamp. + */ + function getActiveStageFromTimestamp( + uint64 timestamp + ) public view returns (uint256) { + for (uint256 i = 0; i < _mintStages.length; i++) { + if ( + timestamp >= _mintStages[i].startTimeUnixSeconds && + timestamp < _mintStages[i].endTimeUnixSeconds + ) { + return i; + } + } + revert InvalidStage(); + } + + /** + * @dev Returns data hash for the given minter, qty, waiveMintFee and timestamp. + */ + function getCosignDigest( + address minter, + uint256 tokenId, + uint32 qty, + bool waiveMintFee, + uint64 timestamp + ) public view returns (bytes32) { + if (_cosigner == address(0)) revert CosignerNotSet(); + + return + MessageHashUtils.toEthSignedMessageHash( + keccak256( + abi.encodePacked( + address(this), + minter, + qty, + waiveMintFee, + _cosigner, + timestamp, + block.chainid, + getCosignNonce(minter, tokenId) + ) + ) + ); + } + + /** + * @dev Validates the the given signature. Returns whether mint fee is waived. + */ + function assertValidCosign( + address minter, + uint256 tokenId, + uint32 qty, + uint64 timestamp, + bytes memory signature + ) public view returns (bool) { + if ( + SignatureChecker.isValidSignatureNow( + _cosigner, + getCosignDigest( + minter, + tokenId, + qty, + /* waiveMintFee= */ true, + timestamp + ), + signature + ) + ) { + return true; + } + + if ( + SignatureChecker.isValidSignatureNow( + _cosigner, + getCosignDigest( + minter, + tokenId, + qty, + /* waiveMintFee= */ false, + timestamp + ), + signature + ) + ) { + return false; + } + + revert InvalidCosignSignature(); + } + + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC2981, ERC1155Upgradeable) returns (bool) { + return super.supportsInterface(interfaceId) || + ERC2981.supportsInterface(interfaceId) || + ERC1155Upgradeable.supportsInterface(interfaceId); + } + + /** + * @dev The hook of token transfer to validate the transfer. + */ + function _update( + address from, + address to, + uint256[] memory ids, + uint256[] memory values + ) internal virtual override { + super._update(from, to, ids, values); + + bool fromZeroAddress = from == address(0); + bool toZeroAddress = to == address(0); + + if (!fromZeroAddress && !toZeroAddress && !_transferable) { + revert NotTransferable(); + } + } + + /** + * @dev Validates the start timestamp is before end timestamp. Used when updating stages. + */ + function _assertValidStartAndEndTimestamp( + uint64 start, + uint64 end + ) internal pure { + if (start >= end) revert InvalidStartAndEndTimestamp(); + } + + /** + * @dev Validates the timestamp is not expired. + */ + function _assertValidTimestamp(uint64 timestamp) internal view { + if (timestamp < block.timestamp - _timestampExpirySeconds) + revert TimestampExpired(); + } + + function _assertValidStageArgsLength( + MintStageInfo1155 calldata stageInfo + ) internal view { + if ( + stageInfo.price.length != _numTokens || + stageInfo.mintFee.length != _numTokens || + stageInfo.walletLimit.length != _numTokens || + stageInfo.merkleRoot.length != _numTokens || + stageInfo.maxStageSupply.length != _numTokens + ) { + revert InvalidStageArgsLength(); + } + } + + function setDefaultRoyalty( + address receiver, + uint96 feeNumerator + ) public onlyOwner { + super._setDefaultRoyalty(receiver, feeNumerator); + emit DefaultRoyaltySet(receiver, feeNumerator); + } + + function setTokenRoyalty( + uint256 tokenId, + address receiver, + uint96 feeNumerator + ) public onlyOwner { + super._setTokenRoyalty(tokenId, receiver, feeNumerator); + emit TokenRoyaltySet(tokenId, receiver, feeNumerator); + } +} diff --git a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol index 4e93c8a3..70578e69 100644 --- a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol +++ b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol @@ -54,4 +54,4 @@ interface IERC721MInitializable is IERC721AQueryableUpgradeable, ERC721MErrorsAn uint64 timestamp, bytes calldata signature ) external payable; -} +} \ No newline at end of file diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol index 40f95277..a2c78b43 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol @@ -12,7 +12,6 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC721ACQueryableInitializable, ERC721AUpgradeable, IERC721AUpgradeable} from "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; import {MINT_FEE_RECEIVER} from "../../../utils/Constants.sol"; -import {IInitializableToken} from "../../../common/interfaces/IInitializableToken.sol"; import {UpdatableRoyaltiesInitializable, ERC2981} from "../../../royalties/UpdatableRoyaltiesInitializable.sol"; import {MintStageInfo} from "../../../common/Structs.sol"; import {IERC721MInitializable} from "../interfaces/IERC721MInitializable.sol"; @@ -22,7 +21,6 @@ import {IERC721MInitializable} from "../interfaces/IERC721MInitializable.sol"; * contract is for use with EIP-1167 Minimal Proxies (Clones). */ contract ERC721CMInitializableV2 is - IInitializableToken, IERC721MInitializable, ERC721ACQueryableInitializable, UpdatableRoyaltiesInitializable, @@ -86,9 +84,8 @@ contract ERC721CMInitializableV2 is function initialize( string calldata name, - string calldata symbol, - address payable initialOwner - ) external override initializerERC721A initializer { + string calldata symbol + ) external initializerERC721A initializer { __ERC721ACQueryableInitializable_init(name, symbol); } diff --git a/test/erc721m/ERC721CMInitializableV2Test.t.sol b/test/erc721m/ERC721CMInitializableV2Test.t.sol index e266410e..79358889 100644 --- a/test/erc721m/ERC721CMInitializableV2Test.t.sol +++ b/test/erc721m/ERC721CMInitializableV2Test.t.sol @@ -30,7 +30,7 @@ contract ERC721CMInitializableV2Test is Test { vm.deal(crossmintAddress, 1 ether); nft = new ERC721CMInitializableV2(owner); - nft.initialize("Test", "TEST", payable(owner)); + nft.initialize("Test", "TEST"); nft.setup( ".json", INITIAL_SUPPLY, From ca21094fbddb3fa4faea90388c0c4dc8b0dbaa72 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 27 Sep 2024 16:06:02 -0400 Subject: [PATCH 031/145] update solidity versions Signed-off-by: Wolfy --- contracts/common/ErrorsAndEvents.sol | 2 +- contracts/common/Structs.sol | 2 +- contracts/common/interfaces/IInitializableToken.sol | 2 +- contracts/factory/MagicDropCloneFactory.sol | 2 +- .../factory/interfaces/IMagicDropCloneFactory.sol | 2 +- contracts/mocks/ERC1155MTestReentrantExploit.sol | 2 +- contracts/mocks/MockERC20.sol | 2 +- contracts/mocks/MockERC721A.sol | 2 +- contracts/mocks/TestReentrantExploit.sol | 2 +- contracts/mocks/TestStaking.sol | 2 +- contracts/nft/MagicDropERC1155Initializable.sol | 2 +- contracts/nft/MagicDropERC721Initializable.sol | 2 +- .../nft/creator-token-standards/ERC721ACQueryable.sol | 2 +- .../ERC721ACQueryableInitializable.sol | 2 +- contracts/nft/erc1155m/ERC1155MInitializable.sol | 10 +++------- contracts/nft/erc721m/ERC721CM.sol | 2 +- contracts/nft/erc721m/ERC721CMInitializable.sol | 2 +- contracts/nft/erc721m/ERC721M.sol | 2 +- contracts/nft/erc721m/extensions/BucketAuction.sol | 2 +- .../nft/erc721m/extensions/ERC721CMBasicRoyalties.sol | 2 +- contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol | 2 +- .../nft/erc721m/extensions/ERC721MOperatorFilterer.sol | 2 +- contracts/nft/erc721m/interfaces/IBucketAuction.sol | 2 +- contracts/nft/erc721m/interfaces/IERC721M.sol | 2 +- .../nft/erc721m/interfaces/IERC721MInitializable.sol | 2 +- contracts/operator-filter/OwnedRegistrant.sol | 2 +- contracts/registry/MagicDropTokenImplRegistry.sol | 2 +- .../interfaces/IMagicDropTokenImplRegistry.sol | 2 +- contracts/royalties/UpdatableRoyalties.sol | 2 +- .../royalties/UpdatableRoyaltiesInitializable.sol | 2 +- contracts/utils/Constants.sol | 2 +- contracts/utils/ERC721BatchTransfer.sol | 2 +- test/factory/MagicDropCloneFactoryTest.t.sol | 2 +- test/factory/MagicDropTokenImplRegistryTest.t.sol | 2 +- 34 files changed, 36 insertions(+), 40 deletions(-) diff --git a/contracts/common/ErrorsAndEvents.sol b/contracts/common/ErrorsAndEvents.sol index 61bd9e2a..c864f987 100644 --- a/contracts/common/ErrorsAndEvents.sol +++ b/contracts/common/ErrorsAndEvents.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; interface ErrorsAndEvents { error CannotIncreaseMaxMintableSupply(); diff --git a/contracts/common/Structs.sol b/contracts/common/Structs.sol index 90533fa7..705fffba 100644 --- a/contracts/common/Structs.sol +++ b/contracts/common/Structs.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; enum TokenStandard { ERC721, diff --git a/contracts/common/interfaces/IInitializableToken.sol b/contracts/common/interfaces/IInitializableToken.sol index 145bb0d7..bc8855c7 100644 --- a/contracts/common/interfaces/IInitializableToken.sol +++ b/contracts/common/interfaces/IInitializableToken.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; interface IInitializableToken { function initialize( diff --git a/contracts/factory/MagicDropCloneFactory.sol b/contracts/factory/MagicDropCloneFactory.sol index 5943f5ac..d2acac90 100644 --- a/contracts/factory/MagicDropCloneFactory.sol +++ b/contracts/factory/MagicDropCloneFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; diff --git a/contracts/factory/interfaces/IMagicDropCloneFactory.sol b/contracts/factory/interfaces/IMagicDropCloneFactory.sol index 3ad69d55..742702b3 100644 --- a/contracts/factory/interfaces/IMagicDropCloneFactory.sol +++ b/contracts/factory/interfaces/IMagicDropCloneFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {TokenStandard} from "../../common/Structs.sol"; diff --git a/contracts/mocks/ERC1155MTestReentrantExploit.sol b/contracts/mocks/ERC1155MTestReentrantExploit.sol index 510be5d3..7eca2255 100644 --- a/contracts/mocks/ERC1155MTestReentrantExploit.sol +++ b/contracts/mocks/ERC1155MTestReentrantExploit.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "../nft/erc1155m/ERC1155M.sol"; import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; diff --git a/contracts/mocks/MockERC20.sol b/contracts/mocks/MockERC20.sol index 7b494c50..61ad41fa 100644 --- a/contracts/mocks/MockERC20.sol +++ b/contracts/mocks/MockERC20.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/MockERC721A.sol b/contracts/mocks/MockERC721A.sol index 13c7cc82..541c690f 100644 --- a/contracts/mocks/MockERC721A.sol +++ b/contracts/mocks/MockERC721A.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import {ERC721A} from "erc721a/contracts/ERC721A.sol"; diff --git a/contracts/mocks/TestReentrantExploit.sol b/contracts/mocks/TestReentrantExploit.sol index e3188eda..080dca17 100644 --- a/contracts/mocks/TestReentrantExploit.sol +++ b/contracts/mocks/TestReentrantExploit.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "../nft/erc721m/ERC721M.sol"; diff --git a/contracts/mocks/TestStaking.sol b/contracts/mocks/TestStaking.sol index 6f58f809..46f1337e 100644 --- a/contracts/mocks/TestStaking.sol +++ b/contracts/mocks/TestStaking.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "erc721a/contracts/ERC721A.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/contracts/nft/MagicDropERC1155Initializable.sol b/contracts/nft/MagicDropERC1155Initializable.sol index a43253d7..26c93997 100644 --- a/contracts/nft/MagicDropERC1155Initializable.sol +++ b/contracts/nft/MagicDropERC1155Initializable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; diff --git a/contracts/nft/MagicDropERC721Initializable.sol b/contracts/nft/MagicDropERC721Initializable.sol index 1f9062c2..d71385c3 100644 --- a/contracts/nft/MagicDropERC721Initializable.sol +++ b/contracts/nft/MagicDropERC721Initializable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; diff --git a/contracts/nft/creator-token-standards/ERC721ACQueryable.sol b/contracts/nft/creator-token-standards/ERC721ACQueryable.sol index 17088d3f..c7fd0653 100644 --- a/contracts/nft/creator-token-standards/ERC721ACQueryable.sol +++ b/contracts/nft/creator-token-standards/ERC721ACQueryable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "@limitbreak/creator-token-standards/src/utils/CreatorTokenBase.sol"; import "erc721a/contracts/extensions/ERC721AQueryable.sol"; diff --git a/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol b/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol index ba650fdc..e133d497 100644 --- a/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol +++ b/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "@limitbreak/creator-token-standards/src/utils/CreatorTokenBase.sol"; import "erc721a-upgradeable/contracts/extensions/ERC721AQueryableUpgradeable.sol"; diff --git a/contracts/nft/erc1155m/ERC1155MInitializable.sol b/contracts/nft/erc1155m/ERC1155MInitializable.sol index 59165b89..4d13bcbc 100644 --- a/contracts/nft/erc1155m/ERC1155MInitializable.sol +++ b/contracts/nft/erc1155m/ERC1155MInitializable.sol @@ -64,19 +64,17 @@ contract ERC1155MInitializable is function initialize( string calldata name_, string calldata symbol_, - string calldata uri_, address initialOwner ) external initializer { - __ERC1155_init(uri_); name = name_; symbol = symbol_; + __ERC1155_init(""); __Ownable_init(initialOwner); } function setup( - string memory collectionName, - string memory collectionSymbol, + string calldata uri_, uint256[] memory maxMintableSupply, uint256[] memory globalWalletLimit, address cosigner, @@ -98,17 +96,15 @@ contract ERC1155MInitializable is } } - name = collectionName; - symbol = collectionSymbol; _numTokens = globalWalletLimit.length; _maxMintableSupply = maxMintableSupply; _globalWalletLimit = globalWalletLimit; _cosigner = cosigner; _transferable = true; - _mintCurrency = mintCurrency; _fundReceiver = fundReceiver; + _setURI(uri_); setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); } diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index 8324c702..6476d48d 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/common/ERC2981.sol"; diff --git a/contracts/nft/erc721m/ERC721CMInitializable.sol b/contracts/nft/erc721m/ERC721CMInitializable.sol index 196b3bc2..014dee55 100644 --- a/contracts/nft/erc721m/ERC721CMInitializable.sol +++ b/contracts/nft/erc721m/ERC721CMInitializable.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/common/ERC2981.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; diff --git a/contracts/nft/erc721m/ERC721M.sol b/contracts/nft/erc721m/ERC721M.sol index c6198e77..1f65086f 100644 --- a/contracts/nft/erc721m/ERC721M.sol +++ b/contracts/nft/erc721m/ERC721M.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; diff --git a/contracts/nft/erc721m/extensions/BucketAuction.sol b/contracts/nft/erc721m/extensions/BucketAuction.sol index 49c583eb..e8128f84 100644 --- a/contracts/nft/erc721m/extensions/BucketAuction.sol +++ b/contracts/nft/erc721m/extensions/BucketAuction.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; diff --git a/contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol b/contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol index 4a3fbf1a..be3ec60b 100644 --- a/contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol +++ b/contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "@limitbreak/creator-token-standards/src/programmable-royalties/BasicRoyalties.sol"; import "../ERC721CM.sol"; diff --git a/contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol b/contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol index 202d9d92..38396294 100644 --- a/contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol +++ b/contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import {ERC2981, UpdatableRoyalties} from "../../../royalties/UpdatableRoyalties.sol"; import {ERC721CM, ERC721ACQueryable, IERC721A} from "../ERC721CM.sol"; diff --git a/contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol b/contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol index c66a4c16..eca6b431 100644 --- a/contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol +++ b/contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import {ERC721A, ERC721M, IERC721A, Ownable} from "../ERC721M.sol"; import {UpdatableOperatorFilterer} from "operator-filter-registry/src/UpdatableOperatorFilterer.sol"; diff --git a/contracts/nft/erc721m/interfaces/IBucketAuction.sol b/contracts/nft/erc721m/interfaces/IBucketAuction.sol index 94b1bb1e..332641cd 100644 --- a/contracts/nft/erc721m/interfaces/IBucketAuction.sol +++ b/contracts/nft/erc721m/interfaces/IBucketAuction.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; interface IBucketAuction { error AlreadySentTokensToUser(); diff --git a/contracts/nft/erc721m/interfaces/IERC721M.sol b/contracts/nft/erc721m/interfaces/IERC721M.sol index e6bdd01c..22490d32 100644 --- a/contracts/nft/erc721m/interfaces/IERC721M.sol +++ b/contracts/nft/erc721m/interfaces/IERC721M.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import {MintStageInfo} from "../../../common/Structs.sol"; import "erc721a/contracts/extensions/IERC721AQueryable.sol"; diff --git a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol index 70578e69..15583364 100644 --- a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol +++ b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import "erc721a-upgradeable/contracts/extensions/IERC721AQueryableUpgradeable.sol"; import "../../../common/Structs.sol"; diff --git a/contracts/operator-filter/OwnedRegistrant.sol b/contracts/operator-filter/OwnedRegistrant.sol index 6c45c3e3..c7020bec 100644 --- a/contracts/operator-filter/OwnedRegistrant.sol +++ b/contracts/operator-filter/OwnedRegistrant.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import {IOperatorFilterRegistry} from "operator-filter-registry/src/IOperatorFilterRegistry.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; diff --git a/contracts/registry/MagicDropTokenImplRegistry.sol b/contracts/registry/MagicDropTokenImplRegistry.sol index 941b6cad..b4d49657 100644 --- a/contracts/registry/MagicDropTokenImplRegistry.sol +++ b/contracts/registry/MagicDropTokenImplRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; diff --git a/contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol b/contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol index 71d4149f..5ab1d141 100644 --- a/contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol +++ b/contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {TokenStandard} from "../../common/Structs.sol"; diff --git a/contracts/royalties/UpdatableRoyalties.sol b/contracts/royalties/UpdatableRoyalties.sol index 2bc6f338..48d395a5 100644 --- a/contracts/royalties/UpdatableRoyalties.sol +++ b/contracts/royalties/UpdatableRoyalties.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; diff --git a/contracts/royalties/UpdatableRoyaltiesInitializable.sol b/contracts/royalties/UpdatableRoyaltiesInitializable.sol index c1115bcf..90f679ec 100644 --- a/contracts/royalties/UpdatableRoyaltiesInitializable.sol +++ b/contracts/royalties/UpdatableRoyaltiesInitializable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import {OwnableInitializable} from "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; diff --git a/contracts/utils/Constants.sol b/contracts/utils/Constants.sol index c8f87646..faae6db9 100644 --- a/contracts/utils/Constants.sol +++ b/contracts/utils/Constants.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; address constant CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS = 0x000000000000AAeB6D7670E522A718067333cd4E; address constant ME_SUBSCRIPTION = 0x0403c10721Ff2936EfF684Bbb57CD792Fd4b1B6c; diff --git a/contracts/utils/ERC721BatchTransfer.sol b/contracts/utils/ERC721BatchTransfer.sol index 9e0de923..5ba28651 100644 --- a/contracts/utils/ERC721BatchTransfer.sol +++ b/contracts/utils/ERC721BatchTransfer.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.20; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; diff --git a/test/factory/MagicDropCloneFactoryTest.t.sol b/test/factory/MagicDropCloneFactoryTest.t.sol index 94acee91..b85088a7 100644 --- a/test/factory/MagicDropCloneFactoryTest.t.sol +++ b/test/factory/MagicDropCloneFactoryTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {MagicDropCloneFactory} from "../../contracts/factory/MagicDropCloneFactory.sol"; diff --git a/test/factory/MagicDropTokenImplRegistryTest.t.sol b/test/factory/MagicDropTokenImplRegistryTest.t.sol index ea0473bb..fcd6f434 100644 --- a/test/factory/MagicDropTokenImplRegistryTest.t.sol +++ b/test/factory/MagicDropTokenImplRegistryTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {MagicDropTokenImplRegistry} from "../../contracts/registry/MagicDropTokenImplRegistry.sol"; From 41f42086c3193e7f947c6378bc0564384b1b39dc Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 27 Sep 2024 16:12:33 -0400 Subject: [PATCH 032/145] cleanup Signed-off-by: Wolfy --- contracts/nft/erc1155m/ERC1155M.sol | 2 ++ contracts/nft/erc1155m/ERC1155MInitializable.sol | 2 ++ contracts/nft/erc721m/ERC721CM.sol | 4 ---- contracts/nft/erc721m/ERC721CMInitializable.sol | 4 +--- contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol | 4 +--- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/contracts/nft/erc1155m/ERC1155M.sol b/contracts/nft/erc1155m/ERC1155M.sol index c73d2de8..454ac4e0 100644 --- a/contracts/nft/erc1155m/ERC1155M.sol +++ b/contracts/nft/erc1155m/ERC1155M.sol @@ -80,6 +80,8 @@ contract ERC1155M is // Fund receiver address public immutable FUND_RECEIVER; + uint256 public constant VERSION = 1; + // Authorized minters mapping(address => bool) private _authorizedMinters; diff --git a/contracts/nft/erc1155m/ERC1155MInitializable.sol b/contracts/nft/erc1155m/ERC1155MInitializable.sol index 4d13bcbc..6e6d3644 100644 --- a/contracts/nft/erc1155m/ERC1155MInitializable.sol +++ b/contracts/nft/erc1155m/ERC1155MInitializable.sol @@ -40,6 +40,8 @@ contract ERC1155MInitializable is using ECDSA for bytes32; using SafeERC20 for IERC20; + uint256 public constant VERSION = 1; + // Mint stage information. See MintStageInfo for details. MintStageInfo1155[] private _mintStages; diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index 6476d48d..3ce9b3c2 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -725,8 +725,4 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard { functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); isViewFunction = true; } - - function _tokenType() internal pure override returns(uint16) { - return uint16(721); - } } diff --git a/contracts/nft/erc721m/ERC721CMInitializable.sol b/contracts/nft/erc721m/ERC721CMInitializable.sol index 014dee55..f4cc1292 100644 --- a/contracts/nft/erc721m/ERC721CMInitializable.sol +++ b/contracts/nft/erc721m/ERC721CMInitializable.sol @@ -583,7 +583,5 @@ abstract contract ERC721CMInitializable is isViewFunction = true; } - function _tokenType() internal pure override returns(uint16) { - return uint16(721); - } + } diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol index a2c78b43..06636701 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol @@ -712,9 +712,7 @@ contract ERC721CMInitializableV2 is isViewFunction = true; } - function _tokenType() internal pure override returns(uint16) { - return uint16(721); - } + function supportsInterface(bytes4 interfaceId) public view override(ERC2981, IERC721AUpgradeable, ERC721ACQueryableInitializable) returns (bool) { return super.supportsInterface(interfaceId) || From abd9123b4e2eecdbe993591d360af880cbbcbd13 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 27 Sep 2024 16:43:02 -0400 Subject: [PATCH 033/145] cleanup Signed-off-by: Wolfy --- .gitignore | 6 +- README.md | 6 ++ .../mocks/ERC1155MTestReentrantExploit.sol | 10 +-- .../ERC721ACQueryable.sol | 4 ++ .../ERC721ACQueryableInitializable.sol | 4 ++ contracts/nft/erc1155m/ERC1155M.sol | 64 +++++++++---------- contracts/operator-filter/OwnedRegistrant.sol | 21 ------ test/generate-coverage-report.sh | 7 ++ 8 files changed, 62 insertions(+), 60 deletions(-) delete mode 100644 contracts/operator-filter/OwnedRegistrant.sol create mode 100755 test/generate-coverage-report.sh diff --git a/.gitignore b/.gitignore index 402aa782..27c093a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ node_modules .env -converage/ coverage.json typechain typechain-types @@ -10,4 +9,7 @@ artifacts .vscode dist .DS_Store -out/ \ No newline at end of file +out/ +lcov.info +lcov.info.pruned +coverage/ \ No newline at end of file diff --git a/README.md b/README.md index 62b7927c..2fcc9823 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,12 @@ forge build forge test ``` +### Generate Coverage Report +This project includes a script to generate and view a test coverage report. The script is located at `test/generate-coverage-report.sh`. +```bash +./test/generate-coverage-report.sh +``` + We are targeting 100% lines coverage. ![](https://bafkreic3dyzp5i2fi7co2fekkbgmyxgv342irjy5zfiuhvjqic6fuu53ju.ipfs.nftstorage.link/) diff --git a/contracts/mocks/ERC1155MTestReentrantExploit.sol b/contracts/mocks/ERC1155MTestReentrantExploit.sol index 7eca2255..2f0129cc 100644 --- a/contracts/mocks/ERC1155MTestReentrantExploit.sol +++ b/contracts/mocks/ERC1155MTestReentrantExploit.sol @@ -26,11 +26,11 @@ contract ERC1155MTestReentrantExploit { } function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data + address, + address, + uint256, + uint256, + bytes calldata ) public payable returns (bytes4) { bytes32[] memory proof; ERC1155M(_targetContract).mint{value: msg.value}( diff --git a/contracts/nft/creator-token-standards/ERC721ACQueryable.sol b/contracts/nft/creator-token-standards/ERC721ACQueryable.sol index c7fd0653..6110c284 100644 --- a/contracts/nft/creator-token-standards/ERC721ACQueryable.sol +++ b/contracts/nft/creator-token-standards/ERC721ACQueryable.sol @@ -60,4 +60,8 @@ abstract contract ERC721ACQueryable is ERC721AQueryable, CreatorTokenBase { { return _msgSender(); } + + function _tokenType() internal pure override returns(uint16) { + return uint16(721); + } } diff --git a/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol b/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol index e133d497..77560b25 100644 --- a/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol +++ b/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol @@ -76,4 +76,8 @@ abstract contract ERC721ACQueryableInitializable is { return _msgSender(); } + + function _tokenType() internal pure override returns(uint16) { + return uint16(721); + } } diff --git a/contracts/nft/erc1155m/ERC1155M.sol b/contracts/nft/erc1155m/ERC1155M.sol index 454ac4e0..4d979180 100644 --- a/contracts/nft/erc1155m/ERC1155M.sol +++ b/contracts/nft/erc1155m/ERC1155M.sol @@ -72,13 +72,13 @@ contract ERC1155M is address private _cosigner; // Address of ERC-20 token used to pay for minting. If 0 address, use native currency. - address private immutable MINT_CURRENCY; + address private _mintCurrency; // Number of tokens. - uint256 private immutable NUM_TOKENS; + uint256 private _numTokens; // Fund receiver - address public immutable FUND_RECEIVER; + address private _fundReceiver; uint256 public constant VERSION = 1; @@ -113,15 +113,15 @@ contract ERC1155M is name = collectionName; symbol = collectionSymbol; - NUM_TOKENS = globalWalletLimit.length; + _numTokens = globalWalletLimit.length; _maxMintableSupply = maxMintableSupply; _globalWalletLimit = globalWalletLimit; _cosigner = cosigner; _timestampExpirySeconds = timestampExpirySeconds; _transferable = true; - MINT_CURRENCY = mintCurrency; - FUND_RECEIVER = fundReceiver; + _mintCurrency = mintCurrency; + _fundReceiver = fundReceiver; _setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); } @@ -259,7 +259,7 @@ contract ERC1155M is uint256 tokenId, uint256 maxMintableSupply ) external virtual onlyOwner { - if (tokenId >= NUM_TOKENS) { + if (tokenId >= _numTokens) { revert InvalidTokenId(); } if ( @@ -291,7 +291,7 @@ contract ERC1155M is uint256 tokenId, uint256 globalWalletLimit ) external onlyOwner { - if (tokenId >= NUM_TOKENS) { + if (tokenId >= _numTokens) { revert InvalidTokenId(); } if ( @@ -310,9 +310,9 @@ contract ERC1155M is function totalMintedByAddress( address account ) public view virtual override returns (uint256[] memory) { - uint256[] memory totalMinted = new uint256[](NUM_TOKENS); + uint256[] memory totalMinted = new uint256[](_numTokens); uint256 numStages = _mintStages.length; - for (uint256 token = 0; token < NUM_TOKENS; token++) { + for (uint256 token = 0; token < _numTokens; token++) { for (uint256 stage = 0; stage < numStages; stage++) { totalMinted[token] += _stageMintedCountsPerTokenPerWallet[ stage @@ -325,7 +325,7 @@ contract ERC1155M is /** * @dev Returns number of minted token for a given token and address. */ - function totalMintedByTokenByAddress( + function _totalMintedByTokenByAddress( address account, uint256 tokenId ) internal view virtual returns (uint256) { @@ -342,12 +342,12 @@ contract ERC1155M is /** * @dev Returns number of minted tokens for a given stage and address. */ - function totalMintedByStageByAddress( + function _totalMintedByStageByAddress( uint256 stage, address account ) internal view virtual returns (uint256[] memory) { - uint256[] memory totalMinted = new uint256[](NUM_TOKENS); - for (uint256 token = 0; token < NUM_TOKENS; token++) { + uint256[] memory totalMinted = new uint256[](_numTokens); + for (uint256 token = 0; token < _numTokens; token++) { totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][ token ][account]; @@ -377,7 +377,7 @@ contract ERC1155M is revert InvalidStage(); } uint256[] memory walletMinted = totalMintedByAddress(msg.sender); - uint256[] memory stageMinted = totalMintedByStageByAddress( + uint256[] memory stageMinted = _totalMintedByStageByAddress( stage, msg.sender ); @@ -388,7 +388,7 @@ contract ERC1155M is * @dev Returns mint currency address. */ function getMintCurrency() external view returns (address) { - return MINT_CURRENCY; + return _mintCurrency; } /** @@ -492,7 +492,7 @@ contract ERC1155M is // Check value if minting with ETH if ( - MINT_CURRENCY == address(0) && + _mintCurrency == address(0) && msg.value < (stage.price[tokenId] + adjustedMintFee) * qty ) revert NotEnoughValue(); @@ -507,7 +507,7 @@ contract ERC1155M is // Check global wallet limit if applicable if (_globalWalletLimit[tokenId] > 0) { if ( - totalMintedByTokenByAddress(to, tokenId) + qty > + _totalMintedByTokenByAddress(to, tokenId) + qty > _globalWalletLimit[tokenId] ) revert WalletGlobalLimitExceeded(); } @@ -541,9 +541,9 @@ contract ERC1155M is } } - if (MINT_CURRENCY != address(0)) { + if (_mintCurrency != address(0)) { // ERC20 mint payment - IERC20(MINT_CURRENCY).safeTransferFrom( + IERC20(_mintCurrency).safeTransferFrom( msg.sender, address(this), (stage.price[tokenId] + adjustedMintFee) * qty @@ -579,7 +579,7 @@ contract ERC1155M is _totalMintFee = 0; uint256 remainingValue = address(this).balance; - (success, ) = FUND_RECEIVER.call{value: remainingValue}(""); + (success, ) = _fundReceiver.call{value: remainingValue}(""); if (!success) revert WithdrawFailed(); emit Withdraw(_totalMintFee + remainingValue); @@ -589,15 +589,15 @@ contract ERC1155M is * @dev Withdraws ERC-20 funds by owner. */ function withdrawERC20() external onlyOwner { - if (MINT_CURRENCY == address(0)) revert WrongMintCurrency(); + if (_mintCurrency == address(0)) revert WrongMintCurrency(); - IERC20(MINT_CURRENCY).safeTransfer(MINT_FEE_RECEIVER, _totalMintFee); + IERC20(_mintCurrency).safeTransfer(MINT_FEE_RECEIVER, _totalMintFee); _totalMintFee = 0; - uint256 remaining = IERC20(MINT_CURRENCY).balanceOf(address(this)); - IERC20(MINT_CURRENCY).safeTransfer(FUND_RECEIVER, remaining); + uint256 remaining = IERC20(_mintCurrency).balanceOf(address(this)); + IERC20(_mintCurrency).safeTransfer(_fundReceiver, remaining); - emit WithdrawERC20(MINT_CURRENCY, _totalMintFee + remaining); + emit WithdrawERC20(_mintCurrency, _totalMintFee + remaining); } /** @@ -777,13 +777,13 @@ contract ERC1155M is function _assertValidStageArgsLength( MintStageInfo1155 calldata stageInfo - ) internal { + ) internal view { if ( - stageInfo.price.length != NUM_TOKENS || - stageInfo.mintFee.length != NUM_TOKENS || - stageInfo.walletLimit.length != NUM_TOKENS || - stageInfo.merkleRoot.length != NUM_TOKENS || - stageInfo.maxStageSupply.length != NUM_TOKENS + stageInfo.price.length != _numTokens || + stageInfo.mintFee.length != _numTokens || + stageInfo.walletLimit.length != _numTokens || + stageInfo.merkleRoot.length != _numTokens || + stageInfo.maxStageSupply.length != _numTokens ) { revert InvalidStageArgsLength(); } diff --git a/contracts/operator-filter/OwnedRegistrant.sol b/contracts/operator-filter/OwnedRegistrant.sol deleted file mode 100644 index c7020bec..00000000 --- a/contracts/operator-filter/OwnedRegistrant.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IOperatorFilterRegistry} from "operator-filter-registry/src/IOperatorFilterRegistry.sol"; -import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS} from "../utils/Constants.sol"; -/** - * @title OwnedRegistrant - * @notice Ownable contract that registers itself with the OperatorFilterRegistry and administers its own entries, - * to facilitate a subscription whose ownership can be transferred. - */ - -contract OwnedRegistrant is Ownable2Step { - /// @dev The constructor that is called when the contract is being deployed. - constructor(address _owner) Ownable(msg.sender) { - IOperatorFilterRegistry(CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS) - .register(address(this)); - transferOwnership(_owner); - } -} diff --git a/test/generate-coverage-report.sh b/test/generate-coverage-report.sh new file mode 100755 index 00000000..da1e9239 --- /dev/null +++ b/test/generate-coverage-report.sh @@ -0,0 +1,7 @@ +forge coverage --ir-minimum --report lcov + +lcov --remove ./lcov.info -o ./lcov.info.pruned + +genhtml lcov.info.pruned --output-directory coverage + +open coverage/index.html \ No newline at end of file From 28ed36d31431538ef2af8cabb5cd097d132da7f5 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 27 Sep 2024 17:34:51 -0400 Subject: [PATCH 034/145] split cosignable Signed-off-by: Wolfy --- contracts/common/Cosignable.sol | 98 ++++++++++++ contracts/common/ErrorsAndEvents.sol | 8 +- contracts/nft/erc1155m/ERC1155M.sol | 149 +----------------- .../nft/erc1155m/ERC1155MInitializable.sol | 138 ++-------------- contracts/nft/erc1155m/ERC1155MStorage.sol | 27 ++++ contracts/nft/erc721m/ERC721CM.sol | 98 +----------- contracts/nft/erc721m/ERC721M.sol | 97 +----------- .../erc721m/v2/ERC721CMInitializableV2.sol | 101 ++---------- .../0a-create2-magicdrop-impl-registry.sh | 0 .../0b-create2-magicdrop-clone-factory.sh | 0 .../1a-deploy-magicdrop-impl-registry.sh | 0 .../1b-deploy-magicdrop-clone-factory.sh | 0 .../common/DeployMagicDropCloneFactory.sol | 0 .../DeployMagicDropTokenImplRegistry.sol | 0 14 files changed, 167 insertions(+), 549 deletions(-) create mode 100644 contracts/common/Cosignable.sol create mode 100644 contracts/nft/erc1155m/ERC1155MStorage.sol create mode 100644 scripts-foundry/common/0a-create2-magicdrop-impl-registry.sh create mode 100644 scripts-foundry/common/0b-create2-magicdrop-clone-factory.sh create mode 100644 scripts-foundry/common/1a-deploy-magicdrop-impl-registry.sh create mode 100644 scripts-foundry/common/1b-deploy-magicdrop-clone-factory.sh create mode 100644 scripts-foundry/common/DeployMagicDropCloneFactory.sol create mode 100644 scripts-foundry/common/DeployMagicDropTokenImplRegistry.sol diff --git a/contracts/common/Cosignable.sol b/contracts/common/Cosignable.sol new file mode 100644 index 00000000..09cd9cdc --- /dev/null +++ b/contracts/common/Cosignable.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +contract Cosignable { + address internal _cosigner; + uint256 internal _timestampExpirySeconds; + + event SetCosigner(address indexed cosigner); + + error CosignerNotSet(); + error InvalidCosignSignature(); + error TimestampExpired(); + + function setCosigner(address cosigner) external virtual { + _cosigner = cosigner; + emit SetCosigner(cosigner); + } + + function setTimestampExpirySeconds(uint256 timestampExpirySeconds) external virtual { + _timestampExpirySeconds = timestampExpirySeconds; + } + + function getCosignDigest( + address minter, + uint32 qty, + bool waiveMintFee, + uint64 timestamp, + uint256 cosignNonce + ) public view returns (bytes32) { + if (_cosigner == address(0)) revert CosignerNotSet(); + + return + MessageHashUtils.toEthSignedMessageHash( + keccak256( + abi.encodePacked( + address(this), + minter, + qty, + waiveMintFee, + _cosigner, + timestamp, + block.chainid, + cosignNonce + ) + ) + ); + } + + function assertValidCosign( + address minter, + uint32 qty, + uint64 timestamp, + bytes memory signature, + uint256 cosignNonce + ) public view returns (bool) { + if ( + SignatureChecker.isValidSignatureNow( + _cosigner, + getCosignDigest( + minter, + qty, + true, + timestamp, + cosignNonce + ), + signature + ) + ) { + return true; + } + + if ( + SignatureChecker.isValidSignatureNow( + _cosigner, + getCosignDigest( + minter, + qty, + false, + timestamp, + cosignNonce + ), + signature + ) + ) { + return false; + } + + revert InvalidCosignSignature(); + } + + function _assertValidTimestamp(uint64 timestamp) internal view { + if (timestamp < block.timestamp - _timestampExpirySeconds) + revert TimestampExpired(); + } +} diff --git a/contracts/common/ErrorsAndEvents.sol b/contracts/common/ErrorsAndEvents.sol index c864f987..07770153 100644 --- a/contracts/common/ErrorsAndEvents.sol +++ b/contracts/common/ErrorsAndEvents.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.20; interface ErrorsAndEvents { error CannotIncreaseMaxMintableSupply(); - error CosignerNotSet(); + // error CosignerNotSet(); error CrossmintAddressNotSet(); error CrossmintOnly(); error GlobalWalletLimitOverflow(); error InsufficientStageTimeGap(); - error InvalidCosignSignature(); + // error InvalidCosignSignature(); error InvalidProof(); error InvalidStage(); error InvalidStageArgsLength(); @@ -18,7 +18,7 @@ interface ErrorsAndEvents { error NotMintable(); error Mintable(); error StageSupplyExceeded(); - error TimestampExpired(); + // error TimestampExpired(); error TransferFailed(); error WalletGlobalLimitExceeded(); error WalletStageLimitExceeded(); @@ -29,7 +29,7 @@ interface ErrorsAndEvents { error NewSupplyLessThanTotalSupply(); error NotTransferable(); - event SetCosigner(address cosigner); + // event SetCosigner(address cosigner); event SetCrossmintAddress(address crossmintAddress); event SetMintable(bool mintable); event SetActiveStage(uint256 activeStage); diff --git a/contracts/nft/erc1155m/ERC1155M.sol b/contracts/nft/erc1155m/ERC1155M.sol index 4d979180..e5e519c8 100644 --- a/contracts/nft/erc1155m/ERC1155M.sol +++ b/contracts/nft/erc1155m/ERC1155M.sol @@ -13,8 +13,10 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "../../utils/Constants.sol"; import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; +import "./ERC1155MStorage.sol"; import "./interfaces/IERC1155M.sol"; import {MintStageInfo1155} from "../../common/Structs.sol"; +import {Cosignable} from "../../common/Cosignable.sol"; /** * @title ERC1155M @@ -31,60 +33,13 @@ contract ERC1155M is ERC1155Supply, ERC2981, Ownable2Step, - ReentrancyGuard + ReentrancyGuard, + ERC1155MStorage, + Cosignable { using ECDSA for bytes32; using SafeERC20 for IERC20; - // Collection name. - string public name; - - // Collection symbol. - string public symbol; - - // The total mintable supply per token. - uint256[] private _maxMintableSupply; - - // Global wallet limit, across all stages, per token. - uint256[] private _globalWalletLimit; - - // Mint stage information. See MintStageInfo for details. - MintStageInfo1155[] private _mintStages; - - // Whether the token can be transferred. - bool private _transferable; - - // Minted count per stage per token per wallet - mapping(uint256 => mapping(uint256 => mapping(address => uint32))) - private _stageMintedCountsPerTokenPerWallet; - - // Minted count per stage per token. - mapping(uint256 => mapping(uint256 => uint256)) - private _stageMintedCountsPerToken; - - // Total mint fee - uint256 private _totalMintFee; - - // Specify how long a signature from cosigner is valid for, recommend 300 seconds. - uint64 private _timestampExpirySeconds; - - // The address of the cosigner server. - address private _cosigner; - - // Address of ERC-20 token used to pay for minting. If 0 address, use native currency. - address private _mintCurrency; - - // Number of tokens. - uint256 private _numTokens; - - // Fund receiver - address private _fundReceiver; - - uint256 public constant VERSION = 1; - - // Authorized minters - mapping(address => bool) private _authorizedMinters; - constructor( string memory collectionName, string memory collectionSymbol, @@ -169,14 +124,6 @@ contract ERC1155M is return totalMintedByAddress(minter)[tokenId]; } - /** - * @dev Sets cosigner. - */ - function setCosigner(address cosigner) external onlyOwner { - _cosigner = cosigner; - emit SetCosigner(cosigner); - } - /** * @dev Sets stages in the format of an array of `MintStageInfo`. * @@ -476,10 +423,10 @@ contract ERC1155M is if (_cosigner != address(0)) { waiveMintFee = assertValidCosign( msg.sender, - tokenId, qty, timestamp, - signature + signature, + getCosignNonce(msg.sender, tokenId) ); _assertValidTimestamp(timestamp); stageTimestamp = timestamp; @@ -633,80 +580,6 @@ contract ERC1155M is revert InvalidStage(); } - /** - * @dev Returns data hash for the given minter, qty, waiveMintFee and timestamp. - */ - function getCosignDigest( - address minter, - uint256 tokenId, - uint32 qty, - bool waiveMintFee, - uint64 timestamp - ) public view returns (bytes32) { - if (_cosigner == address(0)) revert CosignerNotSet(); - - return - MessageHashUtils.toEthSignedMessageHash( - keccak256( - abi.encodePacked( - address(this), - minter, - qty, - waiveMintFee, - _cosigner, - timestamp, - block.chainid, - getCosignNonce(minter, tokenId) - ) - ) - ); - } - - /** - * @dev Validates the the given signature. Returns whether mint fee is waived. - */ - function assertValidCosign( - address minter, - uint256 tokenId, - uint32 qty, - uint64 timestamp, - bytes memory signature - ) public view returns (bool) { - if ( - SignatureChecker.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - tokenId, - qty, - /* waiveMintFee= */ true, - timestamp - ), - signature - ) - ) { - return true; - } - - if ( - SignatureChecker.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - tokenId, - qty, - /* waiveMintFee= */ false, - timestamp - ), - signature - ) - ) { - return false; - } - - revert InvalidCosignSignature(); - } - /** * @dev Set default royalty for all tokens */ @@ -767,14 +640,6 @@ contract ERC1155M is if (start >= end) revert InvalidStartAndEndTimestamp(); } - /** - * @dev Validates the timestamp is not expired. - */ - function _assertValidTimestamp(uint64 timestamp) internal view { - if (timestamp < block.timestamp - _timestampExpirySeconds) - revert TimestampExpired(); - } - function _assertValidStageArgsLength( MintStageInfo1155 calldata stageInfo ) internal view { diff --git a/contracts/nft/erc1155m/ERC1155MInitializable.sol b/contracts/nft/erc1155m/ERC1155MInitializable.sol index 6e6d3644..8b617118 100644 --- a/contracts/nft/erc1155m/ERC1155MInitializable.sol +++ b/contracts/nft/erc1155m/ERC1155MInitializable.sol @@ -2,22 +2,22 @@ pragma solidity ^0.8.20; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {ERC1155SupplyUpgradeable, ERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155SupplyUpgradeable.sol"; -import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import { + ERC1155SupplyUpgradeable, + ERC1155Upgradeable +} from "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155SupplyUpgradeable.sol"; import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {MintStageInfo1155} from "../../common/Structs.sol"; import {IERC1155M} from "./interfaces/IERC1155M.sol"; import {MINT_FEE_RECEIVER} from "../../utils/Constants.sol"; +import {ERC1155MStorage} from "./ERC1155MStorage.sol"; +import {Cosignable} from "../../common/Cosignable.sol"; /** @@ -35,34 +35,13 @@ contract ERC1155MInitializable is ERC1155SupplyUpgradeable, OwnableUpgradeable, ReentrancyGuard, - ERC2981 + ERC1155MStorage, + ERC2981, + Cosignable { using ECDSA for bytes32; using SafeERC20 for IERC20; - uint256 public constant VERSION = 1; - - // Mint stage information. See MintStageInfo for details. - MintStageInfo1155[] private _mintStages; - - string public name; - string public symbol; - bool private _transferable; // Whether the token can be transferred. - uint256[] private _maxMintableSupply; // The total mintable supply per token. - uint256[] private _globalWalletLimit; // Global wallet limit, across all stages, per token. - uint256 private _totalMintFee; - uint64 private immutable _timestampExpirySeconds = 300; - address private _cosigner; // The address of the cosigner server. - address private _mintCurrency; // If 0 address, use native currency. - uint256 private _numTokens; - address private _fundReceiver; - - mapping(uint256 => mapping(uint256 => mapping(address => uint32))) - private _stageMintedCountsPerTokenPerWallet; - mapping(uint256 => mapping(uint256 => uint256)) - private _stageMintedCountsPerToken; - mapping(address => bool) private _authorizedMinters; - function initialize( string calldata name_, string calldata symbol_, @@ -74,11 +53,11 @@ contract ERC1155MInitializable is __Ownable_init(initialOwner); } - function setup( string calldata uri_, uint256[] memory maxMintableSupply, uint256[] memory globalWalletLimit, + uint64 timestampExpirySeconds, address cosigner, address mintCurrency, address fundReceiver, @@ -105,7 +84,8 @@ contract ERC1155MInitializable is _transferable = true; _mintCurrency = mintCurrency; _fundReceiver = fundReceiver; - + _timestampExpirySeconds = timestampExpirySeconds; + _setURI(uri_); setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); } @@ -153,14 +133,6 @@ contract ERC1155MInitializable is return totalMintedByAddress(minter)[tokenId]; } - /** - * @dev Sets cosigner. - */ - function setCosigner(address cosigner) external onlyOwner { - _cosigner = cosigner; - emit SetCosigner(cosigner); - } - /** * @dev Sets stages in the format of an array of `MintStageInfo`. * @@ -460,10 +432,10 @@ contract ERC1155MInitializable is if (_cosigner != address(0)) { waiveMintFee = assertValidCosign( msg.sender, - tokenId, qty, timestamp, - signature + signature, + getCosignNonce(msg.sender, tokenId) ); _assertValidTimestamp(timestamp); stageTimestamp = timestamp; @@ -617,80 +589,6 @@ contract ERC1155MInitializable is revert InvalidStage(); } - /** - * @dev Returns data hash for the given minter, qty, waiveMintFee and timestamp. - */ - function getCosignDigest( - address minter, - uint256 tokenId, - uint32 qty, - bool waiveMintFee, - uint64 timestamp - ) public view returns (bytes32) { - if (_cosigner == address(0)) revert CosignerNotSet(); - - return - MessageHashUtils.toEthSignedMessageHash( - keccak256( - abi.encodePacked( - address(this), - minter, - qty, - waiveMintFee, - _cosigner, - timestamp, - block.chainid, - getCosignNonce(minter, tokenId) - ) - ) - ); - } - - /** - * @dev Validates the the given signature. Returns whether mint fee is waived. - */ - function assertValidCosign( - address minter, - uint256 tokenId, - uint32 qty, - uint64 timestamp, - bytes memory signature - ) public view returns (bool) { - if ( - SignatureChecker.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - tokenId, - qty, - /* waiveMintFee= */ true, - timestamp - ), - signature - ) - ) { - return true; - } - - if ( - SignatureChecker.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - tokenId, - qty, - /* waiveMintFee= */ false, - timestamp - ), - signature - ) - ) { - return false; - } - - revert InvalidCosignSignature(); - } - function supportsInterface( bytes4 interfaceId ) public view virtual override(ERC2981, ERC1155Upgradeable) returns (bool) { @@ -728,14 +626,6 @@ contract ERC1155MInitializable is if (start >= end) revert InvalidStartAndEndTimestamp(); } - /** - * @dev Validates the timestamp is not expired. - */ - function _assertValidTimestamp(uint64 timestamp) internal view { - if (timestamp < block.timestamp - _timestampExpirySeconds) - revert TimestampExpired(); - } - function _assertValidStageArgsLength( MintStageInfo1155 calldata stageInfo ) internal view { diff --git a/contracts/nft/erc1155m/ERC1155MStorage.sol b/contracts/nft/erc1155m/ERC1155MStorage.sol new file mode 100644 index 00000000..ed427c79 --- /dev/null +++ b/contracts/nft/erc1155m/ERC1155MStorage.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {MintStageInfo1155} from "../../common/Structs.sol"; + +contract ERC1155MStorage { + uint256 public constant VERSION = 1; + + // Mint stage information. See MintStageInfo for details. + MintStageInfo1155[] internal _mintStages; + + string public name; + string public symbol; + bool internal _transferable; // Whether the token can be transferred. + uint256[] internal _maxMintableSupply; // The total mintable supply per token. + uint256[] internal _globalWalletLimit; // Global wallet limit, across all stages, per token. + uint256 internal _totalMintFee; + address internal _mintCurrency; // If 0 address, use native currency. + uint256 internal _numTokens; + address internal _fundReceiver; + + mapping(uint256 => mapping(uint256 => mapping(address => uint32))) + internal _stageMintedCountsPerTokenPerWallet; + mapping(uint256 => mapping(uint256 => uint256)) + internal _stageMintedCountsPerToken; + mapping(address => bool) internal _authorizedMinters; +} diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index 3ce9b3c2..6f1dbf28 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -13,7 +13,7 @@ import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import "../creator-token-standards/ERC721ACQueryable.sol"; import "./interfaces/IERC721M.sol"; import "../../utils/Constants.sol"; - +import "../../common/Cosignable.sol"; /** * @title ERC721CM * @@ -24,19 +24,13 @@ import "../../utils/Constants.sol"; * - crossmint support * - anti-botting */ -contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard { +contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosignable { using ECDSA for bytes32; using SafeERC20 for IERC20; // Whether this contract is mintable. bool private _mintable; - // Specify how long a signature from cosigner is valid for, recommend 300 seconds. - uint64 private _timestampExpirySeconds; - - // The address of the cosigner server. - address private _cosigner; - // The crossmint address. Need to set if using crossmint. address private _crossmintAddress; @@ -133,14 +127,6 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard { return _numberMinted(minter); } - /** - * @dev Sets cosigner. - */ - function setCosigner(address cosigner) external onlyOwner { - _cosigner = cosigner; - emit SetCosigner(cosigner); - } - /** * @dev Sets expiry in seconds. This timestamp specifies how long a signature from cosigner is valid for. */ @@ -432,7 +418,8 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard { msg.sender, qty, timestamp, - signature + signature, + getCosignNonce(msg.sender) ); _assertValidTimestamp(timestamp); stageTimestamp = timestamp; @@ -595,75 +582,6 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard { _contractURI = uri; } - /** - * @dev Returns data hash for the given minter, qty, waiveMintFee and timestamp. - */ - function getCosignDigest( - address minter, - uint32 qty, - bool waiveMintFee, - uint64 timestamp - ) public view returns (bytes32) { - if (_cosigner == address(0)) revert CosignerNotSet(); - return - MessageHashUtils.toEthSignedMessageHash( - keccak256( - abi.encodePacked( - address(this), - minter, - qty, - waiveMintFee, - _cosigner, - timestamp, - _chainID(), - getCosignNonce(minter) - ) - ) - ); - } - - /** - * @dev Validates the the given signature. Returns whether mint fee is waived. - */ - function assertValidCosign( - address minter, - uint32 qty, - uint64 timestamp, - bytes memory signature - ) public view returns (bool) { - if ( - SignatureChecker.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - qty, - /* waiveMintFee= */ true, - timestamp - ), - signature - ) - ) { - return true; - } - - if ( - SignatureChecker.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - qty, - /* waiveMintFee= */ false, - timestamp - ), - signature - ) - ) { - return false; - } - - revert InvalidCosignSignature(); - } - /** * @dev Returns the current active stage based on timestamp. */ @@ -684,14 +602,6 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard { revert InvalidStage(); } - /** - * @dev Validates the timestamp is not expired. - */ - function _assertValidTimestamp(uint64 timestamp) internal view { - if (timestamp < block.timestamp - _timestampExpirySeconds) - revert TimestampExpired(); - } - /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ diff --git a/contracts/nft/erc721m/ERC721M.sol b/contracts/nft/erc721m/ERC721M.sol index 1f65086f..d469a045 100644 --- a/contracts/nft/erc721m/ERC721M.sol +++ b/contracts/nft/erc721m/ERC721M.sol @@ -12,6 +12,7 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "erc721a/contracts/extensions/ERC721AQueryable.sol"; import "./interfaces/IERC721M.sol"; import "../../utils/Constants.sol"; +import "../../common/Cosignable.sol"; /** * @title ERC721M @@ -23,7 +24,7 @@ import "../../utils/Constants.sol"; * - crossmint support * - anti-botting */ -contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { +contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosignable { using ECDSA for bytes32; using SafeERC20 for IERC20; @@ -33,12 +34,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { // Whether base URI is permanent. Once set, base URI is immutable. bool private _baseURIPermanent; - // Specify how long a signature from cosigner is valid for, recommend 300 seconds. - uint64 private _timestampExpirySeconds; - - // The address of the cosigner server. - address private _cosigner; - // The crossmint address. Need to set if using crossmint. address private _crossmintAddress; @@ -132,14 +127,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { return _numberMinted(minter); } - /** - * @dev Sets cosigner. - */ - function setCosigner(address cosigner) external onlyOwner { - _cosigner = cosigner; - emit SetCosigner(cosigner); - } - /** * @dev Sets expiry in seconds. This timestamp specifies how long a signature from cosigner is valid for. */ @@ -427,7 +414,8 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { msg.sender, qty, timestamp, - signature + signature, + getCosignNonce(msg.sender) ); _assertValidTimestamp(timestamp); stageTimestamp = timestamp; @@ -577,75 +565,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { : ""; } - /** - * @dev Returns data hash for the given minter, qty, waiveMintFee and timestamp. - */ - function getCosignDigest( - address minter, - uint32 qty, - bool waiveMintFee, - uint64 timestamp - ) public view returns (bytes32) { - if (_cosigner == address(0)) revert CosignerNotSet(); - return - MessageHashUtils.toEthSignedMessageHash( - keccak256( - abi.encodePacked( - address(this), - minter, - qty, - waiveMintFee, - _cosigner, - timestamp, - _chainID(), - getCosignNonce(minter) - ) - ) - ); - } - - /** - * @dev Validates the the given signature. Returns whether mint fee is waived. - */ - function assertValidCosign( - address minter, - uint32 qty, - uint64 timestamp, - bytes memory signature - ) public view returns (bool) { - if ( - SignatureChecker.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - qty, - /* waiveMintFee= */ true, - timestamp - ), - signature - ) - ) { - return true; - } - - if ( - SignatureChecker.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - qty, - /* waiveMintFee= */ false, - timestamp - ), - signature - ) - ) { - return false; - } - - revert InvalidCosignSignature(); - } - /** * @dev Returns the current active stage based on timestamp. */ @@ -663,14 +582,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard { revert InvalidStage(); } - /** - * @dev Validates the timestamp is not expired. - */ - function _assertValidTimestamp(uint64 timestamp) internal view { - if (timestamp < block.timestamp - _timestampExpirySeconds) - revert TimestampExpired(); - } - /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol index 06636701..eca5547c 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol @@ -6,7 +6,6 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -15,6 +14,8 @@ import {MINT_FEE_RECEIVER} from "../../../utils/Constants.sol"; import {UpdatableRoyaltiesInitializable, ERC2981} from "../../../royalties/UpdatableRoyaltiesInitializable.sol"; import {MintStageInfo} from "../../../common/Structs.sol"; import {IERC721MInitializable} from "../interfaces/IERC721MInitializable.sol"; +import {Cosignable} from "../../../common/Cosignable.sol"; + /** * @title ERC721CMInitializableV2 * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this @@ -24,7 +25,8 @@ contract ERC721CMInitializableV2 is IERC721MInitializable, ERC721ACQueryableInitializable, UpdatableRoyaltiesInitializable, - ReentrancyGuard + ReentrancyGuard, + Cosignable { using ECDSA for bytes32; using SafeERC20 for IERC20; @@ -32,12 +34,6 @@ contract ERC721CMInitializableV2 is // Whether this contract is mintable. bool private _mintable; - // Specify how long a signature from cosigner is valid for, recommend 300 seconds. - uint64 private immutable _timestampExpirySeconds = 300; - - // The address of the cosigner server. - address private _cosigner; - // The crossmint address. Need to set if using crossmint. address private _crossmintAddress; @@ -93,6 +89,7 @@ contract ERC721CMInitializableV2 is string calldata tokenURISuffix, uint256 maxMintableSupply, uint256 globalWalletLimit, + uint256 timestampExpirySeconds, address mintCurrency, address fundReceiver, address crossmintAddress, @@ -109,7 +106,8 @@ contract ERC721CMInitializableV2 is _mintCurrency = mintCurrency; _fundReceiver = fundReceiver; _crossmintAddress = crossmintAddress; - + _timestampExpirySeconds = timestampExpirySeconds; + if (initialStages.length > 0) { _setStages(initialStages); } @@ -197,14 +195,6 @@ contract ERC721CMInitializableV2 is return _numberMinted(minter); } - /** - * @dev Sets cosigner. - */ - function setCosigner(address cosigner) external onlyOwner { - _cosigner = cosigner; - emit SetCosigner(cosigner); - } - /** * @dev Sets crossmint address if using crossmint. This allows the specified address to call `crossmint`. */ @@ -421,7 +411,8 @@ contract ERC721CMInitializableV2 is msg.sender, qty, timestamp, - signature + signature, + getCosignNonce(msg.sender) ); _assertValidTimestamp(timestamp); stageTimestamp = timestamp; @@ -576,33 +567,6 @@ contract ERC721CMInitializableV2 is : ""; } - /** - * @dev Returns data hash for the given minter, qty, waiveMintFee and timestamp. - */ - function getCosignDigest( - address minter, - uint32 qty, - bool waiveMintFee, - uint64 timestamp - ) public view returns (bytes32) { - if (_cosigner == address(0)) revert CosignerNotSet(); - return - MessageHashUtils.toEthSignedMessageHash( - keccak256( - abi.encodePacked( - address(this), - minter, - qty, - waiveMintFee, - _cosigner, - timestamp, - _chainID(), - getCosignNonce(minter) - ) - ) - ); - } - /** * @dev Returns URI for the collection-level metadata. */ @@ -617,45 +581,6 @@ contract ERC721CMInitializableV2 is _contractURI = uri; } - function assertValidCosign( - address minter, - uint32 qty, - uint64 timestamp, - bytes memory signature - ) public view returns (bool) { - if ( - SignatureChecker.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - qty, - /* waiveMintFee= */ true, - timestamp - ), - signature - ) - ) { - return true; - } - - if ( - SignatureChecker.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - qty, - /* waiveMintFee= */ false, - timestamp - ), - signature - ) - ) { - return false; - } - - revert InvalidCosignSignature(); - } - /** * @dev Returns the current active stage based on timestamp. */ @@ -676,14 +601,6 @@ contract ERC721CMInitializableV2 is revert InvalidStage(); } - /** - * @dev Validates the timestamp is not expired. - */ - function _assertValidTimestamp(uint64 timestamp) internal view { - if (timestamp < block.timestamp - _timestampExpirySeconds) - revert TimestampExpired(); - } - /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ diff --git a/scripts-foundry/common/0a-create2-magicdrop-impl-registry.sh b/scripts-foundry/common/0a-create2-magicdrop-impl-registry.sh new file mode 100644 index 00000000..e69de29b diff --git a/scripts-foundry/common/0b-create2-magicdrop-clone-factory.sh b/scripts-foundry/common/0b-create2-magicdrop-clone-factory.sh new file mode 100644 index 00000000..e69de29b diff --git a/scripts-foundry/common/1a-deploy-magicdrop-impl-registry.sh b/scripts-foundry/common/1a-deploy-magicdrop-impl-registry.sh new file mode 100644 index 00000000..e69de29b diff --git a/scripts-foundry/common/1b-deploy-magicdrop-clone-factory.sh b/scripts-foundry/common/1b-deploy-magicdrop-clone-factory.sh new file mode 100644 index 00000000..e69de29b diff --git a/scripts-foundry/common/DeployMagicDropCloneFactory.sol b/scripts-foundry/common/DeployMagicDropCloneFactory.sol new file mode 100644 index 00000000..e69de29b diff --git a/scripts-foundry/common/DeployMagicDropTokenImplRegistry.sol b/scripts-foundry/common/DeployMagicDropTokenImplRegistry.sol new file mode 100644 index 00000000..e69de29b From 9f79738d3bc0a026f32a3c0c5c9483a1ece7f3b0 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 27 Sep 2024 17:54:35 -0400 Subject: [PATCH 035/145] add authorizedmintercontrol Signed-off-by: Wolfy --- contracts/common/AuthorizedMinterControl.sol | 61 +++++++++++++++++++ contracts/common/ErrorsAndEvents.sol | 2 +- contracts/nft/erc1155m/ERC1155M.sol | 20 +++--- .../nft/erc1155m/ERC1155MInitializable.sol | 28 +++------ contracts/nft/erc1155m/ERC1155MStorage.sol | 1 - contracts/nft/erc721m/ERC721CM.sol | 23 +++---- contracts/nft/erc721m/ERC721M.sol | 23 ++----- .../erc721m/v2/ERC721CMInitializableV2.sol | 23 +++---- 8 files changed, 96 insertions(+), 85 deletions(-) create mode 100644 contracts/common/AuthorizedMinterControl.sol diff --git a/contracts/common/AuthorizedMinterControl.sol b/contracts/common/AuthorizedMinterControl.sol new file mode 100644 index 00000000..e2051abb --- /dev/null +++ b/contracts/common/AuthorizedMinterControl.sol @@ -0,0 +1,61 @@ +pragma solidity ^0.8.20; + +/** + * @title AuthorizedMinterControl + * @dev Abstract contract to manage authorized minters for ERC1155M tokens + */ +abstract contract AuthorizedMinterControl { + mapping(address => bool) private _authorizedMinters; + + event AuthorizedMinterAdded(address indexed minter); + event AuthorizedMinterRemoved(address indexed minter); + + error NotAuthorized(); + + /** + * @dev Modifier to check if the sender is an authorized minter + */ + modifier onlyAuthorizedMinter() { + if (!isAuthorizedMinter(msg.sender)) revert NotAuthorized(); + _; + } + + /** + * @dev Add an authorized minter. Implementation should include access control. + * @param minter Address to be added as an authorized minter + */ + function addAuthorizedMinter(address minter) external virtual; + + /** + * @dev Remove an authorized minter. Implementation should include access control. + * @param minter Address to be removed from authorized minters + */ + function removeAuthorizedMinter(address minter) external virtual; + + /** + * @dev Internal function to add an authorized minter + * @param minter Address to be added as an authorized minter + */ + function _addAuthorizedMinter(address minter) internal { + _authorizedMinters[minter] = true; + emit AuthorizedMinterAdded(minter); + } + + /** + * @dev Internal function to remove an authorized minter + * @param minter Address to be removed from authorized minters + */ + function _removeAuthorizedMinter(address minter) internal { + _authorizedMinters[minter] = false; + emit AuthorizedMinterRemoved(minter); + } + + /** + * @dev Check if an address is an authorized minter + * @param minter Address to check + * @return bool True if the address is an authorized minter, false otherwise + */ + function isAuthorizedMinter(address minter) public view returns (bool) { + return _authorizedMinters[minter]; + } +} diff --git a/contracts/common/ErrorsAndEvents.sol b/contracts/common/ErrorsAndEvents.sol index 07770153..4b60f3b1 100644 --- a/contracts/common/ErrorsAndEvents.sol +++ b/contracts/common/ErrorsAndEvents.sol @@ -25,7 +25,7 @@ interface ErrorsAndEvents { error WithdrawFailed(); error WrongMintCurrency(); error NotSupported(); - error NotAuthorized(); + // error NotAuthorized(); error NewSupplyLessThanTotalSupply(); error NotTransferable(); diff --git a/contracts/nft/erc1155m/ERC1155M.sol b/contracts/nft/erc1155m/ERC1155M.sol index e5e519c8..6466e7f4 100644 --- a/contracts/nft/erc1155m/ERC1155M.sol +++ b/contracts/nft/erc1155m/ERC1155M.sol @@ -17,6 +17,7 @@ import "./ERC1155MStorage.sol"; import "./interfaces/IERC1155M.sol"; import {MintStageInfo1155} from "../../common/Structs.sol"; import {Cosignable} from "../../common/Cosignable.sol"; +import {AuthorizedMinterControl} from "../../common/AuthorizedMinterControl.sol"; /** * @title ERC1155M @@ -35,7 +36,8 @@ contract ERC1155M is Ownable2Step, ReentrancyGuard, ERC1155MStorage, - Cosignable + Cosignable, + AuthorizedMinterControl { using ECDSA for bytes32; using SafeERC20 for IERC20; @@ -92,26 +94,18 @@ contract ERC1155M is _; } - /** - * @dev Returns whether the msg sender is authorized to mint. - */ - modifier onlyAuthorizedMinter() { - if (_authorizedMinters[_msgSender()] != true) revert NotAuthorized(); - _; - } - /** * @dev Add authorized minter. Can only be called by contract owner. */ - function addAuthorizedMinter(address minter) external onlyOwner { - _authorizedMinters[minter] = true; + function addAuthorizedMinter(address minter) external onlyOwner override { + _addAuthorizedMinter(minter); } /** * @dev Remove authorized minter. Can only be called by contract owner. */ - function removeAuthorizedMinter(address minter) external onlyOwner { - _authorizedMinters[minter] = false; + function removeAuthorizedMinter(address minter) external onlyOwner override { + _removeAuthorizedMinter(minter); } /** diff --git a/contracts/nft/erc1155m/ERC1155MInitializable.sol b/contracts/nft/erc1155m/ERC1155MInitializable.sol index 8b617118..6fb74bc8 100644 --- a/contracts/nft/erc1155m/ERC1155MInitializable.sol +++ b/contracts/nft/erc1155m/ERC1155MInitializable.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.20; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -18,6 +17,7 @@ import {IERC1155M} from "./interfaces/IERC1155M.sol"; import {MINT_FEE_RECEIVER} from "../../utils/Constants.sol"; import {ERC1155MStorage} from "./ERC1155MStorage.sol"; import {Cosignable} from "../../common/Cosignable.sol"; +import {AuthorizedMinterControl} from "../../common/AuthorizedMinterControl.sol"; /** @@ -37,9 +37,9 @@ contract ERC1155MInitializable is ReentrancyGuard, ERC1155MStorage, ERC2981, - Cosignable + Cosignable, + AuthorizedMinterControl { - using ECDSA for bytes32; using SafeERC20 for IERC20; function initialize( @@ -101,26 +101,12 @@ contract ERC1155MInitializable is _; } - /** - * @dev Returns whether the msg sender is authorized to mint. - */ - modifier onlyAuthorizedMinter() { - if (_authorizedMinters[_msgSender()] != true) revert NotAuthorized(); - _; + function addAuthorizedMinter(address minter) external onlyOwner override { + _addAuthorizedMinter(minter); } - /** - * @dev Add authorized minter. Can only be called by contract owner. - */ - function addAuthorizedMinter(address minter) external onlyOwner { - _authorizedMinters[minter] = true; - } - - /** - * @dev Remove authorized minter. Can only be called by contract owner. - */ - function removeAuthorizedMinter(address minter) external onlyOwner { - _authorizedMinters[minter] = false; + function removeAuthorizedMinter(address minter) external onlyOwner override { + _removeAuthorizedMinter(minter); } /** diff --git a/contracts/nft/erc1155m/ERC1155MStorage.sol b/contracts/nft/erc1155m/ERC1155MStorage.sol index ed427c79..4a12106b 100644 --- a/contracts/nft/erc1155m/ERC1155MStorage.sol +++ b/contracts/nft/erc1155m/ERC1155MStorage.sol @@ -23,5 +23,4 @@ contract ERC1155MStorage { internal _stageMintedCountsPerTokenPerWallet; mapping(uint256 => mapping(uint256 => uint256)) internal _stageMintedCountsPerToken; - mapping(address => bool) internal _authorizedMinters; } diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index 6f1dbf28..59aaae7a 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -14,6 +14,8 @@ import "../creator-token-standards/ERC721ACQueryable.sol"; import "./interfaces/IERC721M.sol"; import "../../utils/Constants.sol"; import "../../common/Cosignable.sol"; +import "../../common/AuthorizedMinterControl.sol"; + /** * @title ERC721CM * @@ -24,7 +26,7 @@ import "../../common/Cosignable.sol"; * - crossmint support * - anti-botting */ -contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosignable { +contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosignable, AuthorizedMinterControl { using ECDSA for bytes32; using SafeERC20 for IERC20; @@ -68,9 +70,6 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi // Fund receiver address public immutable FUND_RECEIVER; - // Authorized minters - mapping(address => bool) private _authorizedMinters; - uint256 public constant VERSION = 1; constructor( @@ -112,14 +111,6 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi _; } - /** - * @dev Returns whether the msg sender is authorized to mint. - */ - modifier onlyAuthorizedMinter() { - if (_authorizedMinters[_msgSender()] != true) revert NotAuthorized(); - _; - } - /** * @dev Returns cosign nonce. */ @@ -146,15 +137,15 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi /** * @dev Add authorized minter. Can only be called by contract owner. */ - function addAuthorizedMinter(address minter) external onlyOwner { - _authorizedMinters[minter] = true; + function addAuthorizedMinter(address minter) external onlyOwner override { + _addAuthorizedMinter(minter); } /** * @dev Remove authorized minter. Can only be called by contract owner. */ - function removeAuthorizedMinter(address minter) external onlyOwner { - _authorizedMinters[minter] = false; + function removeAuthorizedMinter(address minter) external onlyOwner override { + _removeAuthorizedMinter(minter); } /** diff --git a/contracts/nft/erc721m/ERC721M.sol b/contracts/nft/erc721m/ERC721M.sol index d469a045..a0bcac0f 100644 --- a/contracts/nft/erc721m/ERC721M.sol +++ b/contracts/nft/erc721m/ERC721M.sol @@ -13,7 +13,7 @@ import "erc721a/contracts/extensions/ERC721AQueryable.sol"; import "./interfaces/IERC721M.sol"; import "../../utils/Constants.sol"; import "../../common/Cosignable.sol"; - +import "../../common/AuthorizedMinterControl.sol"; /** * @title ERC721M * @@ -24,7 +24,7 @@ import "../../common/Cosignable.sol"; * - crossmint support * - anti-botting */ -contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosignable { +contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosignable, AuthorizedMinterControl { using ECDSA for bytes32; using SafeERC20 for IERC20; @@ -68,9 +68,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosign // Fund receiver address public immutable FUND_RECEIVER; - // Authorized minters - mapping(address => bool) private _authorizedMinters; - uint256 public constant VERSION = 1; constructor( @@ -112,14 +109,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosign _; } - /** - * @dev Returns whether the msg sender is authorized to mint. - */ - modifier onlyAuthorizedMinter() { - if (_authorizedMinters[_msgSender()] != true) revert NotAuthorized(); - _; - } - /** * @dev Returns cosign nonce. */ @@ -146,15 +135,15 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosign /** * @dev Add authorized minter. Can only be called by contract owner. */ - function addAuthorizedMinter(address minter) external onlyOwner { - _authorizedMinters[minter] = true; + function addAuthorizedMinter(address minter) external onlyOwner override { + _addAuthorizedMinter(minter); } /** * @dev Remove authorized minter. Can only be called by contract owner. */ - function removeAuthorizedMinter(address minter) external onlyOwner { - _authorizedMinters[minter] = false; + function removeAuthorizedMinter(address minter) external onlyOwner override { + _removeAuthorizedMinter(minter); } /** diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol index eca5547c..ee13ea01 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol @@ -15,6 +15,7 @@ import {UpdatableRoyaltiesInitializable, ERC2981} from "../../../royalties/Updat import {MintStageInfo} from "../../../common/Structs.sol"; import {IERC721MInitializable} from "../interfaces/IERC721MInitializable.sol"; import {Cosignable} from "../../../common/Cosignable.sol"; +import {AuthorizedMinterControl} from "../../../common/AuthorizedMinterControl.sol"; /** * @title ERC721CMInitializableV2 @@ -26,7 +27,8 @@ contract ERC721CMInitializableV2 is ERC721ACQueryableInitializable, UpdatableRoyaltiesInitializable, ReentrancyGuard, - Cosignable + Cosignable, + AuthorizedMinterControl { using ECDSA for bytes32; using SafeERC20 for IERC20; @@ -71,9 +73,6 @@ contract ERC721CMInitializableV2 is // Fund receiver address private _fundReceiver; - // Authorized minters - mapping(address => bool) private _authorizedMinters; - uint256 public constant VERSION = 2; constructor(address initialOwner) Ownable(initialOwner) {} @@ -180,14 +179,6 @@ contract ERC721CMInitializableV2 is _; } - /** - * @dev Returns whether the msg sender is authorized to mint. - */ - modifier onlyAuthorizedMinter() { - if (_authorizedMinters[_msgSender()] != true) revert NotAuthorized(); - _; - } - /** * @dev Returns cosign nonce. */ @@ -206,15 +197,15 @@ contract ERC721CMInitializableV2 is /** * @dev Add authorized minter. Can only be called by contract owner. */ - function addAuthorizedMinter(address minter) external onlyOwner { - _authorizedMinters[minter] = true; + function addAuthorizedMinter(address minter) external onlyOwner override { + _addAuthorizedMinter(minter); } /** * @dev Remove authorized minter. Can only be called by contract owner. */ - function removeAuthorizedMinter(address minter) external onlyOwner { - _authorizedMinters[minter] = false; + function removeAuthorizedMinter(address minter) external onlyOwner override { + _removeAuthorizedMinter(minter); } /** From d00310cc1f95a39191bfd17c89d2029512be5c0c Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 3 Oct 2024 15:38:13 -0700 Subject: [PATCH 036/145] use solady Signed-off-by: Wolfy --- .../nft/erc1155m/ERC1155MErrorsAndEvents.sol | 1 + .../nft/erc1155m/ERC1155MInitializable.sol | 85 ++++++------------- hardhat.config.ts | 2 +- package-lock.json | 17 +++- package.json | 5 +- remappings.txt | 2 +- 6 files changed, 44 insertions(+), 68 deletions(-) diff --git a/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol b/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol index 12224eed..80174f79 100644 --- a/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol +++ b/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol @@ -6,6 +6,7 @@ import {ErrorsAndEvents} from "../../common/ErrorsAndEvents.sol"; interface ERC1155MErrorsAndEvents is ErrorsAndEvents { error InvalidLimitArgsLength(); error InvalidTokenId(); + error InsufficientBalance(); event UpdateStage( uint256 indexed stage, diff --git a/contracts/nft/erc1155m/ERC1155MInitializable.sol b/contracts/nft/erc1155m/ERC1155MInitializable.sol index 6fb74bc8..cf38651b 100644 --- a/contracts/nft/erc1155m/ERC1155MInitializable.sol +++ b/contracts/nft/erc1155m/ERC1155MInitializable.sol @@ -2,15 +2,15 @@ pragma solidity ^0.8.20; -import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { ERC1155SupplyUpgradeable, ERC1155Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155SupplyUpgradeable.sol"; -import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {ERC2981} from "solady/src/tokens/ERC2981.sol"; +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {ReentrancyGuard} from "solady/src/utils/ReentrancyGuard.sol"; +import {MerkleProofLib} from "solady/src/utils/MerkleProofLib.sol"; +import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol"; import {MintStageInfo1155} from "../../common/Structs.sol"; import {IERC1155M} from "./interfaces/IERC1155M.sol"; @@ -33,14 +33,13 @@ import {AuthorizedMinterControl} from "../../common/AuthorizedMinterControl.sol" contract ERC1155MInitializable is IERC1155M, ERC1155SupplyUpgradeable, - OwnableUpgradeable, + Ownable, ReentrancyGuard, ERC1155MStorage, ERC2981, Cosignable, AuthorizedMinterControl { - using SafeERC20 for IERC20; function initialize( string calldata name_, @@ -50,7 +49,7 @@ contract ERC1155MInitializable is name = name_; symbol = symbol_; __ERC1155_init(""); - __Ownable_init(initialOwner); + _initializeOwner(initialOwner); } function setup( @@ -101,11 +100,11 @@ contract ERC1155MInitializable is _; } - function addAuthorizedMinter(address minter) external onlyOwner override { + function addAuthorizedMinter(address minter) external override onlyOwner { _addAuthorizedMinter(minter); } - function removeAuthorizedMinter(address minter) external onlyOwner override { + function removeAuthorizedMinter(address minter) external override onlyOwner { _removeAuthorizedMinter(minter); } @@ -121,26 +120,6 @@ contract ERC1155MInitializable is /** * @dev Sets stages in the format of an array of `MintStageInfo`. - * - * Following is an example of launch with two stages. The first stage is exclusive for whitelisted wallets - * specified by merkle root. - * [{ - * price: 10000000000000000000, - * maxStageSupply: 2000, - * walletLimit: 1, - * merkleRoot: 0x559fadeb887449800b7b320bf1e92d309f329b9641ac238bebdb74e15c0a5218, - * startTimeUnixSeconds: 1667768000, - * endTimeUnixSeconds: 1667771600, - * }, - * { - * price: 20000000000000000000, - * maxStageSupply: 3000, - * walletLimit: 2, - * merkleRoot: 0, - * startTimeUnixSeconds: 1667771600, - * endTimeUnixSeconds: 1667775200, - * } - * ] */ function setStages(MintStageInfo1155[] calldata newStages) external onlyOwner { delete _mintStages; @@ -465,12 +444,9 @@ contract ERC1155MInitializable is // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required if (stage.merkleRoot[tokenId] != 0) { - if ( - MerkleProof.processProof( - proof, - keccak256(abi.encodePacked(to, limit)) - ) != stage.merkleRoot[tokenId] - ) revert InvalidProof(); + if (!MerkleProofLib.verify(proof, stage.merkleRoot[tokenId], keccak256(abi.encodePacked(to, limit)))) { + revert InvalidProof(); + } // Verify merkle proof mint limit if ( @@ -485,7 +461,8 @@ contract ERC1155MInitializable is if (_mintCurrency != address(0)) { // ERC20 mint payment - IERC20(_mintCurrency).safeTransferFrom( + SafeTransferLib.safeTransferFrom( + _mintCurrency, msg.sender, address(this), (stage.price[tokenId] + adjustedMintFee) * qty @@ -533,13 +510,18 @@ contract ERC1155MInitializable is function withdrawERC20() external onlyOwner { if (_mintCurrency == address(0)) revert WrongMintCurrency(); - IERC20(_mintCurrency).safeTransfer(MINT_FEE_RECEIVER, _totalMintFee); + uint256 totalFee = _totalMintFee; + uint256 remaining = SafeTransferLib.balanceOf(_mintCurrency, address(this)); + + if (remaining < totalFee) revert InsufficientBalance(); + _totalMintFee = 0; + uint256 totalAmount = totalFee + remaining; - uint256 remaining = IERC20(_mintCurrency).balanceOf(address(this)); - IERC20(_mintCurrency).safeTransfer(_fundReceiver, remaining); + SafeTransferLib.safeTransfer(_mintCurrency, MINT_FEE_RECEIVER, totalFee); + SafeTransferLib.safeTransfer(_mintCurrency, _fundReceiver, remaining); - emit WithdrawERC20(_mintCurrency, _totalMintFee + remaining); + emit WithdrawERC20(_mintCurrency, totalAmount); } /** @@ -583,25 +565,6 @@ contract ERC1155MInitializable is ERC1155Upgradeable.supportsInterface(interfaceId); } - /** - * @dev The hook of token transfer to validate the transfer. - */ - function _update( - address from, - address to, - uint256[] memory ids, - uint256[] memory values - ) internal virtual override { - super._update(from, to, ids, values); - - bool fromZeroAddress = from == address(0); - bool toZeroAddress = to == address(0); - - if (!fromZeroAddress && !toZeroAddress && !_transferable) { - revert NotTransferable(); - } - } - /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ @@ -642,4 +605,4 @@ contract ERC1155MInitializable is super._setTokenRoyalty(tokenId, receiver, feeNumerator); emit TokenRoyaltySet(tokenId, receiver, feeNumerator); } -} +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 4ec9ba0f..07e5044c 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -53,7 +53,7 @@ const config: HardhatUserConfig = { viaIR: true, optimizer: { enabled: true, - runs: 20, + runs: 100, details: { yulDetails: { optimizerSteps: "dhfoD[xarrscLMcCTU]uljmul", diff --git a/package-lock.json b/package-lock.json index 217dc308..97c098e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@me-foundation/magicdrop", - "version": "0.1.5", + "version": "0.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@me-foundation/magicdrop", - "version": "0.1.5", + "version": "0.2.0", "dependencies": { "@inquirer/prompts": "^2.2.0", "@layerzerolabs/solidity-examples": "^0.0.13", @@ -15,7 +15,8 @@ "@openzeppelin/contracts-upgradeable": "^5.0.2", "erc721a": "^4.2.3", "erc721a-upgradeable": "^4.3.0", - "operator-filter-registry": "^1.4.2" + "operator-filter-registry": "^1.4.2", + "solady": "^0.0.249" }, "devDependencies": { "@ethersproject/abstract-provider": "^5.7.0", @@ -21870,6 +21871,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/solady": { + "version": "0.0.249", + "resolved": "https://registry.npmjs.org/solady/-/solady-0.0.249.tgz", + "integrity": "sha512-35xhSV9aU6xaoRKvtba8TcYdsC571czhnF/vH3iCZu6AsWZebneuZv2ExT4UZbM9SrUGIt+ESygDBQ1ZNSQUaw==" + }, "node_modules/solc": { "version": "0.6.12", "resolved": "https://registry.npmjs.org/solc/-/solc-0.6.12.tgz", @@ -40569,6 +40575,11 @@ } } }, + "solady": { + "version": "0.0.249", + "resolved": "https://registry.npmjs.org/solady/-/solady-0.0.249.tgz", + "integrity": "sha512-35xhSV9aU6xaoRKvtba8TcYdsC571czhnF/vH3iCZu6AsWZebneuZv2ExT4UZbM9SrUGIt+ESygDBQ1ZNSQUaw==" + }, "solc": { "version": "0.6.12", "resolved": "https://registry.npmjs.org/solc/-/solc-0.6.12.tgz", diff --git a/package.json b/package.json index 433d4fb2..38179f02 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "@openzeppelin/contracts-upgradeable": "^5.0.2", "erc721a": "^4.2.3", "erc721a-upgradeable": "^4.3.0", - "operator-filter-registry": "^1.4.2" + "operator-filter-registry": "^1.4.2", + "solady": "^0.0.249" }, "peerDependencies": { "ethers": "^5.0.0" @@ -97,4 +98,4 @@ "prettier --write --plugin=prettier-plugin-solidity contracts" ] } -} \ No newline at end of file +} diff --git a/remappings.txt b/remappings.txt index f2a460f1..8c163056 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,4 +1,4 @@ -solady/=lib/solady/src/ +solady/=lib/solady/ solemate/=/lib/solemate/src/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ From 2668ab56630bf9bbaaf9b9c829e2e99cded7474f Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 3 Oct 2024 16:58:33 -0700 Subject: [PATCH 037/145] fixing tests Signed-off-by: Wolfy --- contracts/common/Cosignable.sol | 18 ++++++++---------- contracts/nft/erc1155m/ERC1155M.sol | 8 ++++++++ .../nft/erc1155m/ERC1155MInitializable.sol | 18 ++++++++++++++++-- contracts/nft/erc721m/ERC721CM.sol | 8 ++++++++ contracts/nft/erc721m/ERC721M.sol | 19 +++++++++++++++++-- .../erc721m/v2/ERC721CMInitializableV2.sol | 9 +++++++++ test/erc721m/ERC721CM.test.ts | 12 +++++++++--- test/erc721m/ERC721M.test.ts | 7 ++++--- test/erc721m/extensions/BucketAuction.test.ts | 1 - 9 files changed, 79 insertions(+), 21 deletions(-) diff --git a/contracts/common/Cosignable.sol b/contracts/common/Cosignable.sol index 09cd9cdc..bcaa304a 100644 --- a/contracts/common/Cosignable.sol +++ b/contracts/common/Cosignable.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {SignatureCheckerLib} from "solady/src/utils/SignatureCheckerLib.sol"; -contract Cosignable { +abstract contract Cosignable { address internal _cosigner; uint256 internal _timestampExpirySeconds; @@ -14,10 +13,8 @@ contract Cosignable { error InvalidCosignSignature(); error TimestampExpired(); - function setCosigner(address cosigner) external virtual { - _cosigner = cosigner; - emit SetCosigner(cosigner); - } + // @dev: override this function with onlyOwner modifier in the contract that uses this library + function setCosigner(address cosigner) external virtual; function setTimestampExpirySeconds(uint256 timestampExpirySeconds) external virtual { _timestampExpirySeconds = timestampExpirySeconds; @@ -33,7 +30,7 @@ contract Cosignable { if (_cosigner == address(0)) revert CosignerNotSet(); return - MessageHashUtils.toEthSignedMessageHash( + SignatureCheckerLib.toEthSignedMessageHash( keccak256( abi.encodePacked( address(this), @@ -56,8 +53,9 @@ contract Cosignable { bytes memory signature, uint256 cosignNonce ) public view returns (bool) { + if ( - SignatureChecker.isValidSignatureNow( + SignatureCheckerLib.isValidSignatureNow( _cosigner, getCosignDigest( minter, @@ -73,7 +71,7 @@ contract Cosignable { } if ( - SignatureChecker.isValidSignatureNow( + SignatureCheckerLib.isValidSignatureNow( _cosigner, getCosignDigest( minter, diff --git a/contracts/nft/erc1155m/ERC1155M.sol b/contracts/nft/erc1155m/ERC1155M.sol index 6466e7f4..97cdd193 100644 --- a/contracts/nft/erc1155m/ERC1155M.sol +++ b/contracts/nft/erc1155m/ERC1155M.sol @@ -108,6 +108,14 @@ contract ERC1155M is _removeAuthorizedMinter(minter); } + /** + * @dev Sets cosigner. Can only be called by contract owner. + */ + function setCosigner(address cosigner) external override onlyOwner { + _cosigner = cosigner; + emit SetCosigner(cosigner); + } + /** * @dev Returns cosign nonce. */ diff --git a/contracts/nft/erc1155m/ERC1155MInitializable.sol b/contracts/nft/erc1155m/ERC1155MInitializable.sol index cf38651b..be89c570 100644 --- a/contracts/nft/erc1155m/ERC1155MInitializable.sol +++ b/contracts/nft/erc1155m/ERC1155MInitializable.sol @@ -100,14 +100,28 @@ contract ERC1155MInitializable is _; } - function addAuthorizedMinter(address minter) external override onlyOwner { + /** + * @dev Add authorized minter. Can only be called by contract owner. + */ + function addAuthorizedMinter(address minter) external onlyOwner override { _addAuthorizedMinter(minter); } - function removeAuthorizedMinter(address minter) external override onlyOwner { + /** + * @dev Remove authorized minter. Can only be called by contract owner. + */ + function removeAuthorizedMinter(address minter) external onlyOwner override { _removeAuthorizedMinter(minter); } + /** + * @dev Sets cosigner. Can only be called by contract owner. + */ + function setCosigner(address cosigner) external override onlyOwner { + _cosigner = cosigner; + emit SetCosigner(cosigner); + } + /** * @dev Returns cosign nonce. */ diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index 59aaae7a..d1be6f65 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -148,6 +148,14 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi _removeAuthorizedMinter(minter); } + /** + * @dev Sets cosigner. Can only be called by contract owner. + */ + function setCosigner(address cosigner) external override onlyOwner { + _cosigner = cosigner; + emit SetCosigner(cosigner); + } + /** * @dev Sets stages in the format of an array of `MintStageInfo`. * diff --git a/contracts/nft/erc721m/ERC721M.sol b/contracts/nft/erc721m/ERC721M.sol index a0bcac0f..a84199cc 100644 --- a/contracts/nft/erc721m/ERC721M.sol +++ b/contracts/nft/erc721m/ERC721M.sol @@ -24,7 +24,14 @@ import "../../common/AuthorizedMinterControl.sol"; * - crossmint support * - anti-botting */ -contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosignable, AuthorizedMinterControl { +contract ERC721M is + IERC721M, + ERC721AQueryable, + Ownable, + ReentrancyGuard, + Cosignable, + AuthorizedMinterControl +{ using ECDSA for bytes32; using SafeERC20 for IERC20; @@ -87,10 +94,10 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosign _maxMintableSupply = maxMintableSupply; _globalWalletLimit = globalWalletLimit; _tokenURISuffix = tokenURISuffix; - _cosigner = cosigner; // ethers.constants.AddressZero for no cosigning _timestampExpirySeconds = timestampExpirySeconds; _mintCurrency = mintCurrency; FUND_RECEIVER = fundReceiver; + _cosigner = cosigner; // ethers.constants.AddressZero for no cosigning } /** @@ -146,6 +153,14 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosign _removeAuthorizedMinter(minter); } + /** + * @dev Sets cosigner. Can only be called by contract owner. + */ + function setCosigner(address cosigner) external override onlyOwner { + _cosigner = cosigner; + emit SetCosigner(cosigner); + } + /** * @dev Sets stages in the format of an array of `MintStageInfo`. * diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol index ee13ea01..fc0b4612 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol @@ -208,6 +208,15 @@ contract ERC721CMInitializableV2 is _removeAuthorizedMinter(minter); } + /** + * @dev Sets cosigner. Can only be called by contract owner. + */ + function setCosigner(address cosigner) external override onlyOwner { + _cosigner = cosigner; + emit SetCosigner(cosigner); + } + + /** * @dev Gets whether mintable. */ diff --git a/test/erc721m/ERC721CM.test.ts b/test/erc721m/ERC721CM.test.ts index f8935db4..6f56ff89 100644 --- a/test/erc721m/ERC721CM.test.ts +++ b/test/erc721m/ERC721CM.test.ts @@ -1931,7 +1931,7 @@ describe('ERC721CM', function () { await erc721cm.deployed(); const ownerConn = erc721cm.connect(owner); await expect( - ownerConn.getCosignDigest(owner.address, 1, false, 0), + ownerConn.getCosignDigest(owner.address, 1, false, 0, 0), ).to.be.revertedWith('CosignerNotSet'); // we can set the cosigner @@ -1970,12 +1970,18 @@ describe('ERC721CM', function () { false, ); await expect( - minterConn.assertValidCosign(minter.address, 1, timestamp, sig), + minterConn.assertValidCosign(minter.address, 1, timestamp, sig, 0), ).to.not.be.reverted; const invalidSig = sig + '00'; await expect( - minterConn.assertValidCosign(minter.address, 1, timestamp, invalidSig), + minterConn.assertValidCosign( + minter.address, + 1, + timestamp, + invalidSig, + 0, + ), ).to.be.revertedWith('InvalidCosignSignature'); }); }); diff --git a/test/erc721m/ERC721M.test.ts b/test/erc721m/ERC721M.test.ts index e0773ed6..72f4509f 100644 --- a/test/erc721m/ERC721M.test.ts +++ b/test/erc721m/ERC721M.test.ts @@ -1916,7 +1916,7 @@ describe('ERC721M', function () { await erc721M.deployed(); const ownerConn = erc721M.connect(owner); await expect( - ownerConn.getCosignDigest(owner.address, 1, false, 0), + ownerConn.getCosignDigest(owner.address, 1, false, 0, 0), ).to.be.revertedWith('CosignerNotSet'); // we can set the cosigner @@ -1952,14 +1952,15 @@ describe('ERC721M', function () { minter.address, timestamp, 1, + false, ); await expect( - minterConn.assertValidCosign(minter.address, 1, timestamp, sig), + minterConn.assertValidCosign(minter.address, 1, timestamp, sig, 0), ).to.not.be.reverted; const invalidSig = sig + '00'; await expect( - minterConn.assertValidCosign(minter.address, 1, timestamp, invalidSig), + minterConn.assertValidCosign(minter.address, 1, timestamp, invalidSig, 0), ).to.be.revertedWith('InvalidCosignSignature'); }); }); diff --git a/test/erc721m/extensions/BucketAuction.test.ts b/test/erc721m/extensions/BucketAuction.test.ts index 0b2995c1..e033c5cf 100644 --- a/test/erc721m/extensions/BucketAuction.test.ts +++ b/test/erc721m/extensions/BucketAuction.test.ts @@ -36,7 +36,6 @@ describe('BucketAuction', function () { await ba.deployed(); ownerConn = ba.connect(owner); - await ownerConn.setTimestampExpirySeconds(60); await ownerConn.setStages([ { price: ethers.utils.parseEther('0.1'), From 46e724f7a3f5142b5160d1651cc3b511343b2139 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 3 Oct 2024 17:03:50 -0700 Subject: [PATCH 038/145] fix Signed-off-by: Wolfy --- contracts/nft/erc721m/extensions/BucketAuction.sol | 4 ++-- test/erc721m/extensions/BucketAuction.test.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/nft/erc721m/extensions/BucketAuction.sol b/contracts/nft/erc721m/extensions/BucketAuction.sol index e8128f84..b639a9de 100644 --- a/contracts/nft/erc721m/extensions/BucketAuction.sol +++ b/contracts/nft/erc721m/extensions/BucketAuction.sol @@ -30,6 +30,7 @@ contract BucketAuction is IBucketAuction, ERC721M { uint256 maxMintableSupply, uint256 globalWalletLimit, address cosigner, + uint64 timestampExpirySeconds, uint256 minimumContributionInWei, uint64 startTimeUnixSeconds, uint64 endTimeUnixSeconds, @@ -42,8 +43,7 @@ contract BucketAuction is IBucketAuction, ERC721M { maxMintableSupply, globalWalletLimit, cosigner, - /* timestampExpirySeconds= */ - 300, + timestampExpirySeconds, /* mintCurrency= */ address(0), fundReceiver diff --git a/test/erc721m/extensions/BucketAuction.test.ts b/test/erc721m/extensions/BucketAuction.test.ts index e033c5cf..0bd12d46 100644 --- a/test/erc721m/extensions/BucketAuction.test.ts +++ b/test/erc721m/extensions/BucketAuction.test.ts @@ -28,6 +28,7 @@ describe('BucketAuction', function () { /* maxMintableSupply= */ 1000, /* globalWalletLimit= */ 0, ethers.constants.AddressZero, + 60, // timestampExpirySeconds /* minimumContributionInWei= */ 100, 0, // Placeholder; startTimeUnixSeconds will be overwritten later 1, // Placeholder; endTimeUnixSeconds will be overwritten later From 568ac4837a1af93ab495aa07a60b60035ce7ffe8 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 3 Oct 2024 17:52:47 -0700 Subject: [PATCH 039/145] cleanup Signed-off-by: Wolfy --- contracts/common/ErrorsAndEvents.sol | 6 +- .../nft/erc1155m/ERC1155MErrorsAndEvents.sol | 1 - .../nft/erc1155m/ERC1155MInitializable.sol | 3 + .../nft/erc721m/ERC721MErrorsAndEvents.sol | 3 +- .../erc721m/v2/ERC721CMInitializableV2.sol | 66 +++++++++++-------- .../erc721m/ERC721CMInitializableV2Test.t.sol | 5 +- 6 files changed, 48 insertions(+), 36 deletions(-) diff --git a/contracts/common/ErrorsAndEvents.sol b/contracts/common/ErrorsAndEvents.sol index 4b60f3b1..6a8e471f 100644 --- a/contracts/common/ErrorsAndEvents.sol +++ b/contracts/common/ErrorsAndEvents.sol @@ -3,12 +3,11 @@ pragma solidity ^0.8.20; interface ErrorsAndEvents { error CannotIncreaseMaxMintableSupply(); - // error CosignerNotSet(); error CrossmintAddressNotSet(); error CrossmintOnly(); error GlobalWalletLimitOverflow(); error InsufficientStageTimeGap(); - // error InvalidCosignSignature(); + error InsufficientBalance(); error InvalidProof(); error InvalidStage(); error InvalidStageArgsLength(); @@ -18,18 +17,15 @@ interface ErrorsAndEvents { error NotMintable(); error Mintable(); error StageSupplyExceeded(); - // error TimestampExpired(); error TransferFailed(); error WalletGlobalLimitExceeded(); error WalletStageLimitExceeded(); error WithdrawFailed(); error WrongMintCurrency(); error NotSupported(); - // error NotAuthorized(); error NewSupplyLessThanTotalSupply(); error NotTransferable(); - // event SetCosigner(address cosigner); event SetCrossmintAddress(address crossmintAddress); event SetMintable(bool mintable); event SetActiveStage(uint256 activeStage); diff --git a/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol b/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol index 80174f79..12224eed 100644 --- a/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol +++ b/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol @@ -6,7 +6,6 @@ import {ErrorsAndEvents} from "../../common/ErrorsAndEvents.sol"; interface ERC1155MErrorsAndEvents is ErrorsAndEvents { error InvalidLimitArgsLength(); error InvalidTokenId(); - error InsufficientBalance(); event UpdateStage( uint256 indexed stage, diff --git a/contracts/nft/erc1155m/ERC1155MInitializable.sol b/contracts/nft/erc1155m/ERC1155MInitializable.sol index be89c570..fa9f235b 100644 --- a/contracts/nft/erc1155m/ERC1155MInitializable.sol +++ b/contracts/nft/erc1155m/ERC1155MInitializable.sol @@ -40,6 +40,9 @@ contract ERC1155MInitializable is Cosignable, AuthorizedMinterControl { + constructor() { + _disableInitializers(); + } function initialize( string calldata name_, diff --git a/contracts/nft/erc721m/ERC721MErrorsAndEvents.sol b/contracts/nft/erc721m/ERC721MErrorsAndEvents.sol index 08b3c6d8..59014a31 100644 --- a/contracts/nft/erc721m/ERC721MErrorsAndEvents.sol +++ b/contracts/nft/erc721m/ERC721MErrorsAndEvents.sol @@ -17,4 +17,5 @@ interface ERC721MErrorsAndEvents is ErrorsAndEvents { event SetMaxMintableSupply(uint256 maxMintableSupply); event SetGlobalWalletLimit(uint256 globalWalletLimit); -} \ No newline at end of file + event SetDefaultRoyalty(address receiver, uint96 feeNumerator); +} diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol index fc0b4612..30b6f9f0 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol @@ -2,16 +2,14 @@ pragma solidity ^0.8.20; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {MerkleProofLib} from "solady/src/utils/MerkleProofLib.sol"; +import {ERC2981} from "solady/src/tokens/ERC2981.sol"; +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {ReentrancyGuard} from "solady/src/utils/ReentrancyGuard.sol"; +import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol"; import {ERC721ACQueryableInitializable, ERC721AUpgradeable, IERC721AUpgradeable} from "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; import {MINT_FEE_RECEIVER} from "../../../utils/Constants.sol"; -import {UpdatableRoyaltiesInitializable, ERC2981} from "../../../royalties/UpdatableRoyaltiesInitializable.sol"; import {MintStageInfo} from "../../../common/Structs.sol"; import {IERC721MInitializable} from "../interfaces/IERC721MInitializable.sol"; import {Cosignable} from "../../../common/Cosignable.sol"; @@ -25,14 +23,12 @@ import {AuthorizedMinterControl} from "../../../common/AuthorizedMinterControl.s contract ERC721CMInitializableV2 is IERC721MInitializable, ERC721ACQueryableInitializable, - UpdatableRoyaltiesInitializable, + ERC2981, + Ownable, ReentrancyGuard, Cosignable, AuthorizedMinterControl { - using ECDSA for bytes32; - using SafeERC20 for IERC20; - // Whether this contract is mintable. bool private _mintable; @@ -75,13 +71,17 @@ contract ERC721CMInitializableV2 is uint256 public constant VERSION = 2; - constructor(address initialOwner) Ownable(initialOwner) {} + constructor() { + _disableInitializers(); + } function initialize( string calldata name, - string calldata symbol + string calldata symbol, + address initialOwner ) external initializerERC721A initializer { __ERC721ACQueryableInitializable_init(name, symbol); + _initializeOwner(initialOwner); } function setup( @@ -451,12 +451,9 @@ contract ERC721CMInitializableV2 is // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required if (stage.merkleRoot != 0) { - if ( - MerkleProof.processProof( - proof, - keccak256(abi.encodePacked(to, limit)) - ) != stage.merkleRoot - ) revert InvalidProof(); + if (!MerkleProofLib.verify(proof, stage.merkleRoot, keccak256(abi.encodePacked(to, limit)))) { + revert InvalidProof(); + } // Verify merkle proof mint limit if ( @@ -468,8 +465,8 @@ contract ERC721CMInitializableV2 is } if (_mintCurrency != address(0)) { - // ERC20 mint payment - IERC20(_mintCurrency).safeTransferFrom( + SafeTransferLib.safeTransferFrom( + _mintCurrency, msg.sender, address(this), (stage.price + adjustedMintFee) * qty @@ -517,13 +514,18 @@ contract ERC721CMInitializableV2 is function withdrawERC20() external onlyOwner { if (_mintCurrency == address(0)) revert WrongMintCurrency(); - IERC20(_mintCurrency).safeTransfer(MINT_FEE_RECEIVER, _totalMintFee); + uint256 totalFee = _totalMintFee; + uint256 remaining = SafeTransferLib.balanceOf(_mintCurrency, address(this)); + + if (remaining < totalFee) revert InsufficientBalance(); + _totalMintFee = 0; + uint256 totalAmount = totalFee + remaining; - uint256 remaining = IERC20(_mintCurrency).balanceOf(address(this)); - IERC20(_mintCurrency).safeTransfer(_fundReceiver, remaining); + SafeTransferLib.safeTransfer(_mintCurrency, MINT_FEE_RECEIVER, totalFee); + SafeTransferLib.safeTransfer(_mintCurrency, _fundReceiver, remaining); - emit WithdrawERC20(_mintCurrency, _totalMintFee + remaining); + emit WithdrawERC20(_mintCurrency, totalAmount); } /** @@ -629,11 +631,21 @@ contract ERC721CMInitializableV2 is isViewFunction = true; } - - function supportsInterface(bytes4 interfaceId) public view override(ERC2981, IERC721AUpgradeable, ERC721ACQueryableInitializable) returns (bool) { return super.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId) || ERC721ACQueryableInitializable.supportsInterface(interfaceId); } + + function setDefaultRoyalty( + address receiver, + uint96 feeNumerator + ) public onlyOwner { + super._setDefaultRoyalty(receiver, feeNumerator); + emit SetDefaultRoyalty(receiver, feeNumerator); + } + + function _requireCallerIsContractOwner() internal view override { + return _checkOwner(); + } } diff --git a/test/erc721m/ERC721CMInitializableV2Test.t.sol b/test/erc721m/ERC721CMInitializableV2Test.t.sol index 79358889..e31b9ebb 100644 --- a/test/erc721m/ERC721CMInitializableV2Test.t.sol +++ b/test/erc721m/ERC721CMInitializableV2Test.t.sol @@ -29,12 +29,13 @@ contract ERC721CMInitializableV2Test is Test { vm.deal(minter, 2 ether); vm.deal(crossmintAddress, 1 ether); - nft = new ERC721CMInitializableV2(owner); - nft.initialize("Test", "TEST"); + nft = new ERC721CMInitializableV2(); + nft.initialize("Test", "TEST", owner); nft.setup( ".json", INITIAL_SUPPLY, GLOBAL_WALLET_LIMIT, + 60, // timestampExpirySeconds address(0), fundReceiver, crossmintAddress, From 36a9eb8760bcd57c3d065c02fed3973a525cf75e Mon Sep 17 00:00:00 2001 From: Wolfy Date: Thu, 3 Oct 2024 18:00:52 -0700 Subject: [PATCH 040/145] lint Signed-off-by: Wolfy --- contracts/nft/erc721m/ERC721CM.sol | 1 + contracts/nft/erc721m/ERC721CMInitializable.sol | 1 + contracts/nft/erc721m/ERC721M.sol | 1 + contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol | 1 + 4 files changed, 4 insertions(+) diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index d1be6f65..4b6123d4 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -616,6 +616,7 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi */ function _chainID() private view returns (uint256) { uint256 chainID; + /// @solidity memory-safe-assembly assembly { chainID := chainid() } diff --git a/contracts/nft/erc721m/ERC721CMInitializable.sol b/contracts/nft/erc721m/ERC721CMInitializable.sol index f4cc1292..f6486030 100644 --- a/contracts/nft/erc721m/ERC721CMInitializable.sol +++ b/contracts/nft/erc721m/ERC721CMInitializable.sol @@ -559,6 +559,7 @@ abstract contract ERC721CMInitializable is */ function _chainID() private view returns (uint256) { uint256 chainID; + /// @solidity memory-safe-assembly assembly { chainID := chainid() } diff --git a/contracts/nft/erc721m/ERC721M.sol b/contracts/nft/erc721m/ERC721M.sol index a84199cc..54d93b8d 100644 --- a/contracts/nft/erc721m/ERC721M.sol +++ b/contracts/nft/erc721m/ERC721M.sol @@ -601,6 +601,7 @@ contract ERC721M is */ function _chainID() private view returns (uint256) { uint256 chainID; + /// @solidity memory-safe-assembly assembly { chainID := chainid() } diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol index 30b6f9f0..008efd64 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol @@ -617,6 +617,7 @@ contract ERC721CMInitializableV2 is * @dev Returns chain id. */ function _chainID() private view returns (uint256 chainID) { + /// @solidity memory-safe-assembly assembly { chainID := chainid() } From 2d4c39fa8d8022670e041613aa2baf9571900059 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 4 Oct 2024 11:13:54 -0700 Subject: [PATCH 041/145] fix tests Signed-off-by: Wolfy --- test/erc721m/ERC721CMInitializableV2Test.t.sol | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/erc721m/ERC721CMInitializableV2Test.t.sol b/test/erc721m/ERC721CMInitializableV2Test.t.sol index e31b9ebb..f57097af 100644 --- a/test/erc721m/ERC721CMInitializableV2Test.t.sol +++ b/test/erc721m/ERC721CMInitializableV2Test.t.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +import {LibClone} from "solady/src/utils/LibClone.sol"; +import {Ownable} from "solady/src/auth/Ownable.sol"; + import {IERC721A} from "erc721a/contracts/IERC721A.sol"; import {Test} from "forge-std/Test.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC721CMInitializableV2} from "../../contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol"; import {MintStageInfo} from "../../contracts/common/Structs.sol"; import {ErrorsAndEvents} from "../../contracts/common/ErrorsAndEvents.sol"; @@ -29,7 +31,9 @@ contract ERC721CMInitializableV2Test is Test { vm.deal(minter, 2 ether); vm.deal(crossmintAddress, 1 ether); - nft = new ERC721CMInitializableV2(); + + address clone = LibClone.deployERC1967(address(new ERC721CMInitializableV2())); + nft = ERC721CMInitializableV2(clone); nft.initialize("Test", "TEST", owner); nft.setup( ".json", @@ -59,7 +63,7 @@ contract ERC721CMInitializableV2Test is Test { assertFalse(nft.getMintable()); vm.prank(readonly); - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, readonly)); + vm.expectRevert(Ownable.Unauthorized.selector); nft.setMintable(true); } @@ -74,7 +78,7 @@ contract ERC721CMInitializableV2Test is Test { assertEq(fundReceiver.balance, initialFundReceiverBalance + 100); vm.prank(readonly); - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, readonly)); + vm.expectRevert(Ownable.Unauthorized.selector); nft.withdraw(); } @@ -103,7 +107,7 @@ contract ERC721CMInitializableV2Test is Test { assertEq(nft.getNumberStages(), 2); vm.prank(readonly); - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, readonly)); + vm.expectRevert(Ownable.Unauthorized.selector); nft.setStages(stages); } From 73a2ebb5b6f6fc6648b8bab80eb6e1a9bb303734 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Fri, 4 Oct 2024 11:15:19 -0700 Subject: [PATCH 042/145] forge fmt Signed-off-by: Wolfy --- contracts/common/Cosignable.sol | 56 +--- contracts/common/ErrorsAndEvents.sol | 2 +- contracts/common/Structs.sol | 2 +- .../common/interfaces/IInitializableToken.sol | 6 +- contracts/factory/MagicDropCloneFactory.sol | 20 +- .../mocks/ERC1155MTestReentrantExploit.sol | 30 +- contracts/mocks/TestReentrantExploit.sol | 14 +- contracts/mocks/TestStaking.sol | 11 +- .../nft/MagicDropERC1155Initializable.sol | 17 +- .../nft/MagicDropERC721Initializable.sol | 10 +- .../ERC721ACQueryable.sol | 49 ++-- .../ERC721ACQueryableInitializable.sol | 58 ++-- contracts/nft/erc1155m/ERC1155M.sol | 259 ++++++------------ .../nft/erc1155m/ERC1155MErrorsAndEvents.sol | 8 +- .../nft/erc1155m/ERC1155MInitializable.sol | 259 ++++++------------ contracts/nft/erc1155m/ERC1155MStorage.sol | 6 +- .../nft/erc1155m/interfaces/IERC1155M.sol | 25 +- contracts/nft/erc721m/ERC721CM.sol | 156 ++++------- .../nft/erc721m/ERC721CMInitializable.sol | 164 ++++------- contracts/nft/erc721m/ERC721M.sol | 158 ++++------- .../nft/erc721m/extensions/BucketAuction.sol | 58 ++-- .../extensions/ERC721CMBasicRoyalties.sol | 8 +- .../erc721m/extensions/ERC721CMRoyalties.sol | 8 +- .../extensions/ERC721MOperatorFilterer.sol | 47 ++-- .../nft/erc721m/interfaces/IBucketAuction.sol | 7 +- contracts/nft/erc721m/interfaces/IERC721M.sol | 21 +- .../interfaces/IERC721MInitializable.sol | 23 +- .../erc721m/v2/ERC721CMInitializableV2.sol | 183 +++++-------- contracts/royalties/UpdatableRoyalties.sol | 17 +- .../UpdatableRoyaltiesInitializable.sol | 22 +- contracts/utils/ERC721BatchTransfer.sol | 41 +-- .../erc721m/ERC721CMInitializableV2Test.t.sol | 3 +- test/factory/MagicDropCloneFactoryTest.t.sol | 48 +--- .../MagicDropTokenImplRegistryTest.t.sol | 4 +- 34 files changed, 563 insertions(+), 1237 deletions(-) diff --git a/contracts/common/Cosignable.sol b/contracts/common/Cosignable.sol index bcaa304a..8b42a57a 100644 --- a/contracts/common/Cosignable.sol +++ b/contracts/common/Cosignable.sol @@ -20,30 +20,20 @@ abstract contract Cosignable { _timestampExpirySeconds = timestampExpirySeconds; } - function getCosignDigest( - address minter, - uint32 qty, - bool waiveMintFee, - uint64 timestamp, - uint256 cosignNonce - ) public view returns (bytes32) { + function getCosignDigest(address minter, uint32 qty, bool waiveMintFee, uint64 timestamp, uint256 cosignNonce) + public + view + returns (bytes32) + { if (_cosigner == address(0)) revert CosignerNotSet(); - return - SignatureCheckerLib.toEthSignedMessageHash( - keccak256( - abi.encodePacked( - address(this), - minter, - qty, - waiveMintFee, - _cosigner, - timestamp, - block.chainid, - cosignNonce - ) + return SignatureCheckerLib.toEthSignedMessageHash( + keccak256( + abi.encodePacked( + address(this), minter, qty, waiveMintFee, _cosigner, timestamp, block.chainid, cosignNonce ) - ); + ) + ); } function assertValidCosign( @@ -53,18 +43,9 @@ abstract contract Cosignable { bytes memory signature, uint256 cosignNonce ) public view returns (bool) { - if ( SignatureCheckerLib.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - qty, - true, - timestamp, - cosignNonce - ), - signature + _cosigner, getCosignDigest(minter, qty, true, timestamp, cosignNonce), signature ) ) { return true; @@ -72,15 +53,7 @@ abstract contract Cosignable { if ( SignatureCheckerLib.isValidSignatureNow( - _cosigner, - getCosignDigest( - minter, - qty, - false, - timestamp, - cosignNonce - ), - signature + _cosigner, getCosignDigest(minter, qty, false, timestamp, cosignNonce), signature ) ) { return false; @@ -90,7 +63,8 @@ abstract contract Cosignable { } function _assertValidTimestamp(uint64 timestamp) internal view { - if (timestamp < block.timestamp - _timestampExpirySeconds) + if (timestamp < block.timestamp - _timestampExpirySeconds) { revert TimestampExpired(); + } } } diff --git a/contracts/common/ErrorsAndEvents.sol b/contracts/common/ErrorsAndEvents.sol index 6a8e471f..a8130ab5 100644 --- a/contracts/common/ErrorsAndEvents.sol +++ b/contracts/common/ErrorsAndEvents.sol @@ -34,4 +34,4 @@ interface ErrorsAndEvents { event Withdraw(uint256 value); event WithdrawERC20(address mintCurrency, uint256 value); event SetTimestampExpirySeconds(uint64 expiry); -} \ No newline at end of file +} diff --git a/contracts/common/Structs.sol b/contracts/common/Structs.sol index 705fffba..ba99d294 100644 --- a/contracts/common/Structs.sol +++ b/contracts/common/Structs.sol @@ -24,4 +24,4 @@ struct MintStageInfo1155 { uint24[] maxStageSupply; // 0 for unlimited uint64 startTimeUnixSeconds; uint64 endTimeUnixSeconds; -} \ No newline at end of file +} diff --git a/contracts/common/interfaces/IInitializableToken.sol b/contracts/common/interfaces/IInitializableToken.sol index bc8855c7..bb33267b 100644 --- a/contracts/common/interfaces/IInitializableToken.sol +++ b/contracts/common/interfaces/IInitializableToken.sol @@ -2,9 +2,5 @@ pragma solidity ^0.8.20; interface IInitializableToken { - function initialize( - string calldata name, - string calldata symbol, - address payable initialOwner - ) external; + function initialize(string calldata name, string calldata symbol, address payable initialOwner) external; } diff --git a/contracts/factory/MagicDropCloneFactory.sol b/contracts/factory/MagicDropCloneFactory.sol index d2acac90..a1555db0 100644 --- a/contracts/factory/MagicDropCloneFactory.sol +++ b/contracts/factory/MagicDropCloneFactory.sol @@ -9,18 +9,12 @@ import {IInitializableToken} from "../common/interfaces/IInitializableToken.sol" import {TokenStandard} from "../common/Structs.sol"; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; -contract MagicDropCloneFactory is - IMagicDropCloneFactory, - Ownable2StepUpgradeable, - UUPSUpgradeable -{ +contract MagicDropCloneFactory is IMagicDropCloneFactory, Ownable2StepUpgradeable, UUPSUpgradeable { IMagicDropTokenImplRegistry public immutable REGISTRY; error ImplementationNotRegistered(); - constructor( - IMagicDropTokenImplRegistry _registry - ) { + constructor(IMagicDropTokenImplRegistry _registry) { if (address(_registry) == address(0)) { revert ConstructorRegistryAddressCannotBeZero(); } @@ -53,11 +47,7 @@ contract MagicDropCloneFactory is address instance = Clones.cloneDeterministic(impl, cloneSalt); - IInitializableToken(instance).initialize( - name, - symbol, - initialOwner - ); + IInitializableToken(instance).initialize(name, symbol, initialOwner); emit NewContractInitialized({ contractAddress: instance, @@ -71,9 +61,7 @@ contract MagicDropCloneFactory is return instance; } - function _authorizeUpgrade( - address newImplementation - ) internal override onlyOwner { + function _authorizeUpgrade(address newImplementation) internal override onlyOwner { // This function is left empty as the onlyOwner modifier handles the authorization } } diff --git a/contracts/mocks/ERC1155MTestReentrantExploit.sol b/contracts/mocks/ERC1155MTestReentrantExploit.sol index 2f0129cc..36773acf 100644 --- a/contracts/mocks/ERC1155MTestReentrantExploit.sol +++ b/contracts/mocks/ERC1155MTestReentrantExploit.sol @@ -11,35 +11,13 @@ contract ERC1155MTestReentrantExploit { _targetContract = target; } - function exploit( - uint256 tokenId, - uint32 qty, - bytes32[] calldata proof - ) public payable { - ERC1155M(_targetContract).mint{value: msg.value}( - tokenId, - qty, - proof, - 0, - bytes("0") - ); + function exploit(uint256 tokenId, uint32 qty, bytes32[] calldata proof) public payable { + ERC1155M(_targetContract).mint{value: msg.value}(tokenId, qty, proof, 0, bytes("0")); } - function onERC1155Received( - address, - address, - uint256, - uint256, - bytes calldata - ) public payable returns (bytes4) { + function onERC1155Received(address, address, uint256, uint256, bytes calldata) public payable returns (bytes4) { bytes32[] memory proof; - ERC1155M(_targetContract).mint{value: msg.value}( - 0, - 1, - proof, - 0, - bytes("0") - ); + ERC1155M(_targetContract).mint{value: msg.value}(0, 1, proof, 0, bytes("0")); return IERC1155Receiver.onERC1155Received.selector; } } diff --git a/contracts/mocks/TestReentrantExploit.sol b/contracts/mocks/TestReentrantExploit.sol index 080dca17..37b6960a 100644 --- a/contracts/mocks/TestReentrantExploit.sol +++ b/contracts/mocks/TestReentrantExploit.sol @@ -10,18 +10,8 @@ contract TestReentrantExploit { _targetContract = target; } - function exploit( - uint32 qty, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) public payable { - ERC721M(_targetContract).mint{value: msg.value}( - qty, - proof, - timestamp, - signature - ); + function exploit(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) public payable { + ERC721M(_targetContract).mint{value: msg.value}(qty, proof, timestamp, signature); } function onERC721Received( diff --git a/contracts/mocks/TestStaking.sol b/contracts/mocks/TestStaking.sol index 46f1337e..523937ca 100644 --- a/contracts/mocks/TestStaking.sol +++ b/contracts/mocks/TestStaking.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; contract TestStaking { using EnumerableSet for EnumerableSet.UintSet; + IERC721A private _nft; mapping(address => EnumerableSet.UintSet) private _stakers; @@ -13,10 +14,7 @@ contract TestStaking { _nft = IERC721A(nft); } - function isStaked( - address staker, - uint256 tokenId - ) public view returns (bool) { + function isStaked(address staker, uint256 tokenId) public view returns (bool) { return _stakers[staker].contains(tokenId); } @@ -26,10 +24,7 @@ contract TestStaking { } function stakeFor(address staker, uint256 tokenId) public { - require( - msg.sender == address(_nft), - "Only NFT contract can stake on behalf" - ); + require(msg.sender == address(_nft), "Only NFT contract can stake on behalf"); _nft.transferFrom(staker, address(this), tokenId); _stakers[staker].add(tokenId); } diff --git a/contracts/nft/MagicDropERC1155Initializable.sol b/contracts/nft/MagicDropERC1155Initializable.sol index 26c93997..e642bc77 100644 --- a/contracts/nft/MagicDropERC1155Initializable.sol +++ b/contracts/nft/MagicDropERC1155Initializable.sol @@ -9,11 +9,11 @@ contract MagicDropERC1155Initializable is ERC1155Upgradeable, OwnableUpgradeable string private _name; string private _symbol; - function initialize( - string calldata name_, - string calldata symbol_, - address payable initialOwner - ) external initializer override { + function initialize(string calldata name_, string calldata symbol_, address payable initialOwner) + external + override + initializer + { _name = name_; _symbol = symbol_; __ERC1155_init(""); @@ -32,12 +32,7 @@ contract MagicDropERC1155Initializable is ERC1155Upgradeable, OwnableUpgradeable _setURI(newuri); } - function mint( - address to, - uint256 id, - uint256 amount, - bytes memory data - ) external { + function mint(address to, uint256 id, uint256 amount, bytes memory data) external { _mint(to, id, amount, data); } } diff --git a/contracts/nft/MagicDropERC721Initializable.sol b/contracts/nft/MagicDropERC721Initializable.sol index d71385c3..4cab9f9b 100644 --- a/contracts/nft/MagicDropERC721Initializable.sol +++ b/contracts/nft/MagicDropERC721Initializable.sol @@ -8,11 +8,11 @@ import {IInitializableToken} from "../common/interfaces/IInitializableToken.sol" contract MagicDropERC721Initializable is ERC721Upgradeable, OwnableUpgradeable, IInitializableToken { string private baseURI = ""; - function initialize( - string memory name, - string memory symbol, - address payable initialOwner - ) external initializer override { + function initialize(string memory name, string memory symbol, address payable initialOwner) + external + override + initializer + { __ERC721_init(name, symbol); __Ownable_init(initialOwner); } diff --git a/contracts/nft/creator-token-standards/ERC721ACQueryable.sol b/contracts/nft/creator-token-standards/ERC721ACQueryable.sol index 6110c284..03923056 100644 --- a/contracts/nft/creator-token-standards/ERC721ACQueryable.sol +++ b/contracts/nft/creator-token-standards/ERC721ACQueryable.sol @@ -8,27 +8,19 @@ import "erc721a/contracts/extensions/ERC721AQueryable.sol"; * @title ERC721ACQueryable */ abstract contract ERC721ACQueryable is ERC721AQueryable, CreatorTokenBase { - constructor( - string memory name_, - string memory symbol_ - ) CreatorTokenBase() ERC721A(name_, symbol_) {} + constructor(string memory name_, string memory symbol_) CreatorTokenBase() ERC721A(name_, symbol_) {} - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC721A, IERC721A) returns (bool) { - return - interfaceId == type(ICreatorToken).interfaceId || - ERC721A.supportsInterface(interfaceId); + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721A, IERC721A) returns (bool) { + return interfaceId == type(ICreatorToken).interfaceId || ERC721A.supportsInterface(interfaceId); } /// @dev Ties the erc721a _beforeTokenTransfers hook to more granular transfer validation logic - function _beforeTokenTransfers( - address from, - address to, - uint256 startTokenId, - uint256 quantity - ) internal virtual override { - for (uint256 i = 0; i < quantity; ) { + function _beforeTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity) + internal + virtual + override + { + for (uint256 i = 0; i < quantity;) { _validateBeforeTransfer(from, to, startTokenId + i); unchecked { ++i; @@ -37,13 +29,12 @@ abstract contract ERC721ACQueryable is ERC721AQueryable, CreatorTokenBase { } /// @dev Ties the erc721a _afterTokenTransfer hook to more granular transfer validation logic - function _afterTokenTransfers( - address from, - address to, - uint256 startTokenId, - uint256 quantity - ) internal virtual override { - for (uint256 i = 0; i < quantity; ) { + function _afterTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity) + internal + virtual + override + { + for (uint256 i = 0; i < quantity;) { _validateAfterTransfer(from, to, startTokenId + i); unchecked { ++i; @@ -51,17 +42,11 @@ abstract contract ERC721ACQueryable is ERC721AQueryable, CreatorTokenBase { } } - function _msgSenderERC721A() - internal - view - virtual - override - returns (address) - { + function _msgSenderERC721A() internal view virtual override returns (address) { return _msgSender(); } - function _tokenType() internal pure override returns(uint16) { + function _tokenType() internal pure override returns (uint16) { return uint16(721); } } diff --git a/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol b/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol index 77560b25..d4039bc0 100644 --- a/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol +++ b/contracts/nft/creator-token-standards/ERC721ACQueryableInitializable.sol @@ -10,41 +10,32 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this * contract is for use with EIP-1167 Minimal Proxies (Clones). */ -abstract contract ERC721ACQueryableInitializable is - ERC721AQueryableUpgradeable, - CreatorTokenBase, - Initializable -{ - function __ERC721ACQueryableInitializable_init( - string memory name_, - string memory symbol_ - ) public onlyInitializing { +abstract contract ERC721ACQueryableInitializable is ERC721AQueryableUpgradeable, CreatorTokenBase, Initializable { + function __ERC721ACQueryableInitializable_init(string memory name_, string memory symbol_) + public + onlyInitializing + { __ERC721A_init_unchained(name_, symbol_); __ERC721AQueryable_init_unchained(); } - function supportsInterface( - bytes4 interfaceId - ) + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721AUpgradeable, IERC721AUpgradeable) returns (bool) { - return - interfaceId == type(ICreatorToken).interfaceId || - ERC721AUpgradeable.supportsInterface(interfaceId); + return interfaceId == type(ICreatorToken).interfaceId || ERC721AUpgradeable.supportsInterface(interfaceId); } /// @dev Ties the erc721a _beforeTokenTransfers hook to more granular transfer validation logic - function _beforeTokenTransfers( - address from, - address to, - uint256 startTokenId, - uint256 quantity - ) internal virtual override { - for (uint256 i = 0; i < quantity; ) { + function _beforeTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity) + internal + virtual + override + { + for (uint256 i = 0; i < quantity;) { _validateBeforeTransfer(from, to, startTokenId + i); unchecked { ++i; @@ -53,13 +44,12 @@ abstract contract ERC721ACQueryableInitializable is } /// @dev Ties the erc721a _afterTokenTransfer hook to more granular transfer validation logic - function _afterTokenTransfers( - address from, - address to, - uint256 startTokenId, - uint256 quantity - ) internal virtual override { - for (uint256 i = 0; i < quantity; ) { + function _afterTokenTransfers(address from, address to, uint256 startTokenId, uint256 quantity) + internal + virtual + override + { + for (uint256 i = 0; i < quantity;) { _validateAfterTransfer(from, to, startTokenId + i); unchecked { ++i; @@ -67,17 +57,11 @@ abstract contract ERC721ACQueryableInitializable is } } - function _msgSenderERC721A() - internal - view - virtual - override - returns (address) - { + function _msgSenderERC721A() internal view virtual override returns (address) { return _msgSender(); } - function _tokenType() internal pure override returns(uint16) { + function _tokenType() internal pure override returns (uint16) { return uint16(721); } } diff --git a/contracts/nft/erc1155m/ERC1155M.sol b/contracts/nft/erc1155m/ERC1155M.sol index 97cdd193..8bd067c5 100644 --- a/contracts/nft/erc1155m/ERC1155M.sol +++ b/contracts/nft/erc1155m/ERC1155M.sol @@ -60,10 +60,7 @@ contract ERC1155M is } for (uint256 i = 0; i < globalWalletLimit.length; i++) { - if ( - maxMintableSupply[i] > 0 && - globalWalletLimit[i] > maxMintableSupply[i] - ) { + if (maxMintableSupply[i] > 0 && globalWalletLimit[i] > maxMintableSupply[i]) { revert GlobalWalletLimitOverflow(); } } @@ -87,24 +84,23 @@ contract ERC1155M is * @dev Returns whether it has enough supply for the given qty. */ modifier hasSupply(uint256 tokenId, uint256 qty) { - if ( - _maxMintableSupply[tokenId] > 0 && - totalSupply(tokenId) + qty > _maxMintableSupply[tokenId] - ) revert NoSupplyLeft(); + if (_maxMintableSupply[tokenId] > 0 && totalSupply(tokenId) + qty > _maxMintableSupply[tokenId]) { + revert NoSupplyLeft(); + } _; } /** * @dev Add authorized minter. Can only be called by contract owner. */ - function addAuthorizedMinter(address minter) external onlyOwner override { + function addAuthorizedMinter(address minter) external override onlyOwner { _addAuthorizedMinter(minter); } /** * @dev Remove authorized minter. Can only be called by contract owner. */ - function removeAuthorizedMinter(address minter) external onlyOwner override { + function removeAuthorizedMinter(address minter) external override onlyOwner { _removeAuthorizedMinter(minter); } @@ -119,10 +115,7 @@ contract ERC1155M is /** * @dev Returns cosign nonce. */ - function getCosignNonce( - address minter, - uint256 tokenId - ) public view returns (uint256) { + function getCosignNonce(address minter, uint256 tokenId) public view returns (uint256) { return totalMintedByAddress(minter)[tokenId]; } @@ -154,18 +147,12 @@ contract ERC1155M is for (uint256 i = 0; i < newStages.length; i++) { if (i >= 1) { - if ( - newStages[i].startTimeUnixSeconds < - newStages[i - 1].endTimeUnixSeconds + - TIMESTAMP_EXPIRY_SECONDS - ) { + if (newStages[i].startTimeUnixSeconds < newStages[i - 1].endTimeUnixSeconds + TIMESTAMP_EXPIRY_SECONDS) + { revert InsufficientStageTimeGap(); } } - _assertValidStartAndEndTimestamp( - newStages[i].startTimeUnixSeconds, - newStages[i].endTimeUnixSeconds - ); + _assertValidStartAndEndTimestamp(newStages[i].startTimeUnixSeconds, newStages[i].endTimeUnixSeconds); _assertValidStageArgsLength(newStages[i]); _mintStages.push( @@ -195,26 +182,18 @@ contract ERC1155M is /** * @dev Returns maximum mintable supply per token. */ - function getMaxMintableSupply( - uint256 tokenId - ) external view override returns (uint256) { + function getMaxMintableSupply(uint256 tokenId) external view override returns (uint256) { return _maxMintableSupply[tokenId]; } /** * @dev Sets maximum mintable supply. New supply cannot be larger than the old or the supply alraedy minted. */ - function setMaxMintableSupply( - uint256 tokenId, - uint256 maxMintableSupply - ) external virtual onlyOwner { + function setMaxMintableSupply(uint256 tokenId, uint256 maxMintableSupply) external virtual onlyOwner { if (tokenId >= _numTokens) { revert InvalidTokenId(); } - if ( - _maxMintableSupply[tokenId] != 0 && - maxMintableSupply > _maxMintableSupply[tokenId] - ) { + if (_maxMintableSupply[tokenId] != 0 && maxMintableSupply > _maxMintableSupply[tokenId]) { revert CannotIncreaseMaxMintableSupply(); } if (maxMintableSupply < totalSupply(tokenId)) { @@ -227,26 +206,18 @@ contract ERC1155M is /** * @dev Returns global wallet limit. This is the max number of tokens can be minted by one wallet. */ - function getGlobalWalletLimit( - uint256 tokenId - ) external view override returns (uint256) { + function getGlobalWalletLimit(uint256 tokenId) external view override returns (uint256) { return _globalWalletLimit[tokenId]; } /** * @dev Sets global wallet limit. */ - function setGlobalWalletLimit( - uint256 tokenId, - uint256 globalWalletLimit - ) external onlyOwner { + function setGlobalWalletLimit(uint256 tokenId, uint256 globalWalletLimit) external onlyOwner { if (tokenId >= _numTokens) { revert InvalidTokenId(); } - if ( - _maxMintableSupply[tokenId] > 0 && - globalWalletLimit > _maxMintableSupply[tokenId] - ) { + if (_maxMintableSupply[tokenId] > 0 && globalWalletLimit > _maxMintableSupply[tokenId]) { revert GlobalWalletLimitOverflow(); } _globalWalletLimit[tokenId] = globalWalletLimit; @@ -256,16 +227,12 @@ contract ERC1155M is /** * @dev Returns number of minted tokens for a given address. */ - function totalMintedByAddress( - address account - ) public view virtual override returns (uint256[] memory) { + function totalMintedByAddress(address account) public view virtual override returns (uint256[] memory) { uint256[] memory totalMinted = new uint256[](_numTokens); uint256 numStages = _mintStages.length; for (uint256 token = 0; token < _numTokens; token++) { for (uint256 stage = 0; stage < numStages; stage++) { - totalMinted[token] += _stageMintedCountsPerTokenPerWallet[ - stage - ][token][account]; + totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][token][account]; } } return totalMinted; @@ -274,16 +241,11 @@ contract ERC1155M is /** * @dev Returns number of minted token for a given token and address. */ - function _totalMintedByTokenByAddress( - address account, - uint256 tokenId - ) internal view virtual returns (uint256) { + function _totalMintedByTokenByAddress(address account, uint256 tokenId) internal view virtual returns (uint256) { uint256 totalMinted = 0; uint256 numStages = _mintStages.length; for (uint256 i = 0; i < numStages; i++) { - totalMinted += _stageMintedCountsPerTokenPerWallet[i][tokenId][ - account - ]; + totalMinted += _stageMintedCountsPerTokenPerWallet[i][tokenId][account]; } return totalMinted; } @@ -291,15 +253,15 @@ contract ERC1155M is /** * @dev Returns number of minted tokens for a given stage and address. */ - function _totalMintedByStageByAddress( - uint256 stage, - address account - ) internal view virtual returns (uint256[] memory) { + function _totalMintedByStageByAddress(uint256 stage, address account) + internal + view + virtual + returns (uint256[] memory) + { uint256[] memory totalMinted = new uint256[](_numTokens); for (uint256 token = 0; token < _numTokens; token++) { - totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][ - token - ][account]; + totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][token][account]; } return totalMinted; } @@ -314,9 +276,7 @@ contract ERC1155M is /** * @dev Returns info for one stage specified by stage index (starting from 0). */ - function getStageInfo( - uint256 stage - ) + function getStageInfo(uint256 stage) external view override @@ -326,10 +286,7 @@ contract ERC1155M is revert InvalidStage(); } uint256[] memory walletMinted = totalMintedByAddress(msg.sender); - uint256[] memory stageMinted = _totalMintedByStageByAddress( - stage, - msg.sender - ); + uint256[] memory stageMinted = _totalMintedByStageByAddress(stage, msg.sender); return (_mintStages[stage], walletMinted, stageMinted); } @@ -349,13 +306,12 @@ contract ERC1155M is * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner */ - function mint( - uint256 tokenId, - uint32 qty, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { + function mint(uint256 tokenId, uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable + virtual + nonReentrant + { _mintInternal(msg.sender, tokenId, qty, 0, proof, timestamp, signature); } @@ -377,15 +333,7 @@ contract ERC1155M is uint64 timestamp, bytes calldata signature ) external payable virtual nonReentrant { - _mintInternal( - msg.sender, - tokenId, - qty, - limit, - proof, - timestamp, - signature - ); + _mintInternal(msg.sender, tokenId, qty, limit, proof, timestamp, signature); } /** @@ -397,13 +345,11 @@ contract ERC1155M is * limit - limit for the given minter * proof - the merkle proof generated on client side. This applies if using whitelist */ - function authorizedMint( - address to, - uint256 tokenId, - uint32 qty, - uint32 limit, - bytes32[] calldata proof - ) external payable onlyAuthorizedMinter { + function authorizedMint(address to, uint256 tokenId, uint32 qty, uint32 limit, bytes32[] calldata proof) + external + payable + onlyAuthorizedMinter + { _mintInternal(to, tokenId, qty, limit, proof, 0, bytes("0")); } @@ -423,13 +369,7 @@ contract ERC1155M is bool waiveMintFee = false; if (_cosigner != address(0)) { - waiveMintFee = assertValidCosign( - msg.sender, - qty, - timestamp, - signature, - getCosignNonce(msg.sender, tokenId) - ); + waiveMintFee = assertValidCosign(msg.sender, qty, timestamp, signature, getCosignNonce(msg.sender, tokenId)); _assertValidTimestamp(timestamp); stageTimestamp = timestamp; } @@ -440,52 +380,39 @@ contract ERC1155M is uint80 adjustedMintFee = waiveMintFee ? 0 : stage.mintFee[tokenId]; // Check value if minting with ETH - if ( - _mintCurrency == address(0) && - msg.value < (stage.price[tokenId] + adjustedMintFee) * qty - ) revert NotEnoughValue(); + if (_mintCurrency == address(0) && msg.value < (stage.price[tokenId] + adjustedMintFee) * qty) { + revert NotEnoughValue(); + } // Check stage supply if applicable if (stage.maxStageSupply[tokenId] > 0) { - if ( - _stageMintedCountsPerToken[activeStage][tokenId] + qty > - stage.maxStageSupply[tokenId] - ) revert StageSupplyExceeded(); + if (_stageMintedCountsPerToken[activeStage][tokenId] + qty > stage.maxStageSupply[tokenId]) { + revert StageSupplyExceeded(); + } } // Check global wallet limit if applicable if (_globalWalletLimit[tokenId] > 0) { - if ( - _totalMintedByTokenByAddress(to, tokenId) + qty > - _globalWalletLimit[tokenId] - ) revert WalletGlobalLimitExceeded(); + if (_totalMintedByTokenByAddress(to, tokenId) + qty > _globalWalletLimit[tokenId]) { + revert WalletGlobalLimitExceeded(); + } } // Check wallet limit for stage if applicable, limit == 0 means no limit enforced if (stage.walletLimit[tokenId] > 0) { - if ( - _stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] + - qty > - stage.walletLimit[tokenId] - ) revert WalletStageLimitExceeded(); + if (_stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] + qty > stage.walletLimit[tokenId]) { + revert WalletStageLimitExceeded(); + } } // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required if (stage.merkleRoot[tokenId] != 0) { - if ( - MerkleProof.processProof( - proof, - keccak256(abi.encodePacked(to, limit)) - ) != stage.merkleRoot[tokenId] - ) revert InvalidProof(); + if (MerkleProof.processProof(proof, keccak256(abi.encodePacked(to, limit))) != stage.merkleRoot[tokenId]) { + revert InvalidProof(); + } // Verify merkle proof mint limit - if ( - limit > 0 && - _stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] + - qty > - limit - ) { + if (limit > 0 && _stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] + qty > limit) { revert WalletStageLimitExceeded(); } } @@ -493,9 +420,7 @@ contract ERC1155M is if (_mintCurrency != address(0)) { // ERC20 mint payment IERC20(_mintCurrency).safeTransferFrom( - msg.sender, - address(this), - (stage.price[tokenId] + adjustedMintFee) * qty + msg.sender, address(this), (stage.price[tokenId] + adjustedMintFee) * qty ); } @@ -511,11 +436,7 @@ contract ERC1155M is * NOTE: This function bypasses validations thus only available for owner. * This is typically used for owner to pre-mint or mint the remaining of the supply. */ - function ownerMint( - address to, - uint256 tokenId, - uint32 qty - ) external onlyOwner hasSupply(tokenId, qty) { + function ownerMint(address to, uint256 tokenId, uint32 qty) external onlyOwner hasSupply(tokenId, qty) { _mint(to, tokenId, qty, ""); } @@ -523,12 +444,12 @@ contract ERC1155M is * @dev Withdraws funds by owner. */ function withdraw() external onlyOwner { - (bool success, ) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); + (bool success,) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); if (!success) revert TransferFailed(); _totalMintFee = 0; uint256 remainingValue = address(this).balance; - (success, ) = _fundReceiver.call{value: remainingValue}(""); + (success,) = _fundReceiver.call{value: remainingValue}(""); if (!success) revert WithdrawFailed(); emit Withdraw(_totalMintFee + remainingValue); @@ -568,14 +489,9 @@ contract ERC1155M is /** * @dev Returns the current active stage based on timestamp. */ - function getActiveStageFromTimestamp( - uint64 timestamp - ) public view returns (uint256) { + function getActiveStageFromTimestamp(uint64 timestamp) public view returns (uint256) { for (uint256 i = 0; i < _mintStages.length; i++) { - if ( - timestamp >= _mintStages[i].startTimeUnixSeconds && - timestamp < _mintStages[i].endTimeUnixSeconds - ) { + if (timestamp >= _mintStages[i].startTimeUnixSeconds && timestamp < _mintStages[i].endTimeUnixSeconds) { return i; } } @@ -585,10 +501,7 @@ contract ERC1155M is /** * @dev Set default royalty for all tokens */ - function setDefaultRoyalty( - address receiver, - uint96 feeNumerator - ) public onlyOwner { + function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { super._setDefaultRoyalty(receiver, feeNumerator); emit DefaultRoyaltySet(receiver, feeNumerator); } @@ -596,32 +509,23 @@ contract ERC1155M is /** * @dev Set default royalty for individual token */ - function setTokenRoyalty( - uint256 tokenId, - address receiver, - uint96 feeNumerator - ) public onlyOwner { + function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) public onlyOwner { super._setTokenRoyalty(tokenId, receiver, feeNumerator); emit TokenRoyaltySet(tokenId, receiver, feeNumerator); } - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC2981, ERC1155) returns (bool) { - return - ERC1155.supportsInterface(interfaceId) || - ERC2981.supportsInterface(interfaceId); + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC2981, ERC1155) returns (bool) { + return ERC1155.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); } /** * @dev The hook of token transfer to validate the transfer. */ - function _update( - address from, - address to, - uint256[] memory ids, - uint256[] memory values - ) internal virtual override { + function _update(address from, address to, uint256[] memory ids, uint256[] memory values) + internal + virtual + override + { super._update(from, to, ids, values); bool fromZeroAddress = from == address(0); @@ -635,22 +539,15 @@ contract ERC1155M is /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ - function _assertValidStartAndEndTimestamp( - uint64 start, - uint64 end - ) internal pure { + function _assertValidStartAndEndTimestamp(uint64 start, uint64 end) internal pure { if (start >= end) revert InvalidStartAndEndTimestamp(); } - function _assertValidStageArgsLength( - MintStageInfo1155 calldata stageInfo - ) internal view { + function _assertValidStageArgsLength(MintStageInfo1155 calldata stageInfo) internal view { if ( - stageInfo.price.length != _numTokens || - stageInfo.mintFee.length != _numTokens || - stageInfo.walletLimit.length != _numTokens || - stageInfo.merkleRoot.length != _numTokens || - stageInfo.maxStageSupply.length != _numTokens + stageInfo.price.length != _numTokens || stageInfo.mintFee.length != _numTokens + || stageInfo.walletLimit.length != _numTokens || stageInfo.merkleRoot.length != _numTokens + || stageInfo.maxStageSupply.length != _numTokens ) { revert InvalidStageArgsLength(); } diff --git a/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol b/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol index 12224eed..154ae7d6 100644 --- a/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol +++ b/contracts/nft/erc1155m/ERC1155MErrorsAndEvents.sol @@ -22,9 +22,5 @@ interface ERC1155MErrorsAndEvents is ErrorsAndEvents { event SetTransferable(bool transferable); event DefaultRoyaltySet(address indexed receiver, uint96 feeNumerator); - event TokenRoyaltySet( - uint256 indexed tokenId, - address indexed receiver, - uint96 feeNumerator - ); -} \ No newline at end of file + event TokenRoyaltySet(uint256 indexed tokenId, address indexed receiver, uint96 feeNumerator); +} diff --git a/contracts/nft/erc1155m/ERC1155MInitializable.sol b/contracts/nft/erc1155m/ERC1155MInitializable.sol index fa9f235b..3f746619 100644 --- a/contracts/nft/erc1155m/ERC1155MInitializable.sol +++ b/contracts/nft/erc1155m/ERC1155MInitializable.sol @@ -19,7 +19,6 @@ import {ERC1155MStorage} from "./ERC1155MStorage.sol"; import {Cosignable} from "../../common/Cosignable.sol"; import {AuthorizedMinterControl} from "../../common/AuthorizedMinterControl.sol"; - /** * @title ERC1155MInitializable * @@ -44,11 +43,7 @@ contract ERC1155MInitializable is _disableInitializers(); } - function initialize( - string calldata name_, - string calldata symbol_, - address initialOwner - ) external initializer { + function initialize(string calldata name_, string calldata symbol_, address initialOwner) external initializer { name = name_; symbol = symbol_; __ERC1155_init(""); @@ -71,10 +66,7 @@ contract ERC1155MInitializable is } for (uint256 i = 0; i < globalWalletLimit.length; i++) { - if ( - maxMintableSupply[i] > 0 && - globalWalletLimit[i] > maxMintableSupply[i] - ) { + if (maxMintableSupply[i] > 0 && globalWalletLimit[i] > maxMintableSupply[i]) { revert GlobalWalletLimitOverflow(); } } @@ -87,7 +79,7 @@ contract ERC1155MInitializable is _mintCurrency = mintCurrency; _fundReceiver = fundReceiver; _timestampExpirySeconds = timestampExpirySeconds; - + _setURI(uri_); setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); } @@ -96,24 +88,23 @@ contract ERC1155MInitializable is * @dev Returns whether it has enough supply for the given qty. */ modifier hasSupply(uint256 tokenId, uint256 qty) { - if ( - _maxMintableSupply[tokenId] > 0 && - totalSupply(tokenId) + qty > _maxMintableSupply[tokenId] - ) revert NoSupplyLeft(); + if (_maxMintableSupply[tokenId] > 0 && totalSupply(tokenId) + qty > _maxMintableSupply[tokenId]) { + revert NoSupplyLeft(); + } _; } /** * @dev Add authorized minter. Can only be called by contract owner. */ - function addAuthorizedMinter(address minter) external onlyOwner override { + function addAuthorizedMinter(address minter) external override onlyOwner { _addAuthorizedMinter(minter); } /** * @dev Remove authorized minter. Can only be called by contract owner. */ - function removeAuthorizedMinter(address minter) external onlyOwner override { + function removeAuthorizedMinter(address minter) external override onlyOwner { _removeAuthorizedMinter(minter); } @@ -128,10 +119,7 @@ contract ERC1155MInitializable is /** * @dev Returns cosign nonce. */ - function getCosignNonce( - address minter, - uint256 tokenId - ) public view returns (uint256) { + function getCosignNonce(address minter, uint256 tokenId) public view returns (uint256) { return totalMintedByAddress(minter)[tokenId]; } @@ -143,18 +131,11 @@ contract ERC1155MInitializable is for (uint256 i = 0; i < newStages.length; i++) { if (i >= 1) { - if ( - newStages[i].startTimeUnixSeconds < - newStages[i - 1].endTimeUnixSeconds + - _timestampExpirySeconds - ) { + if (newStages[i].startTimeUnixSeconds < newStages[i - 1].endTimeUnixSeconds + _timestampExpirySeconds) { revert InsufficientStageTimeGap(); } } - _assertValidStartAndEndTimestamp( - newStages[i].startTimeUnixSeconds, - newStages[i].endTimeUnixSeconds - ); + _assertValidStartAndEndTimestamp(newStages[i].startTimeUnixSeconds, newStages[i].endTimeUnixSeconds); _assertValidStageArgsLength(newStages[i]); _mintStages.push( @@ -184,26 +165,18 @@ contract ERC1155MInitializable is /** * @dev Returns maximum mintable supply per token. */ - function getMaxMintableSupply( - uint256 tokenId - ) external view override returns (uint256) { + function getMaxMintableSupply(uint256 tokenId) external view override returns (uint256) { return _maxMintableSupply[tokenId]; } /** * @dev Sets maximum mintable supply. New supply cannot be larger than the old or the supply alraedy minted. */ - function setMaxMintableSupply( - uint256 tokenId, - uint256 maxMintableSupply - ) external virtual onlyOwner { + function setMaxMintableSupply(uint256 tokenId, uint256 maxMintableSupply) external virtual onlyOwner { if (tokenId >= _numTokens) { revert InvalidTokenId(); } - if ( - _maxMintableSupply[tokenId] != 0 && - maxMintableSupply > _maxMintableSupply[tokenId] - ) { + if (_maxMintableSupply[tokenId] != 0 && maxMintableSupply > _maxMintableSupply[tokenId]) { revert CannotIncreaseMaxMintableSupply(); } if (maxMintableSupply < totalSupply(tokenId)) { @@ -216,26 +189,18 @@ contract ERC1155MInitializable is /** * @dev Returns global wallet limit. This is the max number of tokens can be minted by one wallet. */ - function getGlobalWalletLimit( - uint256 tokenId - ) external view override returns (uint256) { + function getGlobalWalletLimit(uint256 tokenId) external view override returns (uint256) { return _globalWalletLimit[tokenId]; } /** * @dev Sets global wallet limit. */ - function setGlobalWalletLimit( - uint256 tokenId, - uint256 globalWalletLimit - ) external onlyOwner { + function setGlobalWalletLimit(uint256 tokenId, uint256 globalWalletLimit) external onlyOwner { if (tokenId >= _numTokens) { revert InvalidTokenId(); } - if ( - _maxMintableSupply[tokenId] > 0 && - globalWalletLimit > _maxMintableSupply[tokenId] - ) { + if (_maxMintableSupply[tokenId] > 0 && globalWalletLimit > _maxMintableSupply[tokenId]) { revert GlobalWalletLimitOverflow(); } _globalWalletLimit[tokenId] = globalWalletLimit; @@ -245,16 +210,12 @@ contract ERC1155MInitializable is /** * @dev Returns number of minted tokens for a given address. */ - function totalMintedByAddress( - address account - ) public view virtual override returns (uint256[] memory) { + function totalMintedByAddress(address account) public view virtual override returns (uint256[] memory) { uint256[] memory totalMinted = new uint256[](_numTokens); uint256 numStages = _mintStages.length; for (uint256 token = 0; token < _numTokens; token++) { for (uint256 stage = 0; stage < numStages; stage++) { - totalMinted[token] += _stageMintedCountsPerTokenPerWallet[ - stage - ][token][account]; + totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][token][account]; } } return totalMinted; @@ -263,16 +224,11 @@ contract ERC1155MInitializable is /** * @dev Returns number of minted token for a given token and address. */ - function _totalMintedByTokenByAddress( - address account, - uint256 tokenId - ) internal view virtual returns (uint256) { + function _totalMintedByTokenByAddress(address account, uint256 tokenId) internal view virtual returns (uint256) { uint256 totalMinted = 0; uint256 numStages = _mintStages.length; for (uint256 i = 0; i < numStages; i++) { - totalMinted += _stageMintedCountsPerTokenPerWallet[i][tokenId][ - account - ]; + totalMinted += _stageMintedCountsPerTokenPerWallet[i][tokenId][account]; } return totalMinted; } @@ -280,15 +236,15 @@ contract ERC1155MInitializable is /** * @dev Returns number of minted tokens for a given stage and address. */ - function _totalMintedByStageByAddress( - uint256 stage, - address account - ) internal view virtual returns (uint256[] memory) { + function _totalMintedByStageByAddress(uint256 stage, address account) + internal + view + virtual + returns (uint256[] memory) + { uint256[] memory totalMinted = new uint256[](_numTokens); for (uint256 token = 0; token < _numTokens; token++) { - totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][ - token - ][account]; + totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][token][account]; } return totalMinted; } @@ -303,9 +259,7 @@ contract ERC1155MInitializable is /** * @dev Returns info for one stage specified by stage index (starting from 0). */ - function getStageInfo( - uint256 stage - ) + function getStageInfo(uint256 stage) external view override @@ -315,10 +269,7 @@ contract ERC1155MInitializable is revert InvalidStage(); } uint256[] memory walletMinted = totalMintedByAddress(msg.sender); - uint256[] memory stageMinted = _totalMintedByStageByAddress( - stage, - msg.sender - ); + uint256[] memory stageMinted = _totalMintedByStageByAddress(stage, msg.sender); return (_mintStages[stage], walletMinted, stageMinted); } @@ -338,13 +289,12 @@ contract ERC1155MInitializable is * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner */ - function mint( - uint256 tokenId, - uint32 qty, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { + function mint(uint256 tokenId, uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable + virtual + nonReentrant + { _mintInternal(msg.sender, tokenId, qty, 0, proof, timestamp, signature); } @@ -366,15 +316,7 @@ contract ERC1155MInitializable is uint64 timestamp, bytes calldata signature ) external payable virtual nonReentrant { - _mintInternal( - msg.sender, - tokenId, - qty, - limit, - proof, - timestamp, - signature - ); + _mintInternal(msg.sender, tokenId, qty, limit, proof, timestamp, signature); } /** @@ -386,13 +328,11 @@ contract ERC1155MInitializable is * limit - limit for the given minter * proof - the merkle proof generated on client side. This applies if using whitelist */ - function authorizedMint( - address to, - uint256 tokenId, - uint32 qty, - uint32 limit, - bytes32[] calldata proof - ) external payable onlyAuthorizedMinter { + function authorizedMint(address to, uint256 tokenId, uint32 qty, uint32 limit, bytes32[] calldata proof) + external + payable + onlyAuthorizedMinter + { _mintInternal(to, tokenId, qty, limit, proof, 0, bytes("0")); } @@ -412,13 +352,7 @@ contract ERC1155MInitializable is bool waiveMintFee = false; if (_cosigner != address(0)) { - waiveMintFee = assertValidCosign( - msg.sender, - qty, - timestamp, - signature, - getCosignNonce(msg.sender, tokenId) - ); + waiveMintFee = assertValidCosign(msg.sender, qty, timestamp, signature, getCosignNonce(msg.sender, tokenId)); _assertValidTimestamp(timestamp); stageTimestamp = timestamp; } @@ -429,34 +363,29 @@ contract ERC1155MInitializable is uint80 adjustedMintFee = waiveMintFee ? 0 : stage.mintFee[tokenId]; // Check value if minting with ETH - if ( - _mintCurrency == address(0) && - msg.value < (stage.price[tokenId] + adjustedMintFee) * qty - ) revert NotEnoughValue(); + if (_mintCurrency == address(0) && msg.value < (stage.price[tokenId] + adjustedMintFee) * qty) { + revert NotEnoughValue(); + } // Check stage supply if applicable if (stage.maxStageSupply[tokenId] > 0) { - if ( - _stageMintedCountsPerToken[activeStage][tokenId] + qty > - stage.maxStageSupply[tokenId] - ) revert StageSupplyExceeded(); + if (_stageMintedCountsPerToken[activeStage][tokenId] + qty > stage.maxStageSupply[tokenId]) { + revert StageSupplyExceeded(); + } } // Check global wallet limit if applicable if (_globalWalletLimit[tokenId] > 0) { - if ( - _totalMintedByTokenByAddress(to, tokenId) + qty > - _globalWalletLimit[tokenId] - ) revert WalletGlobalLimitExceeded(); + if (_totalMintedByTokenByAddress(to, tokenId) + qty > _globalWalletLimit[tokenId]) { + revert WalletGlobalLimitExceeded(); + } } // Check wallet limit for stage if applicable, limit == 0 means no limit enforced if (stage.walletLimit[tokenId] > 0) { - if ( - _stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] + - qty > - stage.walletLimit[tokenId] - ) revert WalletStageLimitExceeded(); + if (_stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] + qty > stage.walletLimit[tokenId]) { + revert WalletStageLimitExceeded(); + } } // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required @@ -466,12 +395,7 @@ contract ERC1155MInitializable is } // Verify merkle proof mint limit - if ( - limit > 0 && - _stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] + - qty > - limit - ) { + if (limit > 0 && _stageMintedCountsPerTokenPerWallet[activeStage][tokenId][to] + qty > limit) { revert WalletStageLimitExceeded(); } } @@ -479,10 +403,7 @@ contract ERC1155MInitializable is if (_mintCurrency != address(0)) { // ERC20 mint payment SafeTransferLib.safeTransferFrom( - _mintCurrency, - msg.sender, - address(this), - (stage.price[tokenId] + adjustedMintFee) * qty + _mintCurrency, msg.sender, address(this), (stage.price[tokenId] + adjustedMintFee) * qty ); } @@ -498,11 +419,7 @@ contract ERC1155MInitializable is * NOTE: This function bypasses validations thus only available for owner. * This is typically used for owner to pre-mint or mint the remaining of the supply. */ - function ownerMint( - address to, - uint256 tokenId, - uint32 qty - ) external onlyOwner hasSupply(tokenId, qty) { + function ownerMint(address to, uint256 tokenId, uint32 qty) external onlyOwner hasSupply(tokenId, qty) { _mint(to, tokenId, qty, ""); } @@ -510,12 +427,12 @@ contract ERC1155MInitializable is * @dev Withdraws funds by owner. */ function withdraw() external onlyOwner { - (bool success, ) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); + (bool success,) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); if (!success) revert TransferFailed(); _totalMintFee = 0; uint256 remainingValue = address(this).balance; - (success, ) = _fundReceiver.call{value: remainingValue}(""); + (success,) = _fundReceiver.call{value: remainingValue}(""); if (!success) revert WithdrawFailed(); emit Withdraw(_totalMintFee + remainingValue); @@ -529,7 +446,7 @@ contract ERC1155MInitializable is uint256 totalFee = _totalMintFee; uint256 remaining = SafeTransferLib.balanceOf(_mintCurrency, address(this)); - + if (remaining < totalFee) revert InsufficientBalance(); _totalMintFee = 0; @@ -560,66 +477,50 @@ contract ERC1155MInitializable is /** * @dev Returns the current active stage based on timestamp. */ - function getActiveStageFromTimestamp( - uint64 timestamp - ) public view returns (uint256) { + function getActiveStageFromTimestamp(uint64 timestamp) public view returns (uint256) { for (uint256 i = 0; i < _mintStages.length; i++) { - if ( - timestamp >= _mintStages[i].startTimeUnixSeconds && - timestamp < _mintStages[i].endTimeUnixSeconds - ) { + if (timestamp >= _mintStages[i].startTimeUnixSeconds && timestamp < _mintStages[i].endTimeUnixSeconds) { return i; } } revert InvalidStage(); } - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC2981, ERC1155Upgradeable) returns (bool) { - return super.supportsInterface(interfaceId) || - ERC2981.supportsInterface(interfaceId) || - ERC1155Upgradeable.supportsInterface(interfaceId); + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC2981, ERC1155Upgradeable) + returns (bool) + { + return super.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId) + || ERC1155Upgradeable.supportsInterface(interfaceId); } /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ - function _assertValidStartAndEndTimestamp( - uint64 start, - uint64 end - ) internal pure { + function _assertValidStartAndEndTimestamp(uint64 start, uint64 end) internal pure { if (start >= end) revert InvalidStartAndEndTimestamp(); } - function _assertValidStageArgsLength( - MintStageInfo1155 calldata stageInfo - ) internal view { + function _assertValidStageArgsLength(MintStageInfo1155 calldata stageInfo) internal view { if ( - stageInfo.price.length != _numTokens || - stageInfo.mintFee.length != _numTokens || - stageInfo.walletLimit.length != _numTokens || - stageInfo.merkleRoot.length != _numTokens || - stageInfo.maxStageSupply.length != _numTokens + stageInfo.price.length != _numTokens || stageInfo.mintFee.length != _numTokens + || stageInfo.walletLimit.length != _numTokens || stageInfo.merkleRoot.length != _numTokens + || stageInfo.maxStageSupply.length != _numTokens ) { revert InvalidStageArgsLength(); } } - function setDefaultRoyalty( - address receiver, - uint96 feeNumerator - ) public onlyOwner { + function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { super._setDefaultRoyalty(receiver, feeNumerator); emit DefaultRoyaltySet(receiver, feeNumerator); } - function setTokenRoyalty( - uint256 tokenId, - address receiver, - uint96 feeNumerator - ) public onlyOwner { + function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) public onlyOwner { super._setTokenRoyalty(tokenId, receiver, feeNumerator); emit TokenRoyaltySet(tokenId, receiver, feeNumerator); } -} \ No newline at end of file +} diff --git a/contracts/nft/erc1155m/ERC1155MStorage.sol b/contracts/nft/erc1155m/ERC1155MStorage.sol index 4a12106b..39b47ec8 100644 --- a/contracts/nft/erc1155m/ERC1155MStorage.sol +++ b/contracts/nft/erc1155m/ERC1155MStorage.sol @@ -19,8 +19,6 @@ contract ERC1155MStorage { uint256 internal _numTokens; address internal _fundReceiver; - mapping(uint256 => mapping(uint256 => mapping(address => uint32))) - internal _stageMintedCountsPerTokenPerWallet; - mapping(uint256 => mapping(uint256 => uint256)) - internal _stageMintedCountsPerToken; + mapping(uint256 => mapping(uint256 => mapping(address => uint32))) internal _stageMintedCountsPerTokenPerWallet; + mapping(uint256 => mapping(uint256 => uint256)) internal _stageMintedCountsPerToken; } diff --git a/contracts/nft/erc1155m/interfaces/IERC1155M.sol b/contracts/nft/erc1155m/interfaces/IERC1155M.sol index 94426d6b..4dc9cde0 100644 --- a/contracts/nft/erc1155m/interfaces/IERC1155M.sol +++ b/contracts/nft/erc1155m/interfaces/IERC1155M.sol @@ -13,15 +13,14 @@ interface IERC1155M is ERC1155MErrorsAndEvents { function totalMintedByAddress(address account) external view returns (uint256[] memory); - function getStageInfo(uint256 stage) external view returns (MintStageInfo1155 memory, uint256[] memory, uint256[] memory); + function getStageInfo(uint256 stage) + external + view + returns (MintStageInfo1155 memory, uint256[] memory, uint256[] memory); - function mint( - uint256 tokenId, - uint32 qty, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable; + function mint(uint256 tokenId, uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable; function mintWithLimit( uint256 tokenId, @@ -32,11 +31,7 @@ interface IERC1155M is ERC1155MErrorsAndEvents { bytes calldata signature ) external payable; - function authorizedMint( - address to, - uint256 tokenId, - uint32 qty, - uint32 limit, - bytes32[] calldata proof - ) external payable; + function authorizedMint(address to, uint256 tokenId, uint32 qty, uint32 limit, bytes32[] calldata proof) + external + payable; } diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index 4b6123d4..94c6d182 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -55,8 +55,7 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi MintStageInfo[] private _mintStages; // Minted count per stage per wallet. - mapping(uint256 => mapping(address => uint32)) - private _stageMintedCountsPerWallet; + mapping(uint256 => mapping(address => uint32)) private _stageMintedCountsPerWallet; // Minted count per stage. mapping(uint256 => uint256) private _stageMintedCounts; @@ -83,8 +82,9 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi address mintCurrency, address fundReceiver ) Ownable(msg.sender) ERC721ACQueryable(collectionName, collectionSymbol) { - if (globalWalletLimit > maxMintableSupply) + if (globalWalletLimit > maxMintableSupply) { revert GlobalWalletLimitOverflow(); + } _mintable = true; _maxMintableSupply = maxMintableSupply; _globalWalletLimit = globalWalletLimit; @@ -137,14 +137,14 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi /** * @dev Add authorized minter. Can only be called by contract owner. */ - function addAuthorizedMinter(address minter) external onlyOwner override { + function addAuthorizedMinter(address minter) external override onlyOwner { _addAuthorizedMinter(minter); } /** * @dev Remove authorized minter. Can only be called by contract owner. */ - function removeAuthorizedMinter(address minter) external onlyOwner override { + function removeAuthorizedMinter(address minter) external override onlyOwner { _removeAuthorizedMinter(minter); } @@ -182,20 +182,13 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi function setStages(MintStageInfo[] calldata newStages) external onlyOwner { delete _mintStages; - for (uint256 i = 0; i < newStages.length; ) { + for (uint256 i = 0; i < newStages.length;) { if (i >= 1) { - if ( - newStages[i].startTimeUnixSeconds < - newStages[i - 1].endTimeUnixSeconds + - _timestampExpirySeconds - ) { + if (newStages[i].startTimeUnixSeconds < newStages[i - 1].endTimeUnixSeconds + _timestampExpirySeconds) { revert InsufficientStageTimeGap(); } } - _assertValidStartAndEndTimestamp( - newStages[i].startTimeUnixSeconds, - newStages[i].endTimeUnixSeconds - ); + _assertValidStartAndEndTimestamp(newStages[i].startTimeUnixSeconds, newStages[i].endTimeUnixSeconds); _mintStages.push( MintStageInfo({ price: newStages[i].price, @@ -258,9 +251,7 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi * * New supply cannot be larger than the old. */ - function setMaxMintableSupply( - uint256 maxMintableSupply - ) external virtual onlyOwner { + function setMaxMintableSupply(uint256 maxMintableSupply) external virtual onlyOwner { if (maxMintableSupply > _maxMintableSupply) { revert CannotIncreaseMaxMintableSupply(); } @@ -278,11 +269,10 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi /** * @dev Sets global wallet limit. */ - function setGlobalWalletLimit( - uint256 globalWalletLimit - ) external onlyOwner { - if (globalWalletLimit > _maxMintableSupply) + function setGlobalWalletLimit(uint256 globalWalletLimit) external onlyOwner { + if (globalWalletLimit > _maxMintableSupply) { revert GlobalWalletLimitOverflow(); + } _globalWalletLimit = globalWalletLimit; emit SetGlobalWalletLimit(globalWalletLimit); } @@ -290,18 +280,14 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi /** * @dev Returns number of minted token for a given address. */ - function totalMintedByAddress( - address a - ) external view virtual override returns (uint256) { + function totalMintedByAddress(address a) external view virtual override returns (uint256) { return _numberMinted(a); } /** * @dev Returns info for one stage specified by index (starting from 0). */ - function getStageInfo( - uint256 index - ) external view override returns (MintStageInfo memory, uint32, uint256) { + function getStageInfo(uint256 index) external view override returns (MintStageInfo memory, uint32, uint256) { if (index >= _mintStages.length) { revert("InvalidStage"); } @@ -325,12 +311,12 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function mint( - uint32 qty, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { + function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable + virtual + nonReentrant + { _mintInternal(qty, msg.sender, 0, proof, timestamp, signature); } @@ -362,13 +348,11 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function crossmint( - uint32 qty, - address to, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable nonReentrant { + function crossmint(uint32 qty, address to, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable + nonReentrant + { if (_crossmintAddress == address(0)) revert CrossmintAddressNotSet(); // Check the caller is Crossmint @@ -413,13 +397,7 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi bool waiveMintFee = false; if (_cosigner != address(0)) { - waiveMintFee = assertValidCosign( - msg.sender, - qty, - timestamp, - signature, - getCosignNonce(msg.sender) - ); + waiveMintFee = assertValidCosign(msg.sender, qty, timestamp, signature, getCosignNonce(msg.sender)); _assertValidTimestamp(timestamp); stageTimestamp = timestamp; } @@ -430,55 +408,43 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi uint80 adjustedMintFee = waiveMintFee ? 0 : stage.mintFee; // Check value if minting with ETH - if ( - _mintCurrency == address(0) && - msg.value < (stage.price + adjustedMintFee) * qty - ) revert NotEnoughValue(); + if (_mintCurrency == address(0) && msg.value < (stage.price + adjustedMintFee) * qty) revert NotEnoughValue(); // Check stage supply if applicable if (stage.maxStageSupply > 0) { - if (_stageMintedCounts[activeStage] + qty > stage.maxStageSupply) + if (_stageMintedCounts[activeStage] + qty > stage.maxStageSupply) { revert StageSupplyExceeded(); + } } // Check global wallet limit if applicable if (_globalWalletLimit > 0) { - if (_numberMinted(to) + qty > _globalWalletLimit) + if (_numberMinted(to) + qty > _globalWalletLimit) { revert WalletGlobalLimitExceeded(); + } } // Check wallet limit for stage if applicable, limit == 0 means no limit enforced if (stage.walletLimit > 0) { - if ( - _stageMintedCountsPerWallet[activeStage][to] + qty > - stage.walletLimit - ) revert WalletStageLimitExceeded(); + if (_stageMintedCountsPerWallet[activeStage][to] + qty > stage.walletLimit) { + revert WalletStageLimitExceeded(); + } } // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required if (stage.merkleRoot != 0) { - if ( - MerkleProof.processProof( - proof, - keccak256(abi.encodePacked(to, limit)) - ) != stage.merkleRoot - ) revert InvalidProof(); + if (MerkleProof.processProof(proof, keccak256(abi.encodePacked(to, limit))) != stage.merkleRoot) { + revert InvalidProof(); + } // Verify merkle proof mint limit - if ( - limit > 0 && - _stageMintedCountsPerWallet[activeStage][to] + qty > limit - ) { + if (limit > 0 && _stageMintedCountsPerWallet[activeStage][to] + qty > limit) { revert WalletStageLimitExceeded(); } } if (_mintCurrency != address(0)) { - IERC20(_mintCurrency).safeTransferFrom( - msg.sender, - address(this), - (stage.price + adjustedMintFee) * qty - ); + IERC20(_mintCurrency).safeTransferFrom(msg.sender, address(this), (stage.price + adjustedMintFee) * qty); } _totalMintFee += adjustedMintFee * qty; @@ -494,10 +460,7 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi * NOTE: This function bypasses validations thus only available for owner. * This is typically used for owner to pre-mint or mint the remaining of the supply. */ - function ownerMint( - uint32 qty, - address to - ) external onlyOwner hasSupply(qty) { + function ownerMint(uint32 qty, address to) external onlyOwner hasSupply(qty) { _safeMint(to, qty); } @@ -505,12 +468,12 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi * @dev Withdraws funds by owner. */ function withdraw() external onlyOwner { - (bool success, ) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); + (bool success,) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); if (!success) revert TransferFailed(); _totalMintFee = 0; uint256 remainingValue = address(this).balance; - (success, ) = FUND_RECEIVER.call{value: remainingValue}(""); + (success,) = FUND_RECEIVER.call{value: remainingValue}(""); if (!success) revert WithdrawFailed(); emit Withdraw(_totalMintFee + remainingValue); @@ -549,22 +512,11 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi /** * @dev Returns token URI for a given token id. */ - function tokenURI( - uint256 tokenId - ) public view override(ERC721A, IERC721A) returns (string memory) { + function tokenURI(uint256 tokenId) public view override(ERC721A, IERC721A) returns (string memory) { if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); string memory baseURI = _currentBaseURI; - return - bytes(baseURI).length != 0 - ? string( - abi.encodePacked( - baseURI, - _toString(tokenId), - _tokenURISuffix - ) - ) - : ""; + return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, _toString(tokenId), _tokenURISuffix)) : ""; } /** @@ -584,14 +536,9 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi /** * @dev Returns the current active stage based on timestamp. */ - function getActiveStageFromTimestamp( - uint64 timestamp - ) public view returns (uint256) { - for (uint256 i = 0; i < _mintStages.length; ) { - if ( - timestamp >= _mintStages[i].startTimeUnixSeconds && - timestamp < _mintStages[i].endTimeUnixSeconds - ) { + function getActiveStageFromTimestamp(uint64 timestamp) public view returns (uint256) { + for (uint256 i = 0; i < _mintStages.length;) { + if (timestamp >= _mintStages[i].startTimeUnixSeconds && timestamp < _mintStages[i].endTimeUnixSeconds) { return i; } unchecked { @@ -604,10 +551,7 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ - function _assertValidStartAndEndTimestamp( - uint64 start, - uint64 end - ) internal pure { + function _assertValidStartAndEndTimestamp(uint64 start, uint64 end) internal pure { if (start >= end) revert InvalidStartAndEndTimestamp(); } @@ -628,8 +572,8 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi } /** - * @notice Returns the function selector for the transfer validator's validation function to be called - * @notice for transaction simulation. + * @notice Returns the function selector for the transfer validator's validation function to be called + * @notice for transaction simulation. */ function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); diff --git a/contracts/nft/erc721m/ERC721CMInitializable.sol b/contracts/nft/erc721m/ERC721CMInitializable.sol index f6486030..709d8cdd 100644 --- a/contracts/nft/erc721m/ERC721CMInitializable.sol +++ b/contracts/nft/erc721m/ERC721CMInitializable.sol @@ -56,8 +56,7 @@ abstract contract ERC721CMInitializable is MintStageInfo[] private _mintStages; // Minted count per stage per wallet. - mapping(uint256 => mapping(address => uint32)) - private _stageMintedCountsPerWallet; + mapping(uint256 => mapping(address => uint32)) private _stageMintedCountsPerWallet; // Minted count per stage. mapping(uint256 => uint256) private _stageMintedCounts; @@ -85,8 +84,9 @@ abstract contract ERC721CMInitializable is ) public onlyInitializing { initializeOwner(msg.sender); __ERC721ACQueryableInitializable_init(collectionName, collectionSymbol); - if (globalWalletLimit > maxMintableSupply) + if (globalWalletLimit > maxMintableSupply) { revert GlobalWalletLimitOverflow(); + } _mintable = true; _maxMintableSupply = maxMintableSupply; _globalWalletLimit = globalWalletLimit; @@ -155,20 +155,13 @@ abstract contract ERC721CMInitializable is function setStages(MintStageInfo[] calldata newStages) external onlyOwner { delete _mintStages; - for (uint256 i = 0; i < newStages.length; ) { + for (uint256 i = 0; i < newStages.length;) { if (i >= 1) { - if ( - newStages[i].startTimeUnixSeconds < - newStages[i - 1].endTimeUnixSeconds + - _timestampExpirySeconds - ) { + if (newStages[i].startTimeUnixSeconds < newStages[i - 1].endTimeUnixSeconds + _timestampExpirySeconds) { revert InsufficientStageTimeGap(); } } - _assertValidStartAndEndTimestamp( - newStages[i].startTimeUnixSeconds, - newStages[i].endTimeUnixSeconds - ); + _assertValidStartAndEndTimestamp(newStages[i].startTimeUnixSeconds, newStages[i].endTimeUnixSeconds); _mintStages.push( MintStageInfo({ price: newStages[i].price, @@ -231,9 +224,7 @@ abstract contract ERC721CMInitializable is * * New supply cannot be larger than the old. */ - function setMaxMintableSupply( - uint256 maxMintableSupply - ) external virtual onlyOwner { + function setMaxMintableSupply(uint256 maxMintableSupply) external virtual onlyOwner { if (maxMintableSupply > _maxMintableSupply) { revert CannotIncreaseMaxMintableSupply(); } @@ -251,11 +242,10 @@ abstract contract ERC721CMInitializable is /** * @dev Sets global wallet limit. */ - function setGlobalWalletLimit( - uint256 globalWalletLimit - ) external onlyOwner { - if (globalWalletLimit > _maxMintableSupply) + function setGlobalWalletLimit(uint256 globalWalletLimit) external onlyOwner { + if (globalWalletLimit > _maxMintableSupply) { revert GlobalWalletLimitOverflow(); + } _globalWalletLimit = globalWalletLimit; emit SetGlobalWalletLimit(globalWalletLimit); } @@ -263,18 +253,14 @@ abstract contract ERC721CMInitializable is /** * @dev Returns number of minted token for a given address. */ - function totalMintedByAddress( - address a - ) external view virtual override returns (uint256) { + function totalMintedByAddress(address a) external view virtual override returns (uint256) { return _numberMinted(a); } /** * @dev Returns info for one stage specified by index (starting from 0). */ - function getStageInfo( - uint256 index - ) external view override returns (MintStageInfo memory, uint32, uint256) { + function getStageInfo(uint256 index) external view override returns (MintStageInfo memory, uint32, uint256) { if (index >= _mintStages.length) { revert("InvalidStage"); } @@ -298,12 +284,12 @@ abstract contract ERC721CMInitializable is * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function mint( - uint32 qty, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { + function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable + virtual + nonReentrant + { _mintInternal(qty, msg.sender, 0, proof); } @@ -335,13 +321,11 @@ abstract contract ERC721CMInitializable is * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function crossmint( - uint32 qty, - address to, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable nonReentrant { + function crossmint(uint32 qty, address to, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable + nonReentrant + { if (_crossmintAddress == address(0)) revert CrossmintAddressNotSet(); // Check the caller is Crossmint @@ -353,12 +337,11 @@ abstract contract ERC721CMInitializable is /** * @dev Implementation of minting. */ - function _mintInternal( - uint32 qty, - address to, - uint32 limit, - bytes32[] calldata proof - ) internal canMint hasSupply(qty) { + function _mintInternal(uint32 qty, address to, uint32 limit, bytes32[] calldata proof) + internal + canMint + hasSupply(qty) + { uint64 stageTimestamp = uint64(block.timestamp); MintStageInfo memory stage; @@ -368,55 +351,43 @@ abstract contract ERC721CMInitializable is stage = _mintStages[activeStage]; // Check value if minting with ETH - if ( - _mintCurrency == address(0) && - msg.value < (stage.price + stage.mintFee) * qty - ) revert NotEnoughValue(); + if (_mintCurrency == address(0) && msg.value < (stage.price + stage.mintFee) * qty) revert NotEnoughValue(); // Check stage supply if applicable if (stage.maxStageSupply > 0) { - if (_stageMintedCounts[activeStage] + qty > stage.maxStageSupply) + if (_stageMintedCounts[activeStage] + qty > stage.maxStageSupply) { revert StageSupplyExceeded(); + } } // Check global wallet limit if applicable if (_globalWalletLimit > 0) { - if (_numberMinted(to) + qty > _globalWalletLimit) + if (_numberMinted(to) + qty > _globalWalletLimit) { revert WalletGlobalLimitExceeded(); + } } // Check wallet limit for stage if applicable, limit == 0 means no limit enforced if (stage.walletLimit > 0) { - if ( - _stageMintedCountsPerWallet[activeStage][to] + qty > - stage.walletLimit - ) revert WalletStageLimitExceeded(); + if (_stageMintedCountsPerWallet[activeStage][to] + qty > stage.walletLimit) { + revert WalletStageLimitExceeded(); + } } // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required if (stage.merkleRoot != 0) { - if ( - MerkleProof.processProof( - proof, - keccak256(abi.encodePacked(to, limit)) - ) != stage.merkleRoot - ) revert InvalidProof(); + if (MerkleProof.processProof(proof, keccak256(abi.encodePacked(to, limit))) != stage.merkleRoot) { + revert InvalidProof(); + } // Verify merkle proof mint limit - if ( - limit > 0 && - _stageMintedCountsPerWallet[activeStage][to] + qty > limit - ) { + if (limit > 0 && _stageMintedCountsPerWallet[activeStage][to] + qty > limit) { revert WalletStageLimitExceeded(); } } if (_mintCurrency != address(0)) { - IERC20(_mintCurrency).safeTransferFrom( - msg.sender, - address(this), - (stage.price + stage.mintFee) * qty - ); + IERC20(_mintCurrency).safeTransferFrom(msg.sender, address(this), (stage.price + stage.mintFee) * qty); } _totalMintFee += stage.mintFee * qty; @@ -432,10 +403,7 @@ abstract contract ERC721CMInitializable is * NOTE: This function bypasses validations thus only available for owner. * This is typically used for owner to pre-mint or mint the remaining of the supply. */ - function ownerMint( - uint32 qty, - address to - ) external onlyOwner hasSupply(qty) { + function ownerMint(uint32 qty, address to) external onlyOwner hasSupply(qty) { _safeMint(to, qty); } @@ -443,12 +411,12 @@ abstract contract ERC721CMInitializable is * @dev Withdraws funds by owner. */ function withdraw() external onlyOwner { - (bool success, ) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); + (bool success,) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); if (!success) revert TransferFailed(); _totalMintFee = 0; uint256 remainingValue = address(this).balance; - (success, ) = FUND_RECEIVER.call{value: remainingValue}(""); + (success,) = FUND_RECEIVER.call{value: remainingValue}(""); if (!success) revert WithdrawFailed(); emit Withdraw(_totalMintFee + remainingValue); @@ -487,9 +455,7 @@ abstract contract ERC721CMInitializable is /** * @dev Returns token URI for a given token id. */ - function tokenURI( - uint256 tokenId - ) + function tokenURI(uint256 tokenId) public view override(ERC721AUpgradeable, IERC721AUpgradeable) @@ -498,16 +464,7 @@ abstract contract ERC721CMInitializable is if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); string memory baseURI = _currentBaseURI; - return - bytes(baseURI).length != 0 - ? string( - abi.encodePacked( - baseURI, - _toString(tokenId), - _tokenURISuffix - ) - ) - : ""; + return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, _toString(tokenId), _tokenURISuffix)) : ""; } /** @@ -527,14 +484,9 @@ abstract contract ERC721CMInitializable is /** * @dev Returns the current active stage based on timestamp. */ - function getActiveStageFromTimestamp( - uint64 timestamp - ) public view returns (uint256) { - for (uint256 i = 0; i < _mintStages.length; ) { - if ( - timestamp >= _mintStages[i].startTimeUnixSeconds && - timestamp < _mintStages[i].endTimeUnixSeconds - ) { + function getActiveStageFromTimestamp(uint64 timestamp) public view returns (uint256) { + for (uint256 i = 0; i < _mintStages.length;) { + if (timestamp >= _mintStages[i].startTimeUnixSeconds && timestamp < _mintStages[i].endTimeUnixSeconds) { return i; } unchecked { @@ -547,10 +499,7 @@ abstract contract ERC721CMInitializable is /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ - function _assertValidStartAndEndTimestamp( - uint64 start, - uint64 end - ) internal pure { + function _assertValidStartAndEndTimestamp(uint64 start, uint64 end) internal pure { if (start >= end) revert InvalidStartAndEndTimestamp(); } @@ -566,23 +515,16 @@ abstract contract ERC721CMInitializable is return chainID; } - function _requireCallerIsContractOwner() - internal - view - virtual - override(OwnableInitializable, OwnablePermissions) - { + function _requireCallerIsContractOwner() internal view virtual override(OwnableInitializable, OwnablePermissions) { _checkOwner(); } /** - * @notice Returns the function selector for the transfer validator's validation function to be called - * @notice for transaction simulation. + * @notice Returns the function selector for the transfer validator's validation function to be called + * @notice for transaction simulation. */ function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); isViewFunction = true; } - - } diff --git a/contracts/nft/erc721m/ERC721M.sol b/contracts/nft/erc721m/ERC721M.sol index 54d93b8d..2bf73585 100644 --- a/contracts/nft/erc721m/ERC721M.sol +++ b/contracts/nft/erc721m/ERC721M.sol @@ -24,14 +24,8 @@ import "../../common/AuthorizedMinterControl.sol"; * - crossmint support * - anti-botting */ -contract ERC721M is - IERC721M, - ERC721AQueryable, - Ownable, - ReentrancyGuard, - Cosignable, - AuthorizedMinterControl -{ + +contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosignable, AuthorizedMinterControl { using ECDSA for bytes32; using SafeERC20 for IERC20; @@ -60,8 +54,7 @@ contract ERC721M is MintStageInfo[] private _mintStages; // Minted count per stage per wallet. - mapping(uint256 => mapping(address => uint32)) - private _stageMintedCountsPerWallet; + mapping(uint256 => mapping(address => uint32)) private _stageMintedCountsPerWallet; // Minted count per stage. mapping(uint256 => uint256) private _stageMintedCounts; @@ -88,8 +81,9 @@ contract ERC721M is address mintCurrency, address fundReceiver ) Ownable(msg.sender) ERC721A(collectionName, collectionSymbol) { - if (globalWalletLimit > maxMintableSupply) + if (globalWalletLimit > maxMintableSupply) { revert GlobalWalletLimitOverflow(); + } _mintable = true; _maxMintableSupply = maxMintableSupply; _globalWalletLimit = globalWalletLimit; @@ -142,14 +136,14 @@ contract ERC721M is /** * @dev Add authorized minter. Can only be called by contract owner. */ - function addAuthorizedMinter(address minter) external onlyOwner override { + function addAuthorizedMinter(address minter) external override onlyOwner { _addAuthorizedMinter(minter); } /** * @dev Remove authorized minter. Can only be called by contract owner. */ - function removeAuthorizedMinter(address minter) external onlyOwner override { + function removeAuthorizedMinter(address minter) external override onlyOwner { _removeAuthorizedMinter(minter); } @@ -189,18 +183,11 @@ contract ERC721M is for (uint256 i = 0; i < newStages.length; i++) { if (i >= 1) { - if ( - newStages[i].startTimeUnixSeconds < - newStages[i - 1].endTimeUnixSeconds + - _timestampExpirySeconds - ) { + if (newStages[i].startTimeUnixSeconds < newStages[i - 1].endTimeUnixSeconds + _timestampExpirySeconds) { revert InsufficientStageTimeGap(); } } - _assertValidStartAndEndTimestamp( - newStages[i].startTimeUnixSeconds, - newStages[i].endTimeUnixSeconds - ); + _assertValidStartAndEndTimestamp(newStages[i].startTimeUnixSeconds, newStages[i].endTimeUnixSeconds); _mintStages.push( MintStageInfo({ price: newStages[i].price, @@ -259,9 +246,7 @@ contract ERC721M is * * New supply cannot be larger than the old. */ - function setMaxMintableSupply( - uint256 maxMintableSupply - ) external virtual onlyOwner { + function setMaxMintableSupply(uint256 maxMintableSupply) external virtual onlyOwner { if (maxMintableSupply > _maxMintableSupply) { revert CannotIncreaseMaxMintableSupply(); } @@ -279,11 +264,10 @@ contract ERC721M is /** * @dev Sets global wallet limit. */ - function setGlobalWalletLimit( - uint256 globalWalletLimit - ) external onlyOwner { - if (globalWalletLimit > _maxMintableSupply) + function setGlobalWalletLimit(uint256 globalWalletLimit) external onlyOwner { + if (globalWalletLimit > _maxMintableSupply) { revert GlobalWalletLimitOverflow(); + } _globalWalletLimit = globalWalletLimit; emit SetGlobalWalletLimit(globalWalletLimit); } @@ -291,18 +275,14 @@ contract ERC721M is /** * @dev Returns number of minted token for a given address. */ - function totalMintedByAddress( - address a - ) external view virtual override returns (uint256) { + function totalMintedByAddress(address a) external view virtual override returns (uint256) { return _numberMinted(a); } /** * @dev Returns info for one stage specified by index (starting from 0). */ - function getStageInfo( - uint256 index - ) external view override returns (MintStageInfo memory, uint32, uint256) { + function getStageInfo(uint256 index) external view override returns (MintStageInfo memory, uint32, uint256) { if (index >= _mintStages.length) { revert("InvalidStage"); } @@ -326,12 +306,12 @@ contract ERC721M is * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function mint( - uint32 qty, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { + function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable + virtual + nonReentrant + { _mintInternal(qty, msg.sender, 0, proof, timestamp, signature); } @@ -363,13 +343,11 @@ contract ERC721M is * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function crossmint( - uint32 qty, - address to, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable nonReentrant { + function crossmint(uint32 qty, address to, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable + nonReentrant + { if (_crossmintAddress == address(0)) revert CrossmintAddressNotSet(); // Check the caller is Crossmint @@ -414,13 +392,7 @@ contract ERC721M is bool waiveMintFee = false; if (_cosigner != address(0)) { - waiveMintFee = assertValidCosign( - msg.sender, - qty, - timestamp, - signature, - getCosignNonce(msg.sender) - ); + waiveMintFee = assertValidCosign(msg.sender, qty, timestamp, signature, getCosignNonce(msg.sender)); _assertValidTimestamp(timestamp); stageTimestamp = timestamp; } @@ -431,56 +403,44 @@ contract ERC721M is uint80 adjustedMintFee = waiveMintFee ? 0 : stage.mintFee; // Check value if minting with ETH - if ( - _mintCurrency == address(0) && - msg.value < (stage.price + adjustedMintFee) * qty - ) revert NotEnoughValue(); + if (_mintCurrency == address(0) && msg.value < (stage.price + adjustedMintFee) * qty) revert NotEnoughValue(); // Check stage supply if applicable if (stage.maxStageSupply > 0) { - if (_stageMintedCounts[activeStage] + qty > stage.maxStageSupply) + if (_stageMintedCounts[activeStage] + qty > stage.maxStageSupply) { revert StageSupplyExceeded(); + } } // Check global wallet limit if applicable if (_globalWalletLimit > 0) { - if (_numberMinted(to) + qty > _globalWalletLimit) + if (_numberMinted(to) + qty > _globalWalletLimit) { revert WalletGlobalLimitExceeded(); + } } // Check wallet limit for stage if applicable, limit == 0 means no limit enforced if (stage.walletLimit > 0) { - if ( - _stageMintedCountsPerWallet[activeStage][to] + qty > - stage.walletLimit - ) revert WalletStageLimitExceeded(); + if (_stageMintedCountsPerWallet[activeStage][to] + qty > stage.walletLimit) { + revert WalletStageLimitExceeded(); + } } // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required if (stage.merkleRoot != 0) { - if ( - MerkleProof.processProof( - proof, - keccak256(abi.encodePacked(to, limit)) - ) != stage.merkleRoot - ) revert InvalidProof(); + if (MerkleProof.processProof(proof, keccak256(abi.encodePacked(to, limit))) != stage.merkleRoot) { + revert InvalidProof(); + } // Verify merkle proof mint limit - if ( - limit > 0 && - _stageMintedCountsPerWallet[activeStage][to] + qty > limit - ) { + if (limit > 0 && _stageMintedCountsPerWallet[activeStage][to] + qty > limit) { revert WalletStageLimitExceeded(); } } if (_mintCurrency != address(0)) { // ERC20 mint payment - IERC20(_mintCurrency).safeTransferFrom( - msg.sender, - address(this), - (stage.price + adjustedMintFee) * qty - ); + IERC20(_mintCurrency).safeTransferFrom(msg.sender, address(this), (stage.price + adjustedMintFee) * qty); } _totalMintFee += adjustedMintFee * qty; @@ -496,10 +456,7 @@ contract ERC721M is * NOTE: This function bypasses validations thus only available for owner. * This is typically used for owner to pre-mint or mint the remaining of the supply. */ - function ownerMint( - uint32 qty, - address to - ) external onlyOwner hasSupply(qty) { + function ownerMint(uint32 qty, address to) external onlyOwner hasSupply(qty) { _safeMint(to, qty); } @@ -507,12 +464,12 @@ contract ERC721M is * @dev Withdraws funds by owner. */ function withdraw() external onlyOwner { - (bool success, ) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); + (bool success,) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); if (!success) revert TransferFailed(); _totalMintFee = 0; uint256 remainingValue = address(this).balance; - (success, ) = FUND_RECEIVER.call{value: remainingValue}(""); + (success,) = FUND_RECEIVER.call{value: remainingValue}(""); if (!success) revert WithdrawFailed(); emit Withdraw(_totalMintFee + remainingValue); @@ -551,35 +508,19 @@ contract ERC721M is /** * @dev Returns token URI for a given token id. */ - function tokenURI( - uint256 tokenId - ) public view override(ERC721A, IERC721A) returns (string memory) { + function tokenURI(uint256 tokenId) public view override(ERC721A, IERC721A) returns (string memory) { if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); string memory baseURI = _currentBaseURI; - return - bytes(baseURI).length != 0 - ? string( - abi.encodePacked( - baseURI, - _toString(tokenId), - _tokenURISuffix - ) - ) - : ""; + return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, _toString(tokenId), _tokenURISuffix)) : ""; } /** * @dev Returns the current active stage based on timestamp. */ - function getActiveStageFromTimestamp( - uint64 timestamp - ) public view returns (uint256) { + function getActiveStageFromTimestamp(uint64 timestamp) public view returns (uint256) { for (uint256 i = 0; i < _mintStages.length; i++) { - if ( - timestamp >= _mintStages[i].startTimeUnixSeconds && - timestamp < _mintStages[i].endTimeUnixSeconds - ) { + if (timestamp >= _mintStages[i].startTimeUnixSeconds && timestamp < _mintStages[i].endTimeUnixSeconds) { return i; } } @@ -589,10 +530,7 @@ contract ERC721M is /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ - function _assertValidStartAndEndTimestamp( - uint64 start, - uint64 end - ) internal pure { + function _assertValidStartAndEndTimestamp(uint64 start, uint64 end) internal pure { if (start >= end) revert InvalidStartAndEndTimestamp(); } diff --git a/contracts/nft/erc721m/extensions/BucketAuction.sol b/contracts/nft/erc721m/extensions/BucketAuction.sol index b639a9de..527c787b 100644 --- a/contracts/nft/erc721m/extensions/BucketAuction.sol +++ b/contracts/nft/erc721m/extensions/BucketAuction.sol @@ -62,18 +62,16 @@ contract BucketAuction is IBucketAuction, ERC721M { } modifier isAuctionActive() { - if ( - _startTimeUnixSeconds > block.timestamp || - _endTimeUnixSeconds <= block.timestamp - ) revert BucketAuctionNotActive(); + if (_startTimeUnixSeconds > block.timestamp || _endTimeUnixSeconds <= block.timestamp) { + revert BucketAuctionNotActive(); + } _; } modifier isAuctionInactive() { - if ( - _startTimeUnixSeconds <= block.timestamp && - block.timestamp < _endTimeUnixSeconds - ) revert BucketAuctionActive(); + if (_startTimeUnixSeconds <= block.timestamp && block.timestamp < _endTimeUnixSeconds) { + revert BucketAuctionActive(); + } _; } @@ -94,19 +92,18 @@ contract BucketAuction is IBucketAuction, ERC721M { } function getAuctionActive() external view returns (bool) { - return - _startTimeUnixSeconds <= block.timestamp && - block.timestamp < _endTimeUnixSeconds; + return _startTimeUnixSeconds <= block.timestamp && block.timestamp < _endTimeUnixSeconds; } function getUserData(address user) external view returns (User memory) { return _userData[user]; } - function getUserDataPage( - uint256 limit, - uint256 offset - ) external view returns (User[] memory, address[] memory, uint256 total) { + function getUserDataPage(uint256 limit, uint256 offset) + external + view + returns (User[] memory, address[] memory, uint256 total) + { uint256 numUsers = _users.length(); uint256 pageSize = limit; if (pageSize > numUsers - offset) { @@ -144,10 +141,7 @@ contract BucketAuction is IBucketAuction, ERC721M { * @param startTime set to unix timestamp for the auction start time. * @param endTime set to unix timestamp for the auction end time. */ - function setStartAndEndTimeUnixSeconds( - uint64 startTime, - uint64 endTime - ) external onlyOwner { + function setStartAndEndTimeUnixSeconds(uint64 startTime, uint64 endTime) external onlyOwner { if (_price != 0) revert PriceHasBeenSet(); if (endTime <= startTime) revert InvalidStartAndEndTimestamp(); @@ -167,8 +161,9 @@ contract BucketAuction is IBucketAuction, ERC721M { // does not overflow contribution_ += msg.value; } - if (contribution_ < _minimumContributionInWei) + if (contribution_ < _minimumContributionInWei) { revert LowerThanMinBidAmount(); + } bidder.contribution = uint216(contribution_); _users.add(msg.sender); @@ -181,9 +176,7 @@ contract BucketAuction is IBucketAuction, ERC721M { * @dev set this price in wei, not eth! * @param minimumContributionInWei new price, set in wei */ - function setMinimumContribution( - uint256 minimumContributionInWei - ) external onlyOwner { + function setMinimumContribution(uint256 minimumContributionInWei) external onlyOwner { _minimumContributionInWei = minimumContributionInWei; emit SetMinimumContribution(minimumContributionInWei); } @@ -195,8 +188,9 @@ contract BucketAuction is IBucketAuction, ERC721M { */ function setPrice(uint256 priceInWei) external onlyOwner { if (_claimable) revert CannotSetPriceIfClaimable(); - if (block.timestamp <= _endTimeUnixSeconds) + if (block.timestamp <= _endTimeUnixSeconds) { revert BucketAuctionActive(); + } if (_firstTokenSent) revert CannotSetPriceIfFirstTokenSent(); _price = priceInWei; @@ -208,10 +202,7 @@ contract BucketAuction is IBucketAuction, ERC721M { * @param to address to mint tokens to. * @param numberOfTokens number of tokens to mint. */ - function _internalMint( - address to, - uint256 numberOfTokens - ) internal hasSupply(numberOfTokens) { + function _internalMint(address to, uint256 numberOfTokens) internal hasSupply(numberOfTokens) { _safeMint(to, numberOfTokens); if (!_firstTokenSent && numberOfTokens > 0) _firstTokenSent = true; } @@ -229,8 +220,9 @@ contract BucketAuction is IBucketAuction, ERC721M { uint256 claimed = user.tokensClaimed; // user.tokensClaimed is uint32 claimed += n; - if (claimed > (user.contribution / price)) + if (claimed > (user.contribution / price)) { revert CannotSendMoreThanUserPurchased(); + } user.tokensClaimed = uint32(claimed); _internalMint(to, n); } @@ -293,7 +285,7 @@ contract BucketAuction is IBucketAuction, ERC721M { user.refundClaimed = true; uint256 refundValue = user.contribution % price; - (bool success, ) = to.call{value: refundValue}(""); + (bool success,) = to.call{value: refundValue}(""); if (!success) revert TransferFailed(); } @@ -341,7 +333,7 @@ contract BucketAuction is IBucketAuction, ERC721M { if (user.refundClaimed) revert UserAlreadyClaimed(); user.refundClaimed = true; uint256 refundValue = user.contribution % price; - (bool success, ) = to.call{value: refundValue}(""); + (bool success,) = to.call{value: refundValue}(""); if (!success) revert TransferFailed(); // send tokens @@ -357,9 +349,7 @@ contract BucketAuction is IBucketAuction, ERC721M { * @notice send refunds and tokens to a batch of addresses. * @param addresses array of addresses to send tokens to. */ - function sendTokensAndRefundBatch( - address[] calldata addresses - ) external onlyOwner { + function sendTokensAndRefundBatch(address[] calldata addresses) external onlyOwner { for (uint256 i; i < addresses.length; i++) { sendTokensAndRefund(addresses[i]); } diff --git a/contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol b/contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol index be3ec60b..6e7c58b1 100644 --- a/contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol +++ b/contracts/nft/erc721m/extensions/ERC721CMBasicRoyalties.sol @@ -36,17 +36,13 @@ contract ERC721CMBasicRoyalties is ERC721CM, BasicRoyalties { BasicRoyalties(royaltyReceiver, royaltyFeeNumerator) {} - function supportsInterface( - bytes4 interfaceId - ) + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC2981, ERC721ACQueryable, IERC721A) returns (bool) { - return - ERC721ACQueryable.supportsInterface(interfaceId) || - ERC2981.supportsInterface(interfaceId); + return ERC721ACQueryable.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); } } diff --git a/contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol b/contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol index 38396294..1774e4c2 100644 --- a/contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol +++ b/contracts/nft/erc721m/extensions/ERC721CMRoyalties.sol @@ -36,17 +36,13 @@ contract ERC721CMRoyalties is ERC721CM, UpdatableRoyalties { UpdatableRoyalties(royaltyReceiver, royaltyFeeNumerator) {} - function supportsInterface( - bytes4 interfaceId - ) + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC2981, ERC721ACQueryable, IERC721A) returns (bool) { - return - ERC721ACQueryable.supportsInterface(interfaceId) || - ERC2981.supportsInterface(interfaceId); + return ERC721ACQueryable.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId); } } diff --git a/contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol b/contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol index eca6b431..b307ddfe 100644 --- a/contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol +++ b/contracts/nft/erc721m/extensions/ERC721MOperatorFilterer.sol @@ -18,11 +18,7 @@ contract ERC721MOperatorFilterer is ERC721M, UpdatableOperatorFilterer { address mintCurrency, address fundReceiver ) - UpdatableOperatorFilterer( - CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS, - ME_SUBSCRIPTION, - true - ) + UpdatableOperatorFilterer(CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS, ME_SUBSCRIPTION, true) ERC721M( collectionName, collectionSymbol, @@ -36,37 +32,34 @@ contract ERC721MOperatorFilterer is ERC721M, UpdatableOperatorFilterer { ) {} - function owner() - public - view - override(Ownable, UpdatableOperatorFilterer) - returns (address) - { + function owner() public view override(Ownable, UpdatableOperatorFilterer) returns (address) { return Ownable.owner(); } - function transferFrom( - address from, - address to, - uint256 tokenId - ) public payable override(ERC721A, IERC721A) onlyAllowedOperator(from) { + function transferFrom(address from, address to, uint256 tokenId) + public + payable + override(ERC721A, IERC721A) + onlyAllowedOperator(from) + { super.transferFrom(from, to, tokenId); } - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public payable override(ERC721A, IERC721A) onlyAllowedOperator(from) { + function safeTransferFrom(address from, address to, uint256 tokenId) + public + payable + override(ERC721A, IERC721A) + onlyAllowedOperator(from) + { super.safeTransferFrom(from, to, tokenId); } - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory data - ) public payable override(ERC721A, IERC721A) onlyAllowedOperator(from) { + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) + public + payable + override(ERC721A, IERC721A) + onlyAllowedOperator(from) + { super.safeTransferFrom(from, to, tokenId, data); } } diff --git a/contracts/nft/erc721m/interfaces/IBucketAuction.sol b/contracts/nft/erc721m/interfaces/IBucketAuction.sol index 332641cd..af16c890 100644 --- a/contracts/nft/erc721m/interfaces/IBucketAuction.sol +++ b/contracts/nft/erc721m/interfaces/IBucketAuction.sol @@ -20,12 +20,7 @@ interface IBucketAuction { bool refundClaimed; // has user been refunded yet } - event Bid( - address indexed bidder, - uint256 bidAmount, - uint256 bidderTotal, - uint256 bucketTotal - ); + event Bid(address indexed bidder, uint256 bidAmount, uint256 bidderTotal, uint256 bucketTotal); event SetMinimumContribution(uint256 minimumContributionInWei); event SetPrice(uint256 price); event SetClaimable(bool claimable); diff --git a/contracts/nft/erc721m/interfaces/IERC721M.sol b/contracts/nft/erc721m/interfaces/IERC721M.sol index 22490d32..2dcf46f0 100644 --- a/contracts/nft/erc721m/interfaces/IERC721M.sol +++ b/contracts/nft/erc721m/interfaces/IERC721M.sol @@ -14,16 +14,9 @@ interface IERC721M is IERC721AQueryable, ERC721MErrorsAndEvents { function totalMintedByAddress(address a) external view returns (uint256); - function getStageInfo( - uint256 index - ) external view returns (MintStageInfo memory, uint32, uint256); + function getStageInfo(uint256 index) external view returns (MintStageInfo memory, uint32, uint256); - function mint( - uint32 qty, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable; + function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable; function mintWithLimit( uint32 qty, @@ -33,13 +26,9 @@ interface IERC721M is IERC721AQueryable, ERC721MErrorsAndEvents { bytes calldata signature ) external payable; - function crossmint( - uint32 qty, - address to, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable; + function crossmint(uint32 qty, address to, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable; function authorizedMint( uint32 qty, diff --git a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol index 15583364..785b3f48 100644 --- a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol +++ b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol @@ -19,16 +19,9 @@ interface IERC721MInitializable is IERC721AQueryableUpgradeable, ERC721MErrorsAn function totalMintedByAddress(address a) external view returns (uint256); - function getStageInfo( - uint256 index - ) external view returns (MintStageInfo memory, uint32, uint256); + function getStageInfo(uint256 index) external view returns (MintStageInfo memory, uint32, uint256); - function mint( - uint32 qty, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable; + function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable; function mintWithLimit( uint32 qty, @@ -38,13 +31,9 @@ interface IERC721MInitializable is IERC721AQueryableUpgradeable, ERC721MErrorsAn bytes calldata signature ) external payable; - function crossmint( - uint32 qty, - address to, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable; + function crossmint(uint32 qty, address to, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable; function authorizedMint( uint32 qty, @@ -54,4 +43,4 @@ interface IERC721MInitializable is IERC721AQueryableUpgradeable, ERC721MErrorsAn uint64 timestamp, bytes calldata signature ) external payable; -} \ No newline at end of file +} diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol index 008efd64..dc7b4ce4 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol +++ b/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol @@ -8,7 +8,11 @@ import {Ownable} from "solady/src/auth/Ownable.sol"; import {ReentrancyGuard} from "solady/src/utils/ReentrancyGuard.sol"; import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol"; -import {ERC721ACQueryableInitializable, ERC721AUpgradeable, IERC721AUpgradeable} from "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; +import { + ERC721ACQueryableInitializable, + ERC721AUpgradeable, + IERC721AUpgradeable +} from "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; import {MINT_FEE_RECEIVER} from "../../../utils/Constants.sol"; import {MintStageInfo} from "../../../common/Structs.sol"; import {IERC721MInitializable} from "../interfaces/IERC721MInitializable.sol"; @@ -54,8 +58,7 @@ contract ERC721CMInitializableV2 is MintStageInfo[] private _mintStages; // Minted count per stage per wallet. - mapping(uint256 => mapping(address => uint32)) - private _stageMintedCountsPerWallet; + mapping(uint256 => mapping(address => uint32)) private _stageMintedCountsPerWallet; // Minted count per stage. mapping(uint256 => uint256) private _stageMintedCounts; @@ -75,11 +78,11 @@ contract ERC721CMInitializableV2 is _disableInitializers(); } - function initialize( - string calldata name, - string calldata symbol, - address initialOwner - ) external initializerERC721A initializer { + function initialize(string calldata name, string calldata symbol, address initialOwner) + external + initializerERC721A + initializer + { __ERC721ACQueryableInitializable_init(name, symbol); _initializeOwner(initialOwner); } @@ -96,8 +99,9 @@ contract ERC721CMInitializableV2 is address defaultRoyaltyReceiver, uint96 defaultRoyaltyFeeNumerator ) external onlyOwner { - if (globalWalletLimit > maxMintableSupply) + if (globalWalletLimit > maxMintableSupply) { revert GlobalWalletLimitOverflow(); + } _mintable = true; _maxMintableSupply = maxMintableSupply; _globalWalletLimit = globalWalletLimit; @@ -117,20 +121,13 @@ contract ERC721CMInitializableV2 is function _setStages(MintStageInfo[] calldata newStages) internal { delete _mintStages; - for (uint256 i = 0; i < newStages.length; ) { + for (uint256 i = 0; i < newStages.length;) { if (i >= 1) { - if ( - newStages[i].startTimeUnixSeconds < - newStages[i - 1].endTimeUnixSeconds + - _timestampExpirySeconds - ) { + if (newStages[i].startTimeUnixSeconds < newStages[i - 1].endTimeUnixSeconds + _timestampExpirySeconds) { revert InsufficientStageTimeGap(); } } - _assertValidStartAndEndTimestamp( - newStages[i].startTimeUnixSeconds, - newStages[i].endTimeUnixSeconds - ); + _assertValidStartAndEndTimestamp(newStages[i].startTimeUnixSeconds, newStages[i].endTimeUnixSeconds); _mintStages.push( MintStageInfo({ price: newStages[i].price, @@ -197,14 +194,14 @@ contract ERC721CMInitializableV2 is /** * @dev Add authorized minter. Can only be called by contract owner. */ - function addAuthorizedMinter(address minter) external onlyOwner override { + function addAuthorizedMinter(address minter) external override onlyOwner { _addAuthorizedMinter(minter); } /** * @dev Remove authorized minter. Can only be called by contract owner. */ - function removeAuthorizedMinter(address minter) external onlyOwner override { + function removeAuthorizedMinter(address minter) external override onlyOwner { _removeAuthorizedMinter(minter); } @@ -216,7 +213,6 @@ contract ERC721CMInitializableV2 is emit SetCosigner(cosigner); } - /** * @dev Gets whether mintable. */ @@ -251,9 +247,7 @@ contract ERC721CMInitializableV2 is * * New supply cannot be larger than the old. */ - function setMaxMintableSupply( - uint256 maxMintableSupply - ) external virtual onlyOwner { + function setMaxMintableSupply(uint256 maxMintableSupply) external virtual onlyOwner { if (maxMintableSupply > _maxMintableSupply) { revert CannotIncreaseMaxMintableSupply(); } @@ -271,11 +265,10 @@ contract ERC721CMInitializableV2 is /** * @dev Sets global wallet limit. */ - function setGlobalWalletLimit( - uint256 globalWalletLimit - ) external onlyOwner { - if (globalWalletLimit > _maxMintableSupply) + function setGlobalWalletLimit(uint256 globalWalletLimit) external onlyOwner { + if (globalWalletLimit > _maxMintableSupply) { revert GlobalWalletLimitOverflow(); + } _globalWalletLimit = globalWalletLimit; emit SetGlobalWalletLimit(globalWalletLimit); } @@ -283,18 +276,14 @@ contract ERC721CMInitializableV2 is /** * @dev Returns number of minted token for a given address. */ - function totalMintedByAddress( - address a - ) external view virtual override returns (uint256) { + function totalMintedByAddress(address a) external view virtual override returns (uint256) { return _numberMinted(a); } /** * @dev Returns info for one stage specified by index (starting from 0). */ - function getStageInfo( - uint256 index - ) external view override returns (MintStageInfo memory, uint32, uint256) { + function getStageInfo(uint256 index) external view override returns (MintStageInfo memory, uint32, uint256) { if (index >= _mintStages.length) { revert InvalidStage(); } @@ -310,7 +299,6 @@ contract ERC721CMInitializableV2 is return _mintCurrency; } - /** * @dev Mints token(s). * @@ -319,12 +307,12 @@ contract ERC721CMInitializableV2 is * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function mint( - uint32 qty, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { + function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable + virtual + nonReentrant + { _mintInternal(qty, msg.sender, 0, proof, timestamp, signature); } @@ -356,13 +344,11 @@ contract ERC721CMInitializableV2 is * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function crossmint( - uint32 qty, - address to, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable nonReentrant { + function crossmint(uint32 qty, address to, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable + nonReentrant + { if (_crossmintAddress == address(0)) revert CrossmintAddressNotSet(); // Check the caller is Crossmint @@ -407,13 +393,7 @@ contract ERC721CMInitializableV2 is bool waiveMintFee = false; if (_cosigner != address(0)) { - waiveMintFee = assertValidCosign( - msg.sender, - qty, - timestamp, - signature, - getCosignNonce(msg.sender) - ); + waiveMintFee = assertValidCosign(msg.sender, qty, timestamp, signature, getCosignNonce(msg.sender)); _assertValidTimestamp(timestamp); stageTimestamp = timestamp; } @@ -424,29 +404,27 @@ contract ERC721CMInitializableV2 is uint80 adjustedMintFee = waiveMintFee ? 0 : stage.mintFee; // Check value if minting with ETH - if ( - _mintCurrency == address(0) && - msg.value < (stage.price + adjustedMintFee) * qty - ) revert NotEnoughValue(); + if (_mintCurrency == address(0) && msg.value < (stage.price + adjustedMintFee) * qty) revert NotEnoughValue(); // Check stage supply if applicable if (stage.maxStageSupply > 0) { - if (_stageMintedCounts[activeStage] + qty > stage.maxStageSupply) + if (_stageMintedCounts[activeStage] + qty > stage.maxStageSupply) { revert StageSupplyExceeded(); + } } // Check global wallet limit if applicable if (_globalWalletLimit > 0) { - if (_numberMinted(to) + qty > _globalWalletLimit) + if (_numberMinted(to) + qty > _globalWalletLimit) { revert WalletGlobalLimitExceeded(); + } } // Check wallet limit for stage if applicable, limit == 0 means no limit enforced if (stage.walletLimit > 0) { - if ( - _stageMintedCountsPerWallet[activeStage][to] + qty > - stage.walletLimit - ) revert WalletStageLimitExceeded(); + if (_stageMintedCountsPerWallet[activeStage][to] + qty > stage.walletLimit) { + revert WalletStageLimitExceeded(); + } } // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required @@ -456,20 +434,14 @@ contract ERC721CMInitializableV2 is } // Verify merkle proof mint limit - if ( - limit > 0 && - _stageMintedCountsPerWallet[activeStage][to] + qty > limit - ) { + if (limit > 0 && _stageMintedCountsPerWallet[activeStage][to] + qty > limit) { revert WalletStageLimitExceeded(); } } if (_mintCurrency != address(0)) { SafeTransferLib.safeTransferFrom( - _mintCurrency, - msg.sender, - address(this), - (stage.price + adjustedMintFee) * qty + _mintCurrency, msg.sender, address(this), (stage.price + adjustedMintFee) * qty ); } @@ -486,10 +458,7 @@ contract ERC721CMInitializableV2 is * NOTE: This function bypasses validations thus only available for owner. * This is typically used for owner to pre-mint or mint the remaining of the supply. */ - function ownerMint( - uint32 qty, - address to - ) external onlyOwner hasSupply(qty) { + function ownerMint(uint32 qty, address to) external onlyOwner hasSupply(qty) { _safeMint(to, qty); } @@ -497,12 +466,12 @@ contract ERC721CMInitializableV2 is * @dev Withdraws funds by owner. */ function withdraw() external onlyOwner { - (bool success, ) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); + (bool success,) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); if (!success) revert TransferFailed(); _totalMintFee = 0; uint256 remainingValue = address(this).balance; - (success, ) = _fundReceiver.call{value: remainingValue}(""); + (success,) = _fundReceiver.call{value: remainingValue}(""); if (!success) revert WithdrawFailed(); emit Withdraw(_totalMintFee + remainingValue); @@ -516,7 +485,7 @@ contract ERC721CMInitializableV2 is uint256 totalFee = _totalMintFee; uint256 remaining = SafeTransferLib.balanceOf(_mintCurrency, address(this)); - + if (remaining < totalFee) revert InsufficientBalance(); _totalMintFee = 0; @@ -546,9 +515,7 @@ contract ERC721CMInitializableV2 is /** * @dev Returns token URI for a given token id. */ - function tokenURI( - uint256 tokenId - ) + function tokenURI(uint256 tokenId) public view override(ERC721AUpgradeable, IERC721AUpgradeable) @@ -557,16 +524,7 @@ contract ERC721CMInitializableV2 is if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); string memory baseURI = _currentBaseURI; - return - bytes(baseURI).length != 0 - ? string( - abi.encodePacked( - baseURI, - _toString(tokenId), - _tokenURISuffix - ) - ) - : ""; + return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, _toString(tokenId), _tokenURISuffix)) : ""; } /** @@ -586,14 +544,9 @@ contract ERC721CMInitializableV2 is /** * @dev Returns the current active stage based on timestamp. */ - function getActiveStageFromTimestamp( - uint64 timestamp - ) public view returns (uint256) { - for (uint256 i = 0; i < _mintStages.length; ) { - if ( - timestamp >= _mintStages[i].startTimeUnixSeconds && - timestamp < _mintStages[i].endTimeUnixSeconds - ) { + function getActiveStageFromTimestamp(uint64 timestamp) public view returns (uint256) { + for (uint256 i = 0; i < _mintStages.length;) { + if (timestamp >= _mintStages[i].startTimeUnixSeconds && timestamp < _mintStages[i].endTimeUnixSeconds) { return i; } unchecked { @@ -606,10 +559,7 @@ contract ERC721CMInitializableV2 is /** * @dev Validates the start timestamp is before end timestamp. Used when updating stages. */ - function _assertValidStartAndEndTimestamp( - uint64 start, - uint64 end - ) internal pure { + function _assertValidStartAndEndTimestamp(uint64 start, uint64 end) internal pure { if (start >= end) revert InvalidStartAndEndTimestamp(); } @@ -624,24 +574,25 @@ contract ERC721CMInitializableV2 is } /** - * @notice Returns the function selector for the transfer validator's validation function to be called - * @notice for transaction simulation. + * @notice Returns the function selector for the transfer validator's validation function to be called + * @notice for transaction simulation. */ function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); isViewFunction = true; } - function supportsInterface(bytes4 interfaceId) public view override(ERC2981, IERC721AUpgradeable, ERC721ACQueryableInitializable) returns (bool) { - return super.supportsInterface(interfaceId) || - ERC2981.supportsInterface(interfaceId) || - ERC721ACQueryableInitializable.supportsInterface(interfaceId); + function supportsInterface(bytes4 interfaceId) + public + view + override(ERC2981, IERC721AUpgradeable, ERC721ACQueryableInitializable) + returns (bool) + { + return super.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId) + || ERC721ACQueryableInitializable.supportsInterface(interfaceId); } - function setDefaultRoyalty( - address receiver, - uint96 feeNumerator - ) public onlyOwner { + function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { super._setDefaultRoyalty(receiver, feeNumerator); emit SetDefaultRoyalty(receiver, feeNumerator); } diff --git a/contracts/royalties/UpdatableRoyalties.sol b/contracts/royalties/UpdatableRoyalties.sol index 48d395a5..da9ff7e9 100644 --- a/contracts/royalties/UpdatableRoyalties.sol +++ b/contracts/royalties/UpdatableRoyalties.sol @@ -9,29 +9,18 @@ import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; */ abstract contract UpdatableRoyalties is ERC2981, Ownable { event DefaultRoyaltySet(address indexed receiver, uint96 feeNumerator); - event TokenRoyaltySet( - uint256 indexed tokenId, - address indexed receiver, - uint96 feeNumerator - ); + event TokenRoyaltySet(uint256 indexed tokenId, address indexed receiver, uint96 feeNumerator); constructor(address receiver, uint96 feeNumerator) { _setDefaultRoyalty(receiver, feeNumerator); } - function setDefaultRoyalty( - address receiver, - uint96 feeNumerator - ) public onlyOwner { + function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { super._setDefaultRoyalty(receiver, feeNumerator); emit DefaultRoyaltySet(receiver, feeNumerator); } - function setTokenRoyalty( - uint256 tokenId, - address receiver, - uint96 feeNumerator - ) public onlyOwner { + function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) public onlyOwner { super._setTokenRoyalty(tokenId, receiver, feeNumerator); emit TokenRoyaltySet(tokenId, receiver, feeNumerator); } diff --git a/contracts/royalties/UpdatableRoyaltiesInitializable.sol b/contracts/royalties/UpdatableRoyaltiesInitializable.sol index 90f679ec..491463db 100644 --- a/contracts/royalties/UpdatableRoyaltiesInitializable.sol +++ b/contracts/royalties/UpdatableRoyaltiesInitializable.sol @@ -10,30 +10,16 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this * contract is for use with EIP-1167 Minimal Proxies (Clones). */ -abstract contract UpdatableRoyaltiesInitializable is - ERC2981, - OwnableInitializable -{ +abstract contract UpdatableRoyaltiesInitializable is ERC2981, OwnableInitializable { event DefaultRoyaltySet(address indexed receiver, uint96 feeNumerator); - event TokenRoyaltySet( - uint256 indexed tokenId, - address indexed receiver, - uint96 feeNumerator - ); + event TokenRoyaltySet(uint256 indexed tokenId, address indexed receiver, uint96 feeNumerator); - function setDefaultRoyalty( - address receiver, - uint96 feeNumerator - ) public onlyOwner { + function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { super._setDefaultRoyalty(receiver, feeNumerator); emit DefaultRoyaltySet(receiver, feeNumerator); } - function setTokenRoyalty( - uint256 tokenId, - address receiver, - uint96 feeNumerator - ) public onlyOwner { + function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) public onlyOwner { super._setTokenRoyalty(tokenId, receiver, feeNumerator); emit TokenRoyaltySet(tokenId, receiver, feeNumerator); } diff --git a/contracts/utils/ERC721BatchTransfer.sol b/contracts/utils/ERC721BatchTransfer.sol index 5ba28651..79977e1c 100644 --- a/contracts/utils/ERC721BatchTransfer.sol +++ b/contracts/utils/ERC721BatchTransfer.sol @@ -14,16 +14,9 @@ contract ERC721BatchTransfer { error InvalidArguments(); error NotOwnerOfToken(); - event BatchTransferToSingle( - address indexed contractAddress, - address indexed to, - uint256 amount - ); + event BatchTransferToSingle(address indexed contractAddress, address indexed to, uint256 amount); - event BatchTransferToMultiple( - address indexed contractAddress, - uint256 amount - ); + event BatchTransferToMultiple(address indexed contractAddress, uint256 amount); /** * @notice Transfer multiple tokens to the same wallet using the ERC721.transferFrom method. @@ -32,13 +25,9 @@ contract ERC721BatchTransfer { * @param to the address that will receive the nfts * @param tokenIds the list of tokens that will be transferred */ - function batchTransferToSingleWallet( - IERC721 erc721Contract, - address to, - uint256[] calldata tokenIds - ) external { + function batchTransferToSingleWallet(IERC721 erc721Contract, address to, uint256[] calldata tokenIds) external { uint256 length = tokenIds.length; - for (uint256 i; i < length; ) { + for (uint256 i; i < length;) { uint256 tokenId = tokenIds[i]; address owner = erc721Contract.ownerOf(tokenId); if (msg.sender != owner) { @@ -58,13 +47,11 @@ contract ERC721BatchTransfer { * @param to the address that will receive the nfts * @param tokenIds the list of tokens that will be transferred */ - function safeBatchTransferToSingleWallet( - IERC721 erc721Contract, - address to, - uint256[] calldata tokenIds - ) external { + function safeBatchTransferToSingleWallet(IERC721 erc721Contract, address to, uint256[] calldata tokenIds) + external + { uint256 length = tokenIds.length; - for (uint256 i; i < length; ) { + for (uint256 i; i < length;) { uint256 tokenId = tokenIds[i]; address owner = erc721Contract.ownerOf(tokenId); if (msg.sender != owner) { @@ -90,15 +77,13 @@ contract ERC721BatchTransfer { * @param tos the list of addresses that will receive the nfts * @param tokenIds the list of tokens that will be transferred */ - function batchTransferToMultipleWallets( - IERC721 erc721Contract, - address[] calldata tos, - uint256[] calldata tokenIds - ) external { + function batchTransferToMultipleWallets(IERC721 erc721Contract, address[] calldata tos, uint256[] calldata tokenIds) + external + { uint256 length = tokenIds.length; if (tos.length != length) revert InvalidArguments(); - for (uint256 i; i < length; ) { + for (uint256 i; i < length;) { uint256 tokenId = tokenIds[i]; address owner = erc721Contract.ownerOf(tokenId); address to = tos[i]; @@ -133,7 +118,7 @@ contract ERC721BatchTransfer { uint256 length = tokenIds.length; if (tos.length != length) revert InvalidArguments(); - for (uint256 i; i < length; ) { + for (uint256 i; i < length;) { uint256 tokenId = tokenIds[i]; address owner = erc721Contract.ownerOf(tokenId); address to = tos[i]; diff --git a/test/erc721m/ERC721CMInitializableV2Test.t.sol b/test/erc721m/ERC721CMInitializableV2Test.t.sol index f57097af..658bfb45 100644 --- a/test/erc721m/ERC721CMInitializableV2Test.t.sol +++ b/test/erc721m/ERC721CMInitializableV2Test.t.sol @@ -30,7 +30,6 @@ contract ERC721CMInitializableV2Test is Test { vm.deal(owner, 10 ether); vm.deal(minter, 2 ether); vm.deal(crossmintAddress, 1 ether); - address clone = LibClone.deployERC1967(address(new ERC721CMInitializableV2())); nft = ERC721CMInitializableV2(clone); @@ -197,4 +196,4 @@ contract ERC721CMInitializableV2Test is Test { nft.setContractURI(uri); assertEq(nft.contractURI(), uri); } -} \ No newline at end of file +} diff --git a/test/factory/MagicDropCloneFactoryTest.t.sol b/test/factory/MagicDropCloneFactoryTest.t.sol index b85088a7..56e1f27b 100644 --- a/test/factory/MagicDropCloneFactoryTest.t.sol +++ b/test/factory/MagicDropCloneFactoryTest.t.sol @@ -21,7 +21,7 @@ contract MagicDropCloneFactoryTest is Test { function setUp() public { vm.startPrank(owner); - + // Deploy and initialize registry registry = new MagicDropTokenImplRegistry(); registry.initialize(); @@ -43,15 +43,9 @@ contract MagicDropCloneFactoryTest is Test { function testCreateERC721Contract() public { vm.startPrank(user); - - address newContract = factory.createContract( - "TestNFT", - "TNFT", - TokenStandard.ERC721, - payable(user), - erc721ImplId, - bytes32(0) - ); + + address newContract = + factory.createContract("TestNFT", "TNFT", TokenStandard.ERC721, payable(user), erc721ImplId, bytes32(0)); MagicDropERC721Initializable nft = MagicDropERC721Initializable(newContract); assertEq(nft.name(), "TestNFT"); @@ -67,14 +61,9 @@ contract MagicDropCloneFactoryTest is Test { function testCreateERC1155Contract() public { vm.startPrank(user); - + address newContract = factory.createContract( - "TestMultiToken", - "TMT", - TokenStandard.ERC1155, - payable(user), - erc1155ImplId, - bytes32(0) + "TestMultiToken", "TMT", TokenStandard.ERC1155, payable(user), erc1155ImplId, bytes32(0) ); MagicDropERC1155Initializable multiToken = MagicDropERC1155Initializable(newContract); @@ -91,23 +80,13 @@ contract MagicDropCloneFactoryTest is Test { function testCreateContractWithDifferentSalts() public { vm.startPrank(user); - + address contract1 = factory.createContract( - "TestNFT1", - "TNFT1", - TokenStandard.ERC721, - payable(user), - erc721ImplId, - bytes32(uint256(1)) + "TestNFT1", "TNFT1", TokenStandard.ERC721, payable(user), erc721ImplId, bytes32(uint256(1)) ); address contract2 = factory.createContract( - "TestNFT2", - "TNFT2", - TokenStandard.ERC721, - payable(user), - erc721ImplId, - bytes32(uint256(2)) + "TestNFT2", "TNFT2", TokenStandard.ERC721, payable(user), erc721ImplId, bytes32(uint256(2)) ); assertTrue(contract1 != contract2); @@ -119,13 +98,6 @@ contract MagicDropCloneFactoryTest is Test { uint256 invalidImplId = 999; vm.prank(user); - factory.createContract( - "TestNFT", - "TNFT", - TokenStandard.ERC721, - payable(user), - invalidImplId, - bytes32(0) - ); + factory.createContract("TestNFT", "TNFT", TokenStandard.ERC721, payable(user), invalidImplId, bytes32(0)); } } diff --git a/test/factory/MagicDropTokenImplRegistryTest.t.sol b/test/factory/MagicDropTokenImplRegistryTest.t.sol index fcd6f434..13fecd73 100644 --- a/test/factory/MagicDropTokenImplRegistryTest.t.sol +++ b/test/factory/MagicDropTokenImplRegistryTest.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {MagicDropTokenImplRegistry} from "../../contracts/registry/MagicDropTokenImplRegistry.sol"; -import {TokenStandard} from "../../contracts/common/Structs.sol"; +import {TokenStandard} from "../../contracts/common/Structs.sol"; import {MockERC721A} from "../../contracts/mocks/MockERC721A.sol"; contract MagicDropTokenImplRegistryTest is Test { @@ -61,7 +61,7 @@ contract MagicDropTokenImplRegistryTest is Test { function testFailUnregisterImplementationAsNonOwner() public { vm.prank(owner); uint256 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); - + vm.prank(user); registry.unregisterImplementation(TokenStandard.ERC721, implId); } From bd8ff948005d5f571fab3161848e1cb1c2d99577 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Sun, 6 Oct 2024 11:41:38 -0400 Subject: [PATCH 043/145] cleanup Signed-off-by: Wolfy --- .gitignore | 2 +- contracts/common/AuthorizedMinterControl.sol | 83 +++--- contracts/common/Cosignable.sol | 63 ++++- contracts/factory/MagicDropCloneFactory.sol | 244 ++++++++++++++++-- .../interfaces/IMagicDropCloneFactory.sol | 27 -- .../registry/MagicDropTokenImplRegistry.sol | 221 +++++++++++----- .../IMagicDropTokenImplRegistry.sol | 11 +- test/common/AuthorizedMinterControlTest.t.sol | 56 ++++ test/common/CosignableTest.t.sol | 122 +++++++++ test/factory/MagicDropCloneFactoryTest.t.sol | 115 ++++++--- .../MagicDropTokenImplRegistryTest.t.sol | 77 +++--- 11 files changed, 799 insertions(+), 222 deletions(-) delete mode 100644 contracts/factory/interfaces/IMagicDropCloneFactory.sol create mode 100644 test/common/AuthorizedMinterControlTest.t.sol create mode 100644 test/common/CosignableTest.t.sol diff --git a/.gitignore b/.gitignore index 27c093a6..a7133c7c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ dist out/ lcov.info lcov.info.pruned -coverage/ \ No newline at end of file +coverage/ diff --git a/contracts/common/AuthorizedMinterControl.sol b/contracts/common/AuthorizedMinterControl.sol index e2051abb..c1938c87 100644 --- a/contracts/common/AuthorizedMinterControl.sol +++ b/contracts/common/AuthorizedMinterControl.sol @@ -1,61 +1,78 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -/** - * @title AuthorizedMinterControl - * @dev Abstract contract to manage authorized minters for ERC1155M tokens - */ +///@title AuthorizedMinterControl +///@dev Abstract contract to manage authorized minters for MagicDrop tokens abstract contract AuthorizedMinterControl { + /*============================================================== + = STORAGE = + ==============================================================*/ + mapping(address => bool) private _authorizedMinters; + /*============================================================== + = EVENTS = + ==============================================================*/ + event AuthorizedMinterAdded(address indexed minter); event AuthorizedMinterRemoved(address indexed minter); + /*============================================================== + = ERRORS = + ==============================================================*/ + error NotAuthorized(); - /** - * @dev Modifier to check if the sender is an authorized minter - */ + /*============================================================== + = MODIFIERS = + ==============================================================*/ + + ///@dev Modifier to check if the sender is an authorized minter modifier onlyAuthorizedMinter() { - if (!isAuthorizedMinter(msg.sender)) revert NotAuthorized(); + if (!_authorizedMinters[msg.sender]) revert NotAuthorized(); _; } - /** - * @dev Add an authorized minter. Implementation should include access control. - * @param minter Address to be added as an authorized minter - */ + /*============================================================== + = PUBLIC WRITE METHODS = + ==============================================================*/ + + ///@dev Add an authorized minter. + ///@dev Please override this function to check if `msg.sender` is authorized + ///@param minter Address to be added as an authorized minter function addAuthorizedMinter(address minter) external virtual; - /** - * @dev Remove an authorized minter. Implementation should include access control. - * @param minter Address to be removed from authorized minters - */ + ///@dev Remove an authorized minter. + ///@dev Please override this function to check if `msg.sender` is authorized + ///@param minter Address to be removed from authorized minters function removeAuthorizedMinter(address minter) external virtual; - /** - * @dev Internal function to add an authorized minter - * @param minter Address to be added as an authorized minter - */ + /*============================================================== + = PUBLIC VIEW METHODS = + ==============================================================*/ + + ///@dev Check if an address is an authorized minter + ///@param minter Address to check + ///@return bool True if the address is an authorized minter, false otherwise + function isAuthorizedMinter(address minter) public view returns (bool) { + return _authorizedMinters[minter]; + } + + /*============================================================== + = INTERNAL HELPERS = + ==============================================================*/ + + ///@dev Internal function to add an authorized minter + ///@param minter Address to be added as an authorized minter function _addAuthorizedMinter(address minter) internal { _authorizedMinters[minter] = true; emit AuthorizedMinterAdded(minter); } - /** - * @dev Internal function to remove an authorized minter - * @param minter Address to be removed from authorized minters - */ + ///@dev Internal function to remove an authorized minter + ///@param minter Address to be removed from authorized minters function _removeAuthorizedMinter(address minter) internal { _authorizedMinters[minter] = false; emit AuthorizedMinterRemoved(minter); } - - /** - * @dev Check if an address is an authorized minter - * @param minter Address to check - * @return bool True if the address is an authorized minter, false otherwise - */ - function isAuthorizedMinter(address minter) public view returns (bool) { - return _authorizedMinters[minter]; - } } diff --git a/contracts/common/Cosignable.sol b/contracts/common/Cosignable.sol index 8b42a57a..22cfae61 100644 --- a/contracts/common/Cosignable.sol +++ b/contracts/common/Cosignable.sol @@ -3,24 +3,65 @@ pragma solidity ^0.8.20; import {SignatureCheckerLib} from "solady/src/utils/SignatureCheckerLib.sol"; +/// @title Cosignable +/// @notice Abstract contract for implementing cosigner functionality +/// @dev This contract should be inherited by contracts that require cosigner approval abstract contract Cosignable { + /*============================================================== + = STORAGE = + ==============================================================*/ + + /// @notice The address of the cosigner address internal _cosigner; + /// @notice The number of seconds after which a timestamp is considered expired uint256 internal _timestampExpirySeconds; + /*============================================================== + = EVENTS = + ==============================================================*/ + + /// @notice Emitted when a new cosigner is set + /// @param cosigner The address of the new cosigner event SetCosigner(address indexed cosigner); + /*============================================================== + = ERRORS = + ==============================================================*/ + + /// @notice Thrown when an operation requires a cosigner but none is set error CosignerNotSet(); + /// @notice Thrown when the provided cosign signature is invalid error InvalidCosignSignature(); + /// @notice Thrown when the provided timestamp has expired error TimestampExpired(); - // @dev: override this function with onlyOwner modifier in the contract that uses this library + /*============================================================== + = PUBLIC WRITE METHODS = + ==============================================================*/ + + /// @notice Sets the cosigner address + /// @dev This function should be overridden with appropriate access control + /// @param cosigner The address of the new cosigner function setCosigner(address cosigner) external virtual; + /// @notice Sets the expiry time for timestamps + /// @param timestampExpirySeconds The number of seconds after which a timestamp is considered expired function setTimestampExpirySeconds(uint256 timestampExpirySeconds) external virtual { _timestampExpirySeconds = timestampExpirySeconds; } - function getCosignDigest(address minter, uint32 qty, bool waiveMintFee, uint64 timestamp, uint256 cosignNonce) + /*============================================================== + = PUBLIC VIEW METHODS = + ==============================================================*/ + + /// @notice Generates a digest for cosigning + /// @param minter The address of the minter + /// @param qty The quantity to mint + /// @param waiveMintFee Whether to waive the mint fee + /// @param timestamp The timestamp of the request + /// @param cosignNonce A nonce to prevent replay attacks + /// @return The generated digest + function getCosignDigest(address minter, uint32 qty, bool waiveMintFee, uint256 timestamp, uint256 cosignNonce) public view returns (bytes32) @@ -36,10 +77,17 @@ abstract contract Cosignable { ); } + /// @notice Verifies the validity of a cosign signature + /// @param minter The address of the minter + /// @param qty The quantity to mint + /// @param timestamp The timestamp of the request + /// @param signature The cosigner's signature + /// @param cosignNonce A nonce to prevent replay attacks + /// @return A boolean indicating whether the mint fee should be waived function assertValidCosign( address minter, uint32 qty, - uint64 timestamp, + uint256 timestamp, bytes memory signature, uint256 cosignNonce ) public view returns (bool) { @@ -62,7 +110,14 @@ abstract contract Cosignable { revert InvalidCosignSignature(); } - function _assertValidTimestamp(uint64 timestamp) internal view { + /*============================================================== + = INTERNAL HELPERS = + ==============================================================*/ + + /// @notice Checks if a given timestamp is valid + /// @param timestamp The timestamp to check + /// @dev Reverts if the timestamp has expired + function _assertValidTimestamp(uint256 timestamp) internal view { if (timestamp < block.timestamp - _timestampExpirySeconds) { revert TimestampExpired(); } diff --git a/contracts/factory/MagicDropCloneFactory.sol b/contracts/factory/MagicDropCloneFactory.sol index a1555db0..57ac149c 100644 --- a/contracts/factory/MagicDropCloneFactory.sol +++ b/contracts/factory/MagicDropCloneFactory.sol @@ -1,53 +1,149 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import {IMagicDropCloneFactory} from "./interfaces/IMagicDropCloneFactory.sol"; -import {IMagicDropTokenImplRegistry} from "../registry/interfaces/IMagicDropTokenImplRegistry.sol"; -import {IInitializableToken} from "../common/interfaces/IInitializableToken.sol"; +import {UUPSUpgradeable} from "solady/src/utils/UUPSUpgradeable.sol"; +import {Initializable} from "solady/src/utils/Initializable.sol"; +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; import {TokenStandard} from "../common/Structs.sol"; -import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {MagicDropTokenImplRegistry} from "../registry/MagicDropTokenImplRegistry.sol"; -contract MagicDropCloneFactory is IMagicDropCloneFactory, Ownable2StepUpgradeable, UUPSUpgradeable { - IMagicDropTokenImplRegistry public immutable REGISTRY; +/// @title MagicDropCloneFactory +/// @notice A factory contract for creating and managing clones of MagicDrop contracts +/// @dev This contract uses the UUPS proxy pattern and is initializable +contract MagicDropCloneFactory is Initializable, Ownable, UUPSUpgradeable { + /*============================================================== + = CONSTANTS = + ==============================================================*/ + + MagicDropTokenImplRegistry private _registry; + bytes4 private constant INITIALIZE_SELECTOR = bytes4(keccak256("initialize(string,string,address)")); + + /*============================================================== + = STRUCTS = + ==============================================================*/ + + struct MagicDropFactoryStorage { + mapping(bytes32 => bool) usedSalts; + } + + /*============================================================== + = STORAGE = + ==============================================================*/ + + // keccak256(abi.encode(uint256(keccak256("magicdrop.factory.MagicDropCloneFactory")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant MAGICDROP_FACTORY_STORAGE = + 0x994baf5eba3c0ba1b1d51c78027db74423206530efe4b85d37542c047fcc6500; + + /*============================================================== + = EVENTS = + ==============================================================*/ + + event MagicDropFactoryInitialized(); + event NewContractInitialized( + address contractAddress, address initialOwner, uint32 implId, TokenStandard standard, string name, string symbol + ); + + /*============================================================== + = ERRORS = + ==============================================================*/ error ImplementationNotRegistered(); + error InitializationFailed(); + error SaltAlreadyUsed(bytes32 salt); + error ContractAlreadyDeployed(address deployedAddress); + error RegistryAddressCannotBeZero(); + error ImplementationDeprecated(); - constructor(IMagicDropTokenImplRegistry _registry) { - if (address(_registry) == address(0)) { - revert ConstructorRegistryAddressCannotBeZero(); - } + /*============================================================== + = INITIALIZER = + ==============================================================*/ - REGISTRY = _registry; + /// @dev Disables initializers to ensure this contract is used by a proxy + constructor() { + _disableInitializers(); } - function initialize() public initializer { - __Ownable2Step_init(); - __Ownable_init(msg.sender); - __UUPSUpgradeable_init(); + /// @notice Initializes the contract + /// @param initialOwner The address of the initial owner + /// @param registry The address of the registry contract + /// @dev This function can only be called once + function initialize(address initialOwner, address registry) public initializer { + if (registry == address(0)) { + revert RegistryAddressCannotBeZero(); + } + + _registry = MagicDropTokenImplRegistry(registry); + _initializeOwner(initialOwner); + emit MagicDropFactoryInitialized(); } - function createContract( + /*============================================================== + = PUBLIC WRITE METHODS = + ==============================================================*/ + + /// @notice Creates a new deterministic clone of a MagicDrop contract + /// @param name The name of the new contract + /// @param symbol The symbol of the new contract + /// @param standard The token standard of the new contract + /// @param initialOwner The initial owner of the new contract + /// @param implId The implementation ID + /// @param salt A unique salt for deterministic address generation + /// @return The address of the newly created contract + function createContractDeterministic( string calldata name, string calldata symbol, TokenStandard standard, address payable initialOwner, - uint256 implId, + uint32 implId, bytes32 salt - ) external override returns (address) { - address impl = REGISTRY.getImplementation(standard, implId); + ) external returns (address) { + // Retrieve the implementation address from the registry + (address impl, bool deprecated) = _registry.getImplementation(standard, implId); + + if (deprecated) { + revert ImplementationDeprecated(); + } if (impl == address(0)) { revert ImplementationNotRegistered(); } - bytes32 cloneSalt = keccak256(abi.encodePacked(salt, blockhash(block.number))); + assembly { + mstore(0x00, salt) + mstore(0x20, MAGICDROP_FACTORY_STORAGE) + let saltUsed := sload(keccak256(0x00, 0x40)) // usedSalts[salt] - address instance = Clones.cloneDeterministic(impl, cloneSalt); + if saltUsed { + mstore(0x00, shl(224, 0x8bae94a6)) // SaltAlreadyUsed(salt) + mstore(0x04, salt) // Store `salt` argument + revert(0x00, 0x24) // Revert with 36 bytes (4-byte selector + 32-byte `salt`) + } + } + + // Predict the address where the contract will be deployed + address predictedAddress = LibClone.predictDeterministicAddress(impl, salt, address(this)); - IInitializableToken(instance).initialize(name, symbol, initialOwner); + // Check if a contract already exists at the predicted address + if (predictedAddress.code.length > 0) { + revert ContractAlreadyDeployed(predictedAddress); + } + + // Create a deterministic clone of the implementation contract + address instance = LibClone.cloneDeterministic(impl, salt); + + // Initialize the newly created contract + (bool success,) = instance.call(abi.encodeWithSelector(INITIALIZE_SELECTOR, name, symbol, initialOwner)); + if (!success) { + revert InitializationFailed(); + } + + assembly { + mstore(0x00, salt) + mstore(0x20, MAGICDROP_FACTORY_STORAGE) + sstore(keccak256(0x00, 0x40), 1) // usedSalts[salt] = true + } emit NewContractInitialized({ contractAddress: instance, @@ -61,7 +157,103 @@ contract MagicDropCloneFactory is IMagicDropCloneFactory, Ownable2StepUpgradeabl return instance; } - function _authorizeUpgrade(address newImplementation) internal override onlyOwner { - // This function is left empty as the onlyOwner modifier handles the authorization + /// @notice Creates a new clone of a MagicDrop contract + /// @param name The name of the new contract + /// @param symbol The symbol of the new contract + /// @param standard The token standard of the new contract + /// @param initialOwner The initial owner of the new contract + /// @param implId The implementation ID + /// @return The address of the newly created contract + function createContract( + string calldata name, + string calldata symbol, + TokenStandard standard, + address payable initialOwner, + uint32 implId + ) external returns (address) { + // Retrieve the implementation address from the registry + (address impl, bool deprecated) = _registry.getImplementation(standard, implId); + + if (deprecated) { + revert ImplementationDeprecated(); + } + + if (impl == address(0)) { + revert ImplementationNotRegistered(); + } + + // Create a non-deterministic clone of the implementation contract + address instance = LibClone.clone(impl); + + // Initialize the newly created contract + (bool success,) = instance.call(abi.encodeWithSelector(INITIALIZE_SELECTOR, name, symbol, initialOwner)); + if (!success) { + revert InitializationFailed(); + } + + emit NewContractInitialized({ + contractAddress: instance, + initialOwner: initialOwner, + implId: implId, + standard: standard, + name: name, + symbol: symbol + }); + + return instance; } + + /*============================================================== + = PUBLIC VIEW METHODS = + ==============================================================*/ + + /// @notice Predicts the deployment address of a deterministic clone + /// @param standard The token standard of the contract + /// @param implId The implementation ID + /// @param salt The salt used for address generation + /// @return The predicted deployment address + function predictDeploymentAddress(TokenStandard standard, uint32 implId, bytes32 salt) + external + view + returns (address) + { + (address impl, bool deprecated) = _registry.getImplementation(standard, implId); + + if (deprecated) { + revert ImplementationDeprecated(); + } + + if (impl == address(0)) { + revert ImplementationNotRegistered(); + } + + return LibClone.predictDeterministicAddress(impl, salt, address(this)); + } + + /// @notice Checks if a salt has been used + /// @param salt The salt to check + /// @return saltUsed Whether the salt has been used + function isSaltUsed(bytes32 salt) external view returns (bool saltUsed) { + assembly { + mstore(0x00, salt) + mstore(0x20, MAGICDROP_FACTORY_STORAGE) + let slot := keccak256(0x00, 0x40) + saltUsed := sload(slot) + } + } + + /// @notice Retrieves the address of the registry contract + /// @return The address of the registry contract + function getRegistry() external view returns (address) { + return address(_registry); + } + + /*============================================================== + = ADMIN OPERATIONS = + ==============================================================*/ + + ///@dev Internal function to authorize an upgrade. + ///@param newImplementation Address of the new implementation. + ///@notice Only the contract owner can upgrade the contract. + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} } diff --git a/contracts/factory/interfaces/IMagicDropCloneFactory.sol b/contracts/factory/interfaces/IMagicDropCloneFactory.sol deleted file mode 100644 index 742702b3..00000000 --- a/contracts/factory/interfaces/IMagicDropCloneFactory.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {TokenStandard} from "../../common/Structs.sol"; - -interface IMagicDropCloneFactory { - error ConstructorRegistryAddressCannotBeZero(); - - event MagicDropFactoryInitialized(); - event NewContractInitialized( - string name, - string symbol, - address indexed contractAddress, - address indexed initialOwner, - uint256 implId, - TokenStandard standard - ); - - function createContract( - string calldata name, - string calldata symbol, - TokenStandard standard, - address payable initialOwner, - uint256 implId, - bytes32 salt - ) external returns (address); -} diff --git a/contracts/registry/MagicDropTokenImplRegistry.sol b/contracts/registry/MagicDropTokenImplRegistry.sol index b4d49657..f9dc38e3 100644 --- a/contracts/registry/MagicDropTokenImplRegistry.sol +++ b/contracts/registry/MagicDropTokenImplRegistry.sol @@ -1,78 +1,177 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import "./interfaces/IMagicDropTokenImplRegistry.sol"; -import {TokenStandard} from "../common/Structs.sol"; - -/** - * @title MagicDropTokenImplRegistry - * @dev A registry for managing token implementation addresses for different token standards. - * This contract is upgradeable and uses the UUPS pattern. - */ -contract MagicDropTokenImplRegistry is IMagicDropTokenImplRegistry, UUPSUpgradeable, Ownable2StepUpgradeable { - mapping(TokenStandard => mapping(uint256 => address)) private implementations; - mapping(TokenStandard => uint256) private nextImplId; - - /** - * @dev Initializes the contract, setting up the owner and UUPS upgradeability. - * This function replaces the constructor for upgradeable contracts. - */ - function initialize() public initializer { - __Ownable2Step_init(); - __Ownable_init(msg.sender); - __UUPSUpgradeable_init(); - - // Initialize nextImplId for each token standard to 1 - nextImplId[TokenStandard.ERC721] = 1; - nextImplId[TokenStandard.ERC1155] = 1; +import {Ownable} from "solady/src/auth/Ownable.sol"; +import {Initializable} from "solady/src/utils/Initializable.sol"; +import {UUPSUpgradeable} from "solady/src/utils/UUPSUpgradeable.sol"; +import {IMagicDropTokenImplRegistry, TokenStandard} from "./interfaces/IMagicDropTokenImplRegistry.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +/// @title MagicDropTokenImplRegistry +/// @dev A registry for managing token implementation addresses for different token standards. +/// This contract is upgradeable and uses the UUPS pattern. +contract MagicDropTokenImplRegistry is Initializable, UUPSUpgradeable, Ownable, IMagicDropTokenImplRegistry { + /*============================================================== + = STRUCTS = + ==============================================================*/ + + struct RegistryData { + bytes4 interfaceId; + uint32 nextImplId; + mapping(uint256 => address) implementations; + mapping(uint256 => bool) deprecatedImplementations; + } + + struct RegistryStorage { + mapping(TokenStandard => RegistryData) tokenStandardData; + } + + /*============================================================== + = STORAGE = + ==============================================================*/ + + // keccak256(abi.encode(uint256(keccak256("magicdrop.registry.MagicDropTokenImplRegistry")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant MAGICDROP_REGISTRY_STORAGE = + 0xfd008fcd1deb21680f735a35fafc51691c5fb3daec313cfea4dc62938bee9000; + + /*============================================================== + = EVENTS = + ==============================================================*/ + + event ImplementationRegistered(TokenStandard standard, address impl, uint32 implId); + event ImplementationDeprecated(TokenStandard standard, uint32 implId); + + /*============================================================== + = ERRORS = + ==============================================================*/ + + error ImplementationNotRegistered(); + error ImplementationAlreadyDeprecated(); + error ImplementationDoesNotSupportStandard(TokenStandard standard); + error UnsupportedTokenStandard(TokenStandard standard); + + /*============================================================== + = INITIALIZER = + ==============================================================*/ + + /// @dev Disables initializers to ensure this contract is used by a proxy + constructor() { + _disableInitializers(); } - /** - * @dev Registers a new implementation for a given token standard. - * @param standard The token standard (ERC721, ERC1155). - * @param impl The address of the implementation contract. - * @notice Only the contract owner can call this function. - * @notice Reverts if an implementation with the same name is already registered. - */ - function registerImplementation(TokenStandard standard, address impl) external onlyOwner returns (uint256) { - uint256 implId = nextImplId[standard]; - implementations[standard][implId] = impl; - nextImplId[standard] = implId + 1; + /// @dev Initializes the contract, setting up the owner and UUPS upgradeability. + /// This function replaces the constructor for upgradeable contracts. + function initialize(address initialOwner) public initializer { + _initializeOwner(initialOwner); + + // Initialize nextImplId and interface IDs for each token standard + RegistryStorage storage data = _loadRegistryStorage(); + data.tokenStandardData[TokenStandard.ERC721].nextImplId = 1; + data.tokenStandardData[TokenStandard.ERC721].interfaceId = 0x80ac58cd; // ERC721 interface ID + + data.tokenStandardData[TokenStandard.ERC1155].nextImplId = 1; + data.tokenStandardData[TokenStandard.ERC1155].interfaceId = 0xd9b67a26; // ERC1155 interface ID + } + + /*============================================================== + = PUBLIC WRITE METHODS = + ==============================================================*/ + + /// @dev Registers a new implementation for a given token standard. + /// @param standard The token standard (ERC721, ERC1155). + /// @param impl The address of the implementation contract. + /// @notice Only the contract owner can call this function. + /// @notice Reverts if an implementation with the same name is already registered. + function registerImplementation(TokenStandard standard, address impl) external onlyOwner returns (uint32) { + RegistryStorage storage data = _loadRegistryStorage(); + bytes4 interfaceId = data.tokenStandardData[standard].interfaceId; + if (interfaceId == 0) { + revert UnsupportedTokenStandard(standard); + } + + if (!IERC165(impl).supportsInterface(interfaceId)) { + revert ImplementationDoesNotSupportStandard(standard); + } + + uint32 implId = data.tokenStandardData[standard].nextImplId; + data.tokenStandardData[standard].implementations[implId] = impl; + data.tokenStandardData[standard].nextImplId = implId + 1; emit ImplementationRegistered(standard, impl, implId); return implId; } - /** - * @dev Unregisters an implementation for a given token standard. - * @param standard The token standard (ERC721, ERC1155). - * @param implId The ID of the implementation to unregister. - * @notice Only the contract owner can call this function. - * @notice Reverts if the implementation is not registered. - */ - function unregisterImplementation(TokenStandard standard, uint256 implId) external onlyOwner { - if (implementations[standard][implId] == address(0)) { + /// @dev Deprecates an implementation for a given token standard. + /// @param standard The token standard (ERC721, ERC1155). + /// @param implId The ID of the implementation to deprecate. + /// @notice Only the contract owner can call this function. + /// @notice Reverts if the implementation is not registered or already deprecated. + function deprecateImplementation(TokenStandard standard, uint32 implId) external onlyOwner { + RegistryStorage storage data = _loadRegistryStorage(); + if (data.tokenStandardData[standard].implementations[implId] == address(0)) { revert ImplementationNotRegistered(); } - delete implementations[standard][implId]; - emit ImplementationUnregistered(standard, implId); + + if (data.tokenStandardData[standard].deprecatedImplementations[implId]) { + revert ImplementationAlreadyDeprecated(); + } + + data.tokenStandardData[standard].deprecatedImplementations[implId] = true; + + emit ImplementationDeprecated(standard, implId); } - /** - * @dev Retrieves the implementation address for a given token standard and implementation name. - * @param standard The token standard (ERC20, ERC721, ERC1155). - * @param implId The ID of the implementation. - * @return The address of the implementation contract. - */ - function getImplementation(TokenStandard standard, uint256 implId) external view returns (address) { - return implementations[standard][implId]; + /*============================================================== + = PUBLIC VIEW METHODS = + ==============================================================*/ + + /// @dev Retrieves the implementation address for a given token standard and implementation ID. + /// @param standard The token standard (ERC721, ERC1155). + /// @param implId The ID of the implementation. + /// @return implAddress The address of the implementation contract. + /// @return isDeprecated Whether the implementation is deprecated. + function getImplementation(TokenStandard standard, uint32 implId) + external + view + returns (address implAddress, bool isDeprecated) + { + assembly { + // Compute s1 = keccak256(abi.encode(standard, MAGICDROP_REGISTRY_STORAGE)) + mstore(0x00, standard) + mstore(0x20, MAGICDROP_REGISTRY_STORAGE) + let s1 := keccak256(0x00, 0x40) + + // Compute storage slot for implementations[implId] + mstore(0x00, implId) + mstore(0x20, add(s1, 1)) + let implSlot := keccak256(0x00, 0x40) + implAddress := sload(implSlot) + + // Compute storage slot for deprecatedImplementations[implId] + mstore(0x00, implId) + mstore(0x20, add(s1, 2)) + let deprecatedImplSlot := keccak256(0x00, 0x40) + isDeprecated := sload(deprecatedImplSlot) + } + } + + /*============================================================== + = INTERNAL HELPERS = + ==============================================================*/ + + /// @dev Loads the registry storage. + /// @return $ The registry storage. + function _loadRegistryStorage() private pure returns (RegistryStorage storage $) { + assembly { + $.slot := MAGICDROP_REGISTRY_STORAGE + } } - /** - * @dev Internal function to authorize an upgrade. - * @param newImplementation Address of the new implementation. - * @notice Only the contract owner can upgrade the contract. - */ + /*============================================================== + = ADMIN OPERATIONS = + ==============================================================*/ + + /// @dev Internal function to authorize an upgrade. + /// @param newImplementation Address of the new implementation. + /// @notice Only the contract owner can upgrade the contract. function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} } diff --git a/contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol b/contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol index 5ab1d141..e5f1435b 100644 --- a/contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol +++ b/contracts/registry/interfaces/IMagicDropTokenImplRegistry.sol @@ -4,12 +4,7 @@ pragma solidity ^0.8.20; import {TokenStandard} from "../../common/Structs.sol"; interface IMagicDropTokenImplRegistry { - event ImplementationRegistered(TokenStandard standard, address impl, uint256 implId); - event ImplementationUnregistered(TokenStandard standard, uint256 implId); - - error ImplementationNotRegistered(); - - function registerImplementation(TokenStandard standard, address impl) external returns (uint256); - function unregisterImplementation(TokenStandard standard, uint256 implId) external; - function getImplementation(TokenStandard standard, uint256 implId) external view returns (address); + function registerImplementation(TokenStandard standard, address impl) external returns (uint32); + function deprecateImplementation(TokenStandard standard, uint32 implId) external; + function getImplementation(TokenStandard standard, uint32 implId) external view returns (address, bool); } diff --git a/test/common/AuthorizedMinterControlTest.t.sol b/test/common/AuthorizedMinterControlTest.t.sol new file mode 100644 index 00000000..80270652 --- /dev/null +++ b/test/common/AuthorizedMinterControlTest.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {AuthorizedMinterControl} from "../../contracts/common/AuthorizedMinterControl.sol"; + +contract MockAuthorizedMinterControl is AuthorizedMinterControl { + event Minted(address to, uint256 amount); + + function addAuthorizedMinter(address minter) public override { + _addAuthorizedMinter(minter); + } + + function removeAuthorizedMinter(address minter) public override { + _removeAuthorizedMinter(minter); + } + + function mint(address to, uint256 amount) public onlyAuthorizedMinter { + emit Minted(to, amount); + } +} + +contract AuthorizedMinterControlTest is Test { + MockAuthorizedMinterControl public authorizedMinterControl; + + function setUp() public { + authorizedMinterControl = new MockAuthorizedMinterControl(); + } + + function test_addAuthorizedMinter() public { + authorizedMinterControl.addAuthorizedMinter(address(1)); + assertTrue(authorizedMinterControl.isAuthorizedMinter(address(1))); + } + + function test_removeAuthorizedMinter() public { + authorizedMinterControl.addAuthorizedMinter(address(1)); + authorizedMinterControl.removeAuthorizedMinter(address(1)); + assertFalse(authorizedMinterControl.isAuthorizedMinter(address(1))); + } + + function test_isAuthorizedMinter() public { + assertFalse(authorizedMinterControl.isAuthorizedMinter(address(1))); + } + + function test_mint() public { + authorizedMinterControl.addAuthorizedMinter(address(1)); + vm.prank(address(1)); + authorizedMinterControl.mint(address(2), 1); + assertTrue(authorizedMinterControl.isAuthorizedMinter(address(1))); + } + + function test_mint_unauthorized() public { + vm.expectRevert(abi.encodeWithSelector(AuthorizedMinterControl.NotAuthorized.selector)); + authorizedMinterControl.mint(address(2), 1); + } +} diff --git a/test/common/CosignableTest.t.sol b/test/common/CosignableTest.t.sol new file mode 100644 index 00000000..fca20b2e --- /dev/null +++ b/test/common/CosignableTest.t.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {Cosignable} from "../../contracts/common/Cosignable.sol"; + +contract MockCosignable is Cosignable { + constructor(address cosigner) { + setCosigner(cosigner); + } + + function setCosigner(address cosigner) public override { + _cosigner = cosigner; + } + + // Expose internal functions for testing + function exposedAssertValidTimestamp(uint256 timestamp) public view { + _assertValidTimestamp(timestamp); + } + + function getCosigner() public view returns (address) { + return _cosigner; + } + + function getTimestampExpirySeconds() public view returns (uint256) { + return _timestampExpirySeconds; + } +} + +contract CosignableTest is Test { + MockCosignable public cosignable; + address public cosigner; + uint256 private cosignerPrivateKey; + + function setUp() public { + cosignerPrivateKey = 0x1234; + cosigner = vm.addr(cosignerPrivateKey); + cosignable = new MockCosignable(cosigner); + } + + function testSetCosigner() public { + address newCosigner = address(0x2); + cosignable.setCosigner(newCosigner); + assertEq(cosignable.getCosigner(), newCosigner); + } + + function testSetTimestampExpirySeconds() public { + uint256 newExpirySeconds = 3600; + cosignable.setTimestampExpirySeconds(newExpirySeconds); + assertEq(cosignable.getTimestampExpirySeconds(), newExpirySeconds); + } + + function testGetCosignDigest() public { + address minter = address(0x3); + uint32 qty = 5; + bool waiveMintFee = true; + uint256 timestamp = block.timestamp; + uint256 cosignNonce = 1; + + bytes32 digest = cosignable.getCosignDigest(minter, qty, waiveMintFee, timestamp, cosignNonce); + assertTrue(digest != bytes32(0)); + } + + function testFuzzAssertValidCosign(address minter, uint32 qty, uint256 timestamp, uint256 cosignNonce) public { + // Ensure timestamp is within a reasonable range + timestamp = bound(timestamp, block.timestamp, block.timestamp + 365 days); + + bytes32 digest = cosignable.getCosignDigest(minter, qty, true, timestamp, cosignNonce); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(cosignerPrivateKey, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + bool valid = cosignable.assertValidCosign(minter, qty, timestamp, signature, cosignNonce); + assertTrue(valid); + } + + function testAssertValidCosignFalse() public { + address minter = address(0x3); + uint32 qty = 5; + uint256 timestamp = block.timestamp; + uint256 cosignNonce = 1; + + bytes32 digest = cosignable.getCosignDigest(minter, qty, false, timestamp, cosignNonce); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(cosignerPrivateKey, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + bool valid = cosignable.assertValidCosign(minter, qty, timestamp, signature, cosignNonce); + assertFalse(valid); + } + + function testAssertValidCosignInvalidSignature() public { + address minter = address(0x3); + uint32 qty = 5; + uint256 timestamp = block.timestamp; + uint256 cosignNonce = 1; + + bytes memory invalidSignature = new bytes(65); + + vm.expectRevert(Cosignable.InvalidCosignSignature.selector); + cosignable.assertValidCosign(minter, qty, timestamp, invalidSignature, cosignNonce); + } + + function testAssertValidTimestamp() public { + vm.warp(69420); + + uint256 expirySeconds = 3600; + cosignable.setTimestampExpirySeconds(expirySeconds); + + uint256 validTimestamp = block.timestamp - expirySeconds + 1; + cosignable.exposedAssertValidTimestamp(validTimestamp); + + uint256 expiredTimestamp = block.timestamp - expirySeconds - 1; + vm.expectRevert(Cosignable.TimestampExpired.selector); + cosignable.exposedAssertValidTimestamp(expiredTimestamp); + } + + function testCosignerNotSet() public { + MockCosignable newCosignable = new MockCosignable(address(0)); + + vm.expectRevert(Cosignable.CosignerNotSet.selector); + newCosignable.getCosignDigest(address(0x3), 5, true, block.timestamp, 1); + } +} diff --git a/test/factory/MagicDropCloneFactoryTest.t.sol b/test/factory/MagicDropCloneFactoryTest.t.sol index 56e1f27b..bf21a0f7 100644 --- a/test/factory/MagicDropCloneFactoryTest.t.sol +++ b/test/factory/MagicDropCloneFactoryTest.t.sol @@ -2,41 +2,56 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; +import {MockERC721} from "solady/test/utils/mocks/MockERC721.sol"; +import {MockERC1155} from "solady/test/utils/mocks/MockERC1155.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; + import {MagicDropCloneFactory} from "../../contracts/factory/MagicDropCloneFactory.sol"; import {MagicDropTokenImplRegistry} from "../../contracts/registry/MagicDropTokenImplRegistry.sol"; import {MagicDropERC721Initializable} from "../../contracts/nft/MagicDropERC721Initializable.sol"; import {MagicDropERC1155Initializable} from "../../contracts/nft/MagicDropERC1155Initializable.sol"; import {TokenStandard} from "../../contracts/common/Structs.sol"; +contract MockERC721Initializable is MockERC721 { + function initialize(string memory, string memory, address) public {} +} + +contract MockERC1155Initializable is MockERC1155 { + function initialize(string memory, string memory, address) public {} +} + contract MagicDropCloneFactoryTest is Test { MagicDropCloneFactory internal factory; MagicDropTokenImplRegistry internal registry; - MagicDropERC721Initializable internal erc721Implementation; - MagicDropERC1155Initializable internal erc1155Implementation; + + MockERC721Initializable internal erc721Impl; + MockERC1155Initializable internal erc1155Impl; address internal owner = address(0x1); address internal user = address(0x2); - uint256 internal erc721ImplId; - uint256 internal erc1155ImplId; + uint32 internal erc721ImplId; + uint32 internal erc1155ImplId; function setUp() public { vm.startPrank(owner); // Deploy and initialize registry - registry = new MagicDropTokenImplRegistry(); - registry.initialize(); + MagicDropTokenImplRegistry registryImpl = new MagicDropTokenImplRegistry(); + registry = MagicDropTokenImplRegistry(LibClone.deployERC1967(address(registryImpl))); + registry.initialize(owner); // Deploy factory - factory = new MagicDropCloneFactory(registry); - factory.initialize(); + MagicDropCloneFactory factoryImpl = new MagicDropCloneFactory(); + factory = MagicDropCloneFactory(LibClone.deployERC1967(address(factoryImpl))); + factory.initialize(owner, address(registry)); // Deploy implementations - erc721Implementation = new MagicDropERC721Initializable(); - erc1155Implementation = new MagicDropERC1155Initializable(); + erc721Impl = new MockERC721Initializable(); + erc1155Impl = new MockERC1155Initializable(); // Register implementations - erc721ImplId = registry.registerImplementation(TokenStandard.ERC721, address(erc721Implementation)); - erc1155ImplId = registry.registerImplementation(TokenStandard.ERC1155, address(erc1155Implementation)); + erc721ImplId = registry.registerImplementation(TokenStandard.ERC721, address(erc721Impl)); + erc1155ImplId = registry.registerImplementation(TokenStandard.ERC1155, address(erc1155Impl)); vm.stopPrank(); } @@ -45,12 +60,9 @@ contract MagicDropCloneFactoryTest is Test { vm.startPrank(user); address newContract = - factory.createContract("TestNFT", "TNFT", TokenStandard.ERC721, payable(user), erc721ImplId, bytes32(0)); + factory.createContract("TestNFT", "TNFT", TokenStandard.ERC721, payable(user), erc721ImplId); - MagicDropERC721Initializable nft = MagicDropERC721Initializable(newContract); - assertEq(nft.name(), "TestNFT"); - assertEq(nft.symbol(), "TNFT"); - assertEq(nft.owner(), user); + MockERC721Initializable nft = MockERC721Initializable(newContract); // Test minting nft.mint(user, 1); @@ -62,18 +74,14 @@ contract MagicDropCloneFactoryTest is Test { function testCreateERC1155Contract() public { vm.startPrank(user); - address newContract = factory.createContract( - "TestMultiToken", "TMT", TokenStandard.ERC1155, payable(user), erc1155ImplId, bytes32(0) - ); + address newContract = + factory.createContract("TestMultiToken", "TMT", TokenStandard.ERC1155, payable(user), erc1155ImplId); - MagicDropERC1155Initializable multiToken = MagicDropERC1155Initializable(newContract); - assertEq(multiToken.name(), "TestMultiToken"); - assertEq(multiToken.symbol(), "TMT"); - assertEq(multiToken.owner(), user); + MockERC1155Initializable nft = MockERC1155Initializable(newContract); // Test minting - multiToken.mint(user, 1, 100, ""); - assertEq(multiToken.balanceOf(user, 1), 100); + nft.mint(user, 1, 100, ""); + assertEq(nft.balanceOf(user, 1), 100); vm.stopPrank(); } @@ -81,12 +89,11 @@ contract MagicDropCloneFactoryTest is Test { function testCreateContractWithDifferentSalts() public { vm.startPrank(user); - address contract1 = factory.createContract( - "TestNFT1", "TNFT1", TokenStandard.ERC721, payable(user), erc721ImplId, bytes32(uint256(1)) + address contract1 = factory.createContractDeterministic( + "TestNFT1", "TNFT1", TokenStandard.ERC721, payable(user), erc721ImplId, bytes32(uint256(0)) ); - - address contract2 = factory.createContract( - "TestNFT2", "TNFT2", TokenStandard.ERC721, payable(user), erc721ImplId, bytes32(uint256(2)) + address contract2 = factory.createContractDeterministic( + "TestNFT2", "TNFT2", TokenStandard.ERC721, payable(user), erc721ImplId, bytes32(uint256(1)) ); assertTrue(contract1 != contract2); @@ -95,9 +102,51 @@ contract MagicDropCloneFactoryTest is Test { } function testFailCreateContractWithInvalidImplementation() public { - uint256 invalidImplId = 999; + uint32 invalidImplId = 999; vm.prank(user); - factory.createContract("TestNFT", "TNFT", TokenStandard.ERC721, payable(user), invalidImplId, bytes32(0)); + factory.createContract("TestNFT", "TNFT", TokenStandard.ERC721, payable(user), invalidImplId); + } + + function testCreateDeterministicContractWithSameSalt() public { + vm.startPrank(user); + + factory.createContractDeterministic( + "TestNFT1", "TNFT1", TokenStandard.ERC721, payable(user), erc721ImplId, bytes32(0) + ); + + vm.expectRevert(abi.encodeWithSelector(MagicDropCloneFactory.SaltAlreadyUsed.selector, bytes32(0))); + + factory.createContractDeterministic( + "TestNFT2", "TNFT2", TokenStandard.ERC721, payable(user), erc721ImplId, bytes32(0) + ); + } + + function testContractAlreadyDeployed() public { + bytes32 salt = bytes32(uint256(1)); + uint32 implId = 1; + TokenStandard standard = TokenStandard.ERC721; + address initialOwner = address(0x1); + string memory name = "TestToken"; + string memory symbol = "TT"; + + // Predict the address where the contract will be deployed + address predictedAddress = factory.predictDeploymentAddress(standard, implId, salt); + + // Deploy a dummy contract to the predicted address + vm.etch(predictedAddress, address(erc721Impl).code); + + // Try to create a contract with the same parameters + vm.expectRevert( + abi.encodeWithSelector(MagicDropCloneFactory.ContractAlreadyDeployed.selector, predictedAddress) + ); + factory.createContractDeterministic(name, symbol, standard, payable(initialOwner), implId, salt); + } + + function testIsSaltUsed() public { + bytes32 salt = bytes32(uint256(1)); + assertTrue(!factory.isSaltUsed(salt)); + factory.createContractDeterministic("TestNFT", "TNFT", TokenStandard.ERC721, payable(user), erc721ImplId, salt); + assertTrue(factory.isSaltUsed(salt)); } } diff --git a/test/factory/MagicDropTokenImplRegistryTest.t.sol b/test/factory/MagicDropTokenImplRegistryTest.t.sol index 13fecd73..61149643 100644 --- a/test/factory/MagicDropTokenImplRegistryTest.t.sol +++ b/test/factory/MagicDropTokenImplRegistryTest.t.sol @@ -4,65 +4,84 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {MagicDropTokenImplRegistry} from "../../contracts/registry/MagicDropTokenImplRegistry.sol"; import {TokenStandard} from "../../contracts/common/Structs.sol"; -import {MockERC721A} from "../../contracts/mocks/MockERC721A.sol"; +import {MockERC721} from "solady/test/utils/mocks/MockERC721.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; + +interface IMagicDropTokenImplRegistryRaw { + function registerImplementation(uint8 tokenStandard, address implementation) external returns (uint32); +} contract MagicDropTokenImplRegistryTest is Test { MagicDropTokenImplRegistry internal registry; address internal owner = address(0x1); address internal user = address(0x2); - MockERC721A internal mockImpl; + MockERC721 internal mockERC721; function setUp() public { vm.startPrank(owner); - registry = new MagicDropTokenImplRegistry(); - registry.initialize(); + MagicDropTokenImplRegistry registryImpl = new MagicDropTokenImplRegistry(); + registry = MagicDropTokenImplRegistry(LibClone.deployERC1967(address(registryImpl))); + registry.initialize(owner); vm.stopPrank(); - mockImpl = new MockERC721A(); + mockERC721 = new MockERC721(); } function testRegisterImplementation() public { vm.prank(owner); - uint256 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); + uint32 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockERC721)); assertEq(implId, 1); - assertEq(registry.getImplementation(TokenStandard.ERC721, implId), address(mockImpl)); + (address impl, bool deprecated) = registry.getImplementation(TokenStandard.ERC721, implId); + assertEq(impl, address(mockERC721)); + assertFalse(deprecated); } function testRegisterMultipleImplementations() public { vm.startPrank(owner); - uint256 implId1 = registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); - uint256 implId2 = registry.registerImplementation(TokenStandard.ERC721, address(0x3)); + uint32 implId1 = registry.registerImplementation(TokenStandard.ERC721, address(mockERC721)); + uint32 implId2 = registry.registerImplementation(TokenStandard.ERC721, address(mockERC721)); vm.stopPrank(); assertEq(implId1, 1); - assertEq(implId2, 2); - assertEq(registry.getImplementation(TokenStandard.ERC721, implId1), address(mockImpl)); - assertEq(registry.getImplementation(TokenStandard.ERC721, implId2), address(0x3)); - } - - function testUnregisterImplementation() public { - vm.startPrank(owner); - uint256 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); - registry.unregisterImplementation(TokenStandard.ERC721, implId); - vm.stopPrank(); + assertEq(implId2, 2); // ensure the implId is incremented - assertEq(registry.getImplementation(TokenStandard.ERC721, implId), address(0)); - } + (address impl1,) = registry.getImplementation(TokenStandard.ERC721, implId1); + (address impl2,) = registry.getImplementation(TokenStandard.ERC721, implId2); - function testFailUnregisterNonExistentImplementation() public { - vm.prank(owner); - registry.unregisterImplementation(TokenStandard.ERC721, 0); + assertEq(impl1, address(mockERC721)); + assertEq(impl2, address(mockERC721)); } function testFailRegisterImplementationAsNonOwner() public { vm.prank(user); - registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); + registry.registerImplementation(TokenStandard.ERC721, address(mockERC721)); } - function testFailUnregisterImplementationAsNonOwner() public { + function testGetImplementation() public { vm.prank(owner); - uint256 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockImpl)); + uint32 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockERC721)); + (address impl, bool deprecated) = registry.getImplementation(TokenStandard.ERC721, implId); + assertEq(impl, address(mockERC721)); + assertFalse(deprecated); + } - vm.prank(user); - registry.unregisterImplementation(TokenStandard.ERC721, implId); + function testGetImplementationDeprecated() public { + vm.startPrank(owner); + uint32 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockERC721)); + registry.deprecateImplementation(TokenStandard.ERC721, implId); + (address impl, bool deprecated) = registry.getImplementation(TokenStandard.ERC721, implId); + assertEq(impl, address(mockERC721)); + assertTrue(deprecated); + vm.stopPrank(); + } + + function testRegisterUnsupportedStandard() public { + vm.prank(owner); + vm.expectRevert( + abi.encodeWithSelector( + MagicDropTokenImplRegistry.ImplementationDoesNotSupportStandard.selector, TokenStandard.ERC1155 + ) + ); + // register as erc1155 with erc721 impl + registry.registerImplementation(TokenStandard.ERC1155, address(mockERC721)); } } From 6b76fe20746734ca7f0fffa4266372b8ea6296f9 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Sun, 6 Oct 2024 12:00:47 -0400 Subject: [PATCH 044/145] tests Signed-off-by: Wolfy --- .../registry/MagicDropTokenImplRegistry.sol | 28 ++++++------- test/factory/MagicDropCloneFactoryTest.t.sol | 36 ++++++++++++++++ .../MagicDropTokenImplRegistryTest.t.sol | 41 ++++++++++++++----- 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/contracts/registry/MagicDropTokenImplRegistry.sol b/contracts/registry/MagicDropTokenImplRegistry.sol index f9dc38e3..a07167a2 100644 --- a/contracts/registry/MagicDropTokenImplRegistry.sol +++ b/contracts/registry/MagicDropTokenImplRegistry.sol @@ -65,12 +65,12 @@ contract MagicDropTokenImplRegistry is Initializable, UUPSUpgradeable, Ownable, _initializeOwner(initialOwner); // Initialize nextImplId and interface IDs for each token standard - RegistryStorage storage data = _loadRegistryStorage(); - data.tokenStandardData[TokenStandard.ERC721].nextImplId = 1; - data.tokenStandardData[TokenStandard.ERC721].interfaceId = 0x80ac58cd; // ERC721 interface ID + RegistryStorage storage $ = _loadRegistryStorage(); + $.tokenStandardData[TokenStandard.ERC721].nextImplId = 1; + $.tokenStandardData[TokenStandard.ERC721].interfaceId = 0x80ac58cd; // ERC721 interface ID - data.tokenStandardData[TokenStandard.ERC1155].nextImplId = 1; - data.tokenStandardData[TokenStandard.ERC1155].interfaceId = 0xd9b67a26; // ERC1155 interface ID + $.tokenStandardData[TokenStandard.ERC1155].nextImplId = 1; + $.tokenStandardData[TokenStandard.ERC1155].interfaceId = 0xd9b67a26; // ERC1155 interface ID } /*============================================================== @@ -83,8 +83,8 @@ contract MagicDropTokenImplRegistry is Initializable, UUPSUpgradeable, Ownable, /// @notice Only the contract owner can call this function. /// @notice Reverts if an implementation with the same name is already registered. function registerImplementation(TokenStandard standard, address impl) external onlyOwner returns (uint32) { - RegistryStorage storage data = _loadRegistryStorage(); - bytes4 interfaceId = data.tokenStandardData[standard].interfaceId; + RegistryStorage storage $ = _loadRegistryStorage(); + bytes4 interfaceId = $.tokenStandardData[standard].interfaceId; if (interfaceId == 0) { revert UnsupportedTokenStandard(standard); } @@ -93,9 +93,9 @@ contract MagicDropTokenImplRegistry is Initializable, UUPSUpgradeable, Ownable, revert ImplementationDoesNotSupportStandard(standard); } - uint32 implId = data.tokenStandardData[standard].nextImplId; - data.tokenStandardData[standard].implementations[implId] = impl; - data.tokenStandardData[standard].nextImplId = implId + 1; + uint32 implId = $.tokenStandardData[standard].nextImplId; + $.tokenStandardData[standard].implementations[implId] = impl; + $.tokenStandardData[standard].nextImplId = implId + 1; emit ImplementationRegistered(standard, impl, implId); return implId; } @@ -106,16 +106,16 @@ contract MagicDropTokenImplRegistry is Initializable, UUPSUpgradeable, Ownable, /// @notice Only the contract owner can call this function. /// @notice Reverts if the implementation is not registered or already deprecated. function deprecateImplementation(TokenStandard standard, uint32 implId) external onlyOwner { - RegistryStorage storage data = _loadRegistryStorage(); - if (data.tokenStandardData[standard].implementations[implId] == address(0)) { + RegistryStorage storage $ = _loadRegistryStorage(); + if ($.tokenStandardData[standard].implementations[implId] == address(0)) { revert ImplementationNotRegistered(); } - if (data.tokenStandardData[standard].deprecatedImplementations[implId]) { + if ($.tokenStandardData[standard].deprecatedImplementations[implId]) { revert ImplementationAlreadyDeprecated(); } - data.tokenStandardData[standard].deprecatedImplementations[implId] = true; + $.tokenStandardData[standard].deprecatedImplementations[implId] = true; emit ImplementationDeprecated(standard, implId); } diff --git a/test/factory/MagicDropCloneFactoryTest.t.sol b/test/factory/MagicDropCloneFactoryTest.t.sol index bf21a0f7..adf7ba35 100644 --- a/test/factory/MagicDropCloneFactoryTest.t.sol +++ b/test/factory/MagicDropCloneFactoryTest.t.sol @@ -20,6 +20,10 @@ contract MockERC1155Initializable is MockERC1155 { function initialize(string memory, string memory, address) public {} } +contract InvalidImplementation is MockERC721 { + function initialize(string memory) public {} // Missing name and symbol parameters +} + contract MagicDropCloneFactoryTest is Test { MagicDropCloneFactory internal factory; MagicDropTokenImplRegistry internal registry; @@ -149,4 +153,36 @@ contract MagicDropCloneFactoryTest is Test { factory.createContractDeterministic("TestNFT", "TNFT", TokenStandard.ERC721, payable(user), erc721ImplId, salt); assertTrue(factory.isSaltUsed(salt)); } + + function testImplementationDeprecated() public { + TokenStandard standard = TokenStandard.ERC721; + + vm.startPrank(owner); + uint32 implId = registry.registerImplementation(standard, address(erc721Impl)); + registry.deprecateImplementation(standard, implId); + vm.stopPrank(); + + vm.expectRevert(MagicDropCloneFactory.ImplementationDeprecated.selector); + factory.createContractDeterministic("TestNFT", "TNFT", standard, payable(user), implId, bytes32(0)); + } + + function testImplementationNotRegistered() public { + TokenStandard standard = TokenStandard.ERC721; + uint32 implId = 999; + + vm.expectRevert(MagicDropCloneFactory.ImplementationNotRegistered.selector); + factory.createContractDeterministic("TestNFT", "TNFT", standard, payable(user), implId, bytes32(0)); + } + + function testInitializationFailed() public { + TokenStandard standard = TokenStandard.ERC721; + + vm.startPrank(owner); + InvalidImplementation impl = new InvalidImplementation(); + uint32 implId = registry.registerImplementation(standard, address(impl)); + vm.stopPrank(); + + vm.expectRevert(MagicDropCloneFactory.InitializationFailed.selector); + factory.createContractDeterministic("TestNFT", "TNFT", standard, payable(user), implId, bytes32(0)); + } } diff --git a/test/factory/MagicDropTokenImplRegistryTest.t.sol b/test/factory/MagicDropTokenImplRegistryTest.t.sol index 61149643..c6d3a407 100644 --- a/test/factory/MagicDropTokenImplRegistryTest.t.sol +++ b/test/factory/MagicDropTokenImplRegistryTest.t.sol @@ -64,16 +64,6 @@ contract MagicDropTokenImplRegistryTest is Test { assertFalse(deprecated); } - function testGetImplementationDeprecated() public { - vm.startPrank(owner); - uint32 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockERC721)); - registry.deprecateImplementation(TokenStandard.ERC721, implId); - (address impl, bool deprecated) = registry.getImplementation(TokenStandard.ERC721, implId); - assertEq(impl, address(mockERC721)); - assertTrue(deprecated); - vm.stopPrank(); - } - function testRegisterUnsupportedStandard() public { vm.prank(owner); vm.expectRevert( @@ -84,4 +74,35 @@ contract MagicDropTokenImplRegistryTest is Test { // register as erc1155 with erc721 impl registry.registerImplementation(TokenStandard.ERC1155, address(mockERC721)); } + + function testDeprecateImplementation() public { + vm.startPrank(owner); + uint32 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockERC721)); + registry.deprecateImplementation(TokenStandard.ERC721, implId); + vm.stopPrank(); + + (address impl, bool deprecated) = registry.getImplementation(TokenStandard.ERC721, implId); + assertEq(impl, address(mockERC721)); + assertTrue(deprecated); + } + + function testFailDeprecateImplementationAsNonOwner() public { + vm.prank(user); + registry.deprecateImplementation(TokenStandard.ERC721, 1); + } + + function testDeprecateImplementationNotRegistered() public { + vm.prank(owner); + vm.expectRevert(MagicDropTokenImplRegistry.ImplementationNotRegistered.selector); + registry.deprecateImplementation(TokenStandard.ERC721, 1); + } + + function testDeprecateImplementationAlreadyDeprecated() public { + vm.startPrank(owner); + uint32 implId = registry.registerImplementation(TokenStandard.ERC721, address(mockERC721)); + registry.deprecateImplementation(TokenStandard.ERC721, implId); + vm.expectRevert(MagicDropTokenImplRegistry.ImplementationAlreadyDeprecated.selector); + registry.deprecateImplementation(TokenStandard.ERC721, implId); + vm.stopPrank(); + } } From 808657984b5cb521b340044c657a1d67b4f7adb7 Mon Sep 17 00:00:00 2001 From: Wolfy Date: Sun, 6 Oct 2024 12:04:24 -0400 Subject: [PATCH 045/145] cleanup Signed-off-by: Wolfy --- .../common/interfaces/IInitializableToken.sol | 6 --- .../nft/MagicDropERC1155Initializable.sol | 38 ------------------- .../nft/MagicDropERC721Initializable.sol | 27 ------------- 3 files changed, 71 deletions(-) delete mode 100644 contracts/common/interfaces/IInitializableToken.sol delete mode 100644 contracts/nft/MagicDropERC1155Initializable.sol delete mode 100644 contracts/nft/MagicDropERC721Initializable.sol diff --git a/contracts/common/interfaces/IInitializableToken.sol b/contracts/common/interfaces/IInitializableToken.sol deleted file mode 100644 index bb33267b..00000000 --- a/contracts/common/interfaces/IInitializableToken.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -interface IInitializableToken { - function initialize(string calldata name, string calldata symbol, address payable initialOwner) external; -} diff --git a/contracts/nft/MagicDropERC1155Initializable.sol b/contracts/nft/MagicDropERC1155Initializable.sol deleted file mode 100644 index e642bc77..00000000 --- a/contracts/nft/MagicDropERC1155Initializable.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {IInitializableToken} from "../common/interfaces/IInitializableToken.sol"; - -contract MagicDropERC1155Initializable is ERC1155Upgradeable, OwnableUpgradeable, IInitializableToken { - string private _name; - string private _symbol; - - function initialize(string calldata name_, string calldata symbol_, address payable initialOwner) - external - override - initializer - { - _name = name_; - _symbol = symbol_; - __ERC1155_init(""); - __Ownable_init(initialOwner); - } - - function name() external view returns (string memory) { - return _name; - } - - function symbol() external view returns (string memory) { - return _symbol; - } - - function setURI(string calldata newuri) external onlyOwner { - _setURI(newuri); - } - - function mint(address to, uint256 id, uint256 amount, bytes memory data) external { - _mint(to, id, amount, data); - } -} diff --git a/contracts/nft/MagicDropERC721Initializable.sol b/contracts/nft/MagicDropERC721Initializable.sol deleted file mode 100644 index 4cab9f9b..00000000 --- a/contracts/nft/MagicDropERC721Initializable.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {IInitializableToken} from "../common/interfaces/IInitializableToken.sol"; - -contract MagicDropERC721Initializable is ERC721Upgradeable, OwnableUpgradeable, IInitializableToken { - string private baseURI = ""; - - function initialize(string memory name, string memory symbol, address payable initialOwner) - external - override - initializer - { - __ERC721_init(name, symbol); - __Ownable_init(initialOwner); - } - - function _baseURI() internal view virtual override returns (string memory) { - return baseURI; - } - - function mint(address to, uint256 tokenId) external onlyOwner { - _mint(to, tokenId); - } -} From 59f6bb81715110d6c98a95023c7863a0b591953f Mon Sep 17 00:00:00 2001 From: Wolfy Date: Sun, 6 Oct 2024 16:46:19 -0400 Subject: [PATCH 046/145] refactoring Signed-off-by: Wolfy --- .../mocks/ERC1155MTestReentrantExploit.sol | 4 +- contracts/nft/erc1155m/ERC1155M.sol | 21 +- ...le.sol => ERC1155MInitializableV1_0_0.sol} | 581 +++++++++--------- contracts/nft/erc1155m/ERC1155MStorage.sol | 2 - .../nft/erc1155m/interfaces/IERC1155M.sol | 6 +- contracts/nft/erc721m/ERC721CM.sol | 2 - .../nft/erc721m/ERC721CMInitializable.sol | 530 ---------------- ...V2.sol => ERC721CMInitializableV1_0_0.sol} | 22 +- contracts/nft/erc721m/ERC721M.sol | 2 - .../erc721m/ERC721CMInitializableV2Test.t.sol | 11 +- test/factory/MagicDropCloneFactoryTest.t.sol | 2 - 11 files changed, 324 insertions(+), 859 deletions(-) rename contracts/nft/erc1155m/{ERC1155MInitializable.sol => ERC1155MInitializableV1_0_0.sol} (64%) delete mode 100644 contracts/nft/erc721m/ERC721CMInitializable.sol rename contracts/nft/erc721m/{v2/ERC721CMInitializableV2.sol => ERC721CMInitializableV1_0_0.sol} (96%) diff --git a/contracts/mocks/ERC1155MTestReentrantExploit.sol b/contracts/mocks/ERC1155MTestReentrantExploit.sol index 36773acf..22e4ce41 100644 --- a/contracts/mocks/ERC1155MTestReentrantExploit.sol +++ b/contracts/mocks/ERC1155MTestReentrantExploit.sol @@ -12,12 +12,12 @@ contract ERC1155MTestReentrantExploit { } function exploit(uint256 tokenId, uint32 qty, bytes32[] calldata proof) public payable { - ERC1155M(_targetContract).mint{value: msg.value}(tokenId, qty, proof, 0, bytes("0")); + ERC1155M(_targetContract).mint{value: msg.value}(tokenId, qty, 0, proof, 0, bytes("0")); } function onERC1155Received(address, address, uint256, uint256, bytes calldata) public payable returns (bytes4) { bytes32[] memory proof; - ERC1155M(_targetContract).mint{value: msg.value}(0, 1, proof, 0, bytes("0")); + ERC1155M(_targetContract).mint{value: msg.value}(0, 1, 0, proof, 0, bytes("0")); return IERC1155Receiver.onERC1155Received.selector; } } diff --git a/contracts/nft/erc1155m/ERC1155M.sol b/contracts/nft/erc1155m/ERC1155M.sol index 8bd067c5..005ea220 100644 --- a/contracts/nft/erc1155m/ERC1155M.sol +++ b/contracts/nft/erc1155m/ERC1155M.sol @@ -306,26 +306,7 @@ contract ERC1155M is * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner */ - function mint(uint256 tokenId, uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) - external - payable - virtual - nonReentrant - { - _mintInternal(msg.sender, tokenId, qty, 0, proof, timestamp, signature); - } - - /** - * @dev Mints token(s) with limit. - * - * tokenId - token id - * qty - number of tokens to mint - * limit - limit for the given minter - * proof - the merkle proof generated on client side. This applies if using whitelist - * timestamp - the current timestamp - * signature - the signature from cosigner if using cosigner - */ - function mintWithLimit( + function mint( uint256 tokenId, uint32 qty, uint32 limit, diff --git a/contracts/nft/erc1155m/ERC1155MInitializable.sol b/contracts/nft/erc1155m/ERC1155MInitializableV1_0_0.sol similarity index 64% rename from contracts/nft/erc1155m/ERC1155MInitializable.sol rename to contracts/nft/erc1155m/ERC1155MInitializableV1_0_0.sol index 3f746619..40deaf61 100644 --- a/contracts/nft/erc1155m/ERC1155MInitializable.sol +++ b/contracts/nft/erc1155m/ERC1155MInitializableV1_0_0.sol @@ -1,5 +1,4 @@ //SPDX-License-Identifier: MIT - pragma solidity ^0.8.20; import { @@ -19,17 +18,10 @@ import {ERC1155MStorage} from "./ERC1155MStorage.sol"; import {Cosignable} from "../../common/Cosignable.sol"; import {AuthorizedMinterControl} from "../../common/AuthorizedMinterControl.sol"; -/** - * @title ERC1155MInitializable - * - * @dev OpenZeppelin's ERC1155 subclass with MagicEden launchpad features including - * - multi token minting - * - multi minting stages with time-based auto stage switch - * - global and stage wallet-level minting limit - * - whitelist - * - variable wallet limit - */ -contract ERC1155MInitializable is +/// @title ERC1155MInitializableV1_0_0 +/// @notice An upgradeable ERC1155 contract with multi-stage minting, royalties, and authorized minters +/// @dev Implements ERC1155, ERC2981, Ownable, ReentrancyGuard, and custom minting logic +contract ERC1155MInitializableV1_0_0 is IERC1155M, ERC1155SupplyUpgradeable, Ownable, @@ -39,10 +31,19 @@ contract ERC1155MInitializable is Cosignable, AuthorizedMinterControl { + /*============================================================== + = INITIALIZERS = + ==============================================================*/ + + /// @dev Disables initializers for the implementation contract. constructor() { _disableInitializers(); } + /// @notice Initializes the contract + /// @param name_ The name of the token collection + /// @param symbol_ The symbol of the token collection + /// @param initialOwner The address of the initial owner function initialize(string calldata name_, string calldata symbol_, address initialOwner) external initializer { name = name_; symbol = symbol_; @@ -50,6 +51,175 @@ contract ERC1155MInitializable is _initializeOwner(initialOwner); } + /*============================================================== + = META = + ==============================================================*/ + + /// @notice Returns the contract name and version + /// @return The contract name and version as strings + function contractNameAndVersion() public pure returns (string memory, string memory) { + return ("ERC1155MInitializable", "1.0.0"); + } + + /*============================================================== + = MODIFIERS = + ==============================================================*/ + + /// @dev Modifier to check if there's enough supply for minting + /// @param tokenId The ID of the token to mint + /// @param qty The quantity to mint + modifier hasSupply(uint256 tokenId, uint256 qty) { + if (_maxMintableSupply[tokenId] > 0 && totalSupply(tokenId) + qty > _maxMintableSupply[tokenId]) { + revert NoSupplyLeft(); + } + _; + } + + /*============================================================== + = PUBLIC WRITE METHODS = + ==============================================================*/ + + /// @notice Mints tokens for the caller + /// @param tokenId The ID of the token to mint + /// @param qty The quantity to mint + /// @param limit The minting limit for the caller (used in merkle proofs) + /// @param proof The merkle proof for allowlist minting + /// @param timestamp The timestamp for the minting action (used in cosigning) + /// @param signature The cosigner's signature + function mint( + uint256 tokenId, + uint32 qty, + uint32 limit, + bytes32[] calldata proof, + uint64 timestamp, + bytes calldata signature + ) external payable virtual nonReentrant { + _mintInternal(msg.sender, tokenId, qty, limit, proof, timestamp, signature); + } + + /// @notice Allows authorized minters to mint tokens for a specified address + /// @param to The address to mint tokens for + /// @param tokenId The ID of the token to mint + /// @param qty The quantity to mint + /// @param limit The minting limit for the recipient (used in merkle proofs) + /// @param proof The merkle proof for allowlist minting + function authorizedMint(address to, uint256 tokenId, uint32 qty, uint32 limit, bytes32[] calldata proof) + external + payable + onlyAuthorizedMinter + { + _mintInternal(to, tokenId, qty, limit, proof, 0, bytes("0")); + } + + /*============================================================== + = PUBLIC VIEW METHODS = + ==============================================================*/ + + /// @notice Gets the stage info, total minted, and stage minted for a specific stage + /// @param stage The stage number + /// @return The stage info, total minted by the caller, and stage minted by the caller + function getStageInfo(uint256 stage) + external + view + override + returns (MintStageInfo1155 memory, uint256[] memory, uint256[] memory) + { + if (stage >= _mintStages.length) { + revert InvalidStage(); + } + uint256[] memory walletMinted = totalMintedByAddress(msg.sender); + uint256[] memory stageMinted = _totalMintedByStageByAddress(stage, msg.sender); + return (_mintStages[stage], walletMinted, stageMinted); + } + + /// @notice Gets the number of minting stages + /// @return The number of minting stages + function getNumberStages() external view override returns (uint256) { + return _mintStages.length; + } + + /// @notice Gets the active stage based on a given timestamp + /// @param timestamp The timestamp to check + /// @return The active stage number + function getActiveStageFromTimestamp(uint64 timestamp) public view returns (uint256) { + for (uint256 i = 0; i < _mintStages.length; i++) { + if (timestamp >= _mintStages[i].startTimeUnixSeconds && timestamp < _mintStages[i].endTimeUnixSeconds) { + return i; + } + } + revert InvalidStage(); + } + + /// @notice Gets the mint currency address + /// @return The address of the mint currency + function getMintCurrency() external view returns (address) { + return _mintCurrency; + } + + /// @notice Gets the cosign nonce for a specific minter and token ID + /// @param minter The address of the minter + /// @param tokenId The ID of the token + /// @return The cosign nonce + function getCosignNonce(address minter, uint256 tokenId) public view returns (uint256) { + return totalMintedByAddress(minter)[tokenId]; + } + + /// @notice Gets the maximum mintable supply for a specific token ID + /// @param tokenId The ID of the token + /// @return The maximum mintable supply + function getMaxMintableSupply(uint256 tokenId) external view override returns (uint256) { + return _maxMintableSupply[tokenId]; + } + + /// @notice Gets the global wallet limit for a specific token ID + /// @param tokenId The ID of the token + /// @return The global wallet limit + function getGlobalWalletLimit(uint256 tokenId) external view override returns (uint256) { + return _globalWalletLimit[tokenId]; + } + + /// @notice Gets the total minted tokens for each token ID by a specific address + /// @param account The address to check + /// @return An array of total minted tokens for each token ID + function totalMintedByAddress(address account) public view virtual override returns (uint256[] memory) { + uint256[] memory totalMinted = new uint256[](_numTokens); + uint256 numStages = _mintStages.length; + for (uint256 token = 0; token < _numTokens; token++) { + for (uint256 stage = 0; stage < numStages; stage++) { + totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][token][account]; + } + } + return totalMinted; + } + + /// @notice Checks if the contract supports a given interface + /// @param interfaceId The interface identifier + /// @return True if the contract supports the interface, false otherwise + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC2981, ERC1155Upgradeable) + returns (bool) + { + return super.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId) + || ERC1155Upgradeable.supportsInterface(interfaceId); + } + + /*============================================================== + = ADMIN OPERATIONS = + ==============================================================*/ + + /// @notice Sets up the contract with initial parameters + /// @param uri_ The URI for token metadata + /// @param maxMintableSupply Array of maximum mintable supply for each token ID + /// @param globalWalletLimit Array of global wallet limits for each token ID + /// @param timestampExpirySeconds The expiry time in seconds for timestamps + /// @param cosigner The address of the cosigner + /// @param mintCurrency The address of the mint currency + /// @param fundReceiver The address to receive funds + /// @param royaltyReceiver The address to receive royalties + /// @param royaltyFeeNumerator The royalty fee numerator function setup( string calldata uri_, uint256[] memory maxMintableSupply, @@ -84,48 +254,8 @@ contract ERC1155MInitializable is setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); } - /** - * @dev Returns whether it has enough supply for the given qty. - */ - modifier hasSupply(uint256 tokenId, uint256 qty) { - if (_maxMintableSupply[tokenId] > 0 && totalSupply(tokenId) + qty > _maxMintableSupply[tokenId]) { - revert NoSupplyLeft(); - } - _; - } - - /** - * @dev Add authorized minter. Can only be called by contract owner. - */ - function addAuthorizedMinter(address minter) external override onlyOwner { - _addAuthorizedMinter(minter); - } - - /** - * @dev Remove authorized minter. Can only be called by contract owner. - */ - function removeAuthorizedMinter(address minter) external override onlyOwner { - _removeAuthorizedMinter(minter); - } - - /** - * @dev Sets cosigner. Can only be called by contract owner. - */ - function setCosigner(address cosigner) external override onlyOwner { - _cosigner = cosigner; - emit SetCosigner(cosigner); - } - - /** - * @dev Returns cosign nonce. - */ - function getCosignNonce(address minter, uint256 tokenId) public view returns (uint256) { - return totalMintedByAddress(minter)[tokenId]; - } - - /** - * @dev Sets stages in the format of an array of `MintStageInfo`. - */ + /// @notice Sets the minting stages + /// @param newStages An array of new minting stages function setStages(MintStageInfo1155[] calldata newStages) external onlyOwner { delete _mintStages; @@ -162,16 +292,39 @@ contract ERC1155MInitializable is } } - /** - * @dev Returns maximum mintable supply per token. - */ - function getMaxMintableSupply(uint256 tokenId) external view override returns (uint256) { - return _maxMintableSupply[tokenId]; + /// @notice Sets the URI for token metadata + /// @param newURI The new URI to set + function setURI(string calldata newURI) external onlyOwner { + _setURI(newURI); + } + + /// @notice Sets whether tokens are transferable + /// @param transferable True if tokens should be transferable, false otherwise + function setTransferable(bool transferable) external onlyOwner { + _transferable = transferable; + emit SetTransferable(transferable); + } + + /// @notice Sets the default royalty for the contract + /// @param receiver The address to receive royalties + /// @param feeNumerator The royalty fee numerator + function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { + super._setDefaultRoyalty(receiver, feeNumerator); + emit DefaultRoyaltySet(receiver, feeNumerator); + } + + /// @notice Sets the royalty for a specific token + /// @param tokenId The ID of the token + /// @param receiver The address to receive royalties + /// @param feeNumerator The royalty fee numerator + function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) public onlyOwner { + super._setTokenRoyalty(tokenId, receiver, feeNumerator); + emit TokenRoyaltySet(tokenId, receiver, feeNumerator); } - /** - * @dev Sets maximum mintable supply. New supply cannot be larger than the old or the supply alraedy minted. - */ + /// @notice Sets the maximum mintable supply for a specific token + /// @param tokenId The ID of the token + /// @param maxMintableSupply The new maximum mintable supply function setMaxMintableSupply(uint256 tokenId, uint256 maxMintableSupply) external virtual onlyOwner { if (tokenId >= _numTokens) { revert InvalidTokenId(); @@ -186,16 +339,9 @@ contract ERC1155MInitializable is emit SetMaxMintableSupply(tokenId, maxMintableSupply); } - /** - * @dev Returns global wallet limit. This is the max number of tokens can be minted by one wallet. - */ - function getGlobalWalletLimit(uint256 tokenId) external view override returns (uint256) { - return _globalWalletLimit[tokenId]; - } - - /** - * @dev Sets global wallet limit. - */ + /// @notice Sets the global wallet limit for a specific token + /// @param tokenId The ID of the token + /// @param globalWalletLimit The new global wallet limit function setGlobalWalletLimit(uint256 tokenId, uint256 globalWalletLimit) external onlyOwner { if (tokenId >= _numTokens) { revert InvalidTokenId(); @@ -207,138 +353,77 @@ contract ERC1155MInitializable is emit SetGlobalWalletLimit(tokenId, globalWalletLimit); } - /** - * @dev Returns number of minted tokens for a given address. - */ - function totalMintedByAddress(address account) public view virtual override returns (uint256[] memory) { - uint256[] memory totalMinted = new uint256[](_numTokens); - uint256 numStages = _mintStages.length; - for (uint256 token = 0; token < _numTokens; token++) { - for (uint256 stage = 0; stage < numStages; stage++) { - totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][token][account]; - } - } - return totalMinted; - } + /// @notice Withdraws the contract's balance + function withdraw() external onlyOwner { + (bool success,) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); + if (!success) revert TransferFailed(); + _totalMintFee = 0; - /** - * @dev Returns number of minted token for a given token and address. - */ - function _totalMintedByTokenByAddress(address account, uint256 tokenId) internal view virtual returns (uint256) { - uint256 totalMinted = 0; - uint256 numStages = _mintStages.length; - for (uint256 i = 0; i < numStages; i++) { - totalMinted += _stageMintedCountsPerTokenPerWallet[i][tokenId][account]; - } - return totalMinted; - } + uint256 remainingValue = address(this).balance; + (success,) = _fundReceiver.call{value: remainingValue}(""); + if (!success) revert WithdrawFailed(); - /** - * @dev Returns number of minted tokens for a given stage and address. - */ - function _totalMintedByStageByAddress(uint256 stage, address account) - internal - view - virtual - returns (uint256[] memory) - { - uint256[] memory totalMinted = new uint256[](_numTokens); - for (uint256 token = 0; token < _numTokens; token++) { - totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][token][account]; - } - return totalMinted; + emit Withdraw(_totalMintFee + remainingValue); } - /** - * @dev Returns number of stages. - */ - function getNumberStages() external view override returns (uint256) { - return _mintStages.length; + /// @notice Withdraws ERC20 tokens from the contract + /// @dev Can only be called by the owner + function withdrawERC20() external onlyOwner { + if (_mintCurrency == address(0)) revert WrongMintCurrency(); + + uint256 totalFee = _totalMintFee; + uint256 remaining = SafeTransferLib.balanceOf(_mintCurrency, address(this)); + + if (remaining < totalFee) revert InsufficientBalance(); + + _totalMintFee = 0; + uint256 totalAmount = totalFee + remaining; + + SafeTransferLib.safeTransfer(_mintCurrency, MINT_FEE_RECEIVER, totalFee); + SafeTransferLib.safeTransfer(_mintCurrency, _fundReceiver, remaining); + + emit WithdrawERC20(_mintCurrency, totalAmount); } - /** - * @dev Returns info for one stage specified by stage index (starting from 0). - */ - function getStageInfo(uint256 stage) - external - view - override - returns (MintStageInfo1155 memory, uint256[] memory, uint256[] memory) - { - if (stage >= _mintStages.length) { - revert InvalidStage(); - } - uint256[] memory walletMinted = totalMintedByAddress(msg.sender); - uint256[] memory stageMinted = _totalMintedByStageByAddress(stage, msg.sender); - return (_mintStages[stage], walletMinted, stageMinted); + /// @notice Allows the owner to mint tokens + /// @param to The address to mint tokens to + /// @param tokenId The ID of the token to mint + /// @param qty The quantity of tokens to mint + function ownerMint(address to, uint256 tokenId, uint32 qty) external onlyOwner hasSupply(tokenId, qty) { + _mint(to, tokenId, qty, ""); } - /** - * @dev Returns mint currency address. - */ - function getMintCurrency() external view returns (address) { - return _mintCurrency; + /// @notice Adds an authorized minter + /// @param minter The address to add as an authorized minter + function addAuthorizedMinter(address minter) external override onlyOwner { + _addAuthorizedMinter(minter); } - /** - * @dev Mints token(s). - * - * tokenId - token id - * qty - number of tokens to mint - * proof - the merkle proof generated on client side. This applies if using whitelist - * timestamp - the current timestamp - * signature - the signature from cosigner if using cosigner - */ - function mint(uint256 tokenId, uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) - external - payable - virtual - nonReentrant - { - _mintInternal(msg.sender, tokenId, qty, 0, proof, timestamp, signature); - } - - /** - * @dev Mints token(s) with limit. - * - * tokenId - token id - * qty - number of tokens to mint - * limit - limit for the given minter - * proof - the merkle proof generated on client side. This applies if using whitelist - * timestamp - the current timestamp - * signature - the signature from cosigner if using cosigner - */ - function mintWithLimit( - uint256 tokenId, - uint32 qty, - uint32 limit, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { - _mintInternal(msg.sender, tokenId, qty, limit, proof, timestamp, signature); + /// @notice Removes an authorized minter + /// @param minter The address to remove as an authorized minter + function removeAuthorizedMinter(address minter) external override onlyOwner { + _removeAuthorizedMinter(minter); } - /** - * @dev Authorized mints token(s) with limit - * - * to - the token recipient - * tokenId - token id - * qty - number of tokens to mint - * limit - limit for the given minter - * proof - the merkle proof generated on client side. This applies if using whitelist - */ - function authorizedMint(address to, uint256 tokenId, uint32 qty, uint32 limit, bytes32[] calldata proof) - external - payable - onlyAuthorizedMinter - { - _mintInternal(to, tokenId, qty, limit, proof, 0, bytes("0")); + /// @notice Sets the cosigner address + /// @param cosigner The new cosigner address + function setCosigner(address cosigner) external override onlyOwner { + _cosigner = cosigner; + emit SetCosigner(cosigner); } - /** - * @dev Implementation of minting. - */ + /*============================================================== + = INTERNAL HELPERS = + ==============================================================*/ + + /// @dev Internal function to handle minting logic + /// @param to The address to mint tokens for + /// @param tokenId The ID of the token to mint + /// @param qty The quantity to mint + /// @param limit The minting limit for the recipient (used in merkle proofs) + /// @param proof The merkle proof for allowlist minting + /// @param timestamp The timestamp for the minting action (used in cosigning) + /// @param signature The cosigner's signature function _mintInternal( address to, uint256 tokenId, @@ -413,97 +498,45 @@ contract ERC1155MInitializable is _mint(to, tokenId, qty, ""); } - /** - * @dev Mints token(s) by owner. - * - * NOTE: This function bypasses validations thus only available for owner. - * This is typically used for owner to pre-mint or mint the remaining of the supply. - */ - function ownerMint(address to, uint256 tokenId, uint32 qty) external onlyOwner hasSupply(tokenId, qty) { - _mint(to, tokenId, qty, ""); - } - - /** - * @dev Withdraws funds by owner. - */ - function withdraw() external onlyOwner { - (bool success,) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); - if (!success) revert TransferFailed(); - _totalMintFee = 0; - - uint256 remainingValue = address(this).balance; - (success,) = _fundReceiver.call{value: remainingValue}(""); - if (!success) revert WithdrawFailed(); - - emit Withdraw(_totalMintFee + remainingValue); - } - - /** - * @dev Withdraws ERC-20 funds by owner. - */ - function withdrawERC20() external onlyOwner { - if (_mintCurrency == address(0)) revert WrongMintCurrency(); - - uint256 totalFee = _totalMintFee; - uint256 remaining = SafeTransferLib.balanceOf(_mintCurrency, address(this)); - - if (remaining < totalFee) revert InsufficientBalance(); - - _totalMintFee = 0; - uint256 totalAmount = totalFee + remaining; - - SafeTransferLib.safeTransfer(_mintCurrency, MINT_FEE_RECEIVER, totalFee); - SafeTransferLib.safeTransfer(_mintCurrency, _fundReceiver, remaining); - - emit WithdrawERC20(_mintCurrency, totalAmount); - } - - /** - * @dev Sets a new URI for all token types. The URI relies on token type ID - * substitution mechanism. - */ - function setURI(string calldata newURI) external onlyOwner { - _setURI(newURI); - } - - /** - * @dev Sets transferable of the tokens. - */ - function setTransferable(bool transferable) external onlyOwner { - _transferable = transferable; - emit SetTransferable(transferable); - } - - /** - * @dev Returns the current active stage based on timestamp. - */ - function getActiveStageFromTimestamp(uint64 timestamp) public view returns (uint256) { - for (uint256 i = 0; i < _mintStages.length; i++) { - if (timestamp >= _mintStages[i].startTimeUnixSeconds && timestamp < _mintStages[i].endTimeUnixSeconds) { - return i; - } + /// @dev Calculates the total minted tokens for a specific address and token ID + /// @param account The address to check + /// @param tokenId The ID of the token + /// @return The total number of tokens minted for the given address and token ID + function _totalMintedByTokenByAddress(address account, uint256 tokenId) internal view virtual returns (uint256) { + uint256 totalMinted = 0; + uint256 numStages = _mintStages.length; + for (uint256 i = 0; i < numStages; i++) { + totalMinted += _stageMintedCountsPerTokenPerWallet[i][tokenId][account]; } - revert InvalidStage(); + return totalMinted; } - function supportsInterface(bytes4 interfaceId) - public + /// @dev Calculates the total minted tokens for a specific address in a given stage + /// @param stage The stage number + /// @param account The address to check + /// @return An array of total minted tokens for each token ID in the given stage + function _totalMintedByStageByAddress(uint256 stage, address account) + internal view virtual - override(ERC2981, ERC1155Upgradeable) - returns (bool) + returns (uint256[] memory) { - return super.supportsInterface(interfaceId) || ERC2981.supportsInterface(interfaceId) - || ERC1155Upgradeable.supportsInterface(interfaceId); + uint256[] memory totalMinted = new uint256[](_numTokens); + for (uint256 token = 0; token < _numTokens; token++) { + totalMinted[token] += _stageMintedCountsPerTokenPerWallet[stage][token][account]; + } + return totalMinted; } - /** - * @dev Validates the start timestamp is before end timestamp. Used when updating stages. - */ + /// @dev Validates the start and end timestamps for a stage + /// @param start The start timestamp + /// @param end The end timestamp function _assertValidStartAndEndTimestamp(uint64 start, uint64 end) internal pure { if (start >= end) revert InvalidStartAndEndTimestamp(); } + /// @dev Validates the length of stage arguments + /// @param stageInfo The stage information to validate function _assertValidStageArgsLength(MintStageInfo1155 calldata stageInfo) internal view { if ( stageInfo.price.length != _numTokens || stageInfo.mintFee.length != _numTokens @@ -513,14 +546,4 @@ contract ERC1155MInitializable is revert InvalidStageArgsLength(); } } - - function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { - super._setDefaultRoyalty(receiver, feeNumerator); - emit DefaultRoyaltySet(receiver, feeNumerator); - } - - function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) public onlyOwner { - super._setTokenRoyalty(tokenId, receiver, feeNumerator); - emit TokenRoyaltySet(tokenId, receiver, feeNumerator); - } } diff --git a/contracts/nft/erc1155m/ERC1155MStorage.sol b/contracts/nft/erc1155m/ERC1155MStorage.sol index 39b47ec8..863244b5 100644 --- a/contracts/nft/erc1155m/ERC1155MStorage.sol +++ b/contracts/nft/erc1155m/ERC1155MStorage.sol @@ -4,8 +4,6 @@ pragma solidity ^0.8.20; import {MintStageInfo1155} from "../../common/Structs.sol"; contract ERC1155MStorage { - uint256 public constant VERSION = 1; - // Mint stage information. See MintStageInfo for details. MintStageInfo1155[] internal _mintStages; diff --git a/contracts/nft/erc1155m/interfaces/IERC1155M.sol b/contracts/nft/erc1155m/interfaces/IERC1155M.sol index 4dc9cde0..0c35a5bb 100644 --- a/contracts/nft/erc1155m/interfaces/IERC1155M.sol +++ b/contracts/nft/erc1155m/interfaces/IERC1155M.sol @@ -18,11 +18,7 @@ interface IERC1155M is ERC1155MErrorsAndEvents { view returns (MintStageInfo1155 memory, uint256[] memory, uint256[] memory); - function mint(uint256 tokenId, uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) - external - payable; - - function mintWithLimit( + function mint( uint256 tokenId, uint32 qty, uint32 limit, diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index 94c6d182..8c3b0759 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -69,8 +69,6 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi // Fund receiver address public immutable FUND_RECEIVER; - uint256 public constant VERSION = 1; - constructor( string memory collectionName, string memory collectionSymbol, diff --git a/contracts/nft/erc721m/ERC721CMInitializable.sol b/contracts/nft/erc721m/ERC721CMInitializable.sol deleted file mode 100644 index 709d8cdd..00000000 --- a/contracts/nft/erc721m/ERC721CMInitializable.sol +++ /dev/null @@ -1,530 +0,0 @@ -//SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/token/common/ERC2981.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@limitbreak/creator-token-standards/src/access/OwnableInitializable.sol"; -import "../creator-token-standards/ERC721ACQueryableInitializable.sol"; -import "./interfaces/IERC721MInitializable.sol"; -import "../../utils/Constants.sol"; - -/** - * @title ERC721CMInitializable - * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this - * contract is for use with EIP-1167 Minimal Proxies (Clones). - */ -abstract contract ERC721CMInitializable is - IERC721MInitializable, - ERC721ACQueryableInitializable, - OwnableInitializable, - ReentrancyGuard -{ - using ECDSA for bytes32; - using SafeERC20 for IERC20; - - // Whether this contract is mintable. - bool private _mintable; - - // Specify how long a signature from cosigner is valid for, recommend 300 seconds. - uint64 private _timestampExpirySeconds; - - // The crossmint address. Need to set if using crossmint. - address private _crossmintAddress; - - // The total mintable supply. - uint256 internal _maxMintableSupply; - - // Global wallet limit, across all stages. - uint256 private _globalWalletLimit; - - // Current base URI. - string private _currentBaseURI; - - // The suffix for the token URL, e.g. ".json". - string private _tokenURISuffix; - - // The uri for the storefront-level metadata for better indexing. e.g. "ipfs://UyNGgv3jx2HHfBjQX9RnKtxj2xv2xQDtbVXoRi5rJ31234" - string private _contractURI; - - // Mint stage infomation. See MintStageInfo for details. - MintStageInfo[] private _mintStages; - - // Minted count per stage per wallet. - mapping(uint256 => mapping(address => uint32)) private _stageMintedCountsPerWallet; - - // Minted count per stage. - mapping(uint256 => uint256) private _stageMintedCounts; - - // Address of ERC-20 token used to pay for minting. If 0 address, use native currency. - address private _mintCurrency; - - // Total mint fee - uint256 private _totalMintFee; - - // Fund receiver - address public FUND_RECEIVER; - - uint256 public constant VERSION = 1; - - function __ERC721CMInitializable_init( - string memory collectionName, - string memory collectionSymbol, - string memory tokenURISuffix, - uint256 maxMintableSupply, - uint256 globalWalletLimit, - uint64 timestampExpirySeconds, - address mintCurrency, - address fundReceiver - ) public onlyInitializing { - initializeOwner(msg.sender); - __ERC721ACQueryableInitializable_init(collectionName, collectionSymbol); - if (globalWalletLimit > maxMintableSupply) { - revert GlobalWalletLimitOverflow(); - } - _mintable = true; - _maxMintableSupply = maxMintableSupply; - _globalWalletLimit = globalWalletLimit; - _tokenURISuffix = tokenURISuffix; - _timestampExpirySeconds = timestampExpirySeconds; - _mintCurrency = mintCurrency; - FUND_RECEIVER = fundReceiver; - } - - /** - * @dev Returns whether mintable. - */ - modifier canMint() { - if (!_mintable) revert NotMintable(); - _; - } - - /** - * @dev Returns whether it has enough supply for the given qty. - */ - modifier hasSupply(uint256 qty) { - if (totalSupply() + qty > _maxMintableSupply) revert NoSupplyLeft(); - _; - } - - /** - * @dev Returns cosign nonce. - */ - function getCosignNonce(address minter) public view returns (uint256) { - return _numberMinted(minter); - } - - /** - * @dev Sets crossmint address if using crossmint. This allows the specified address to call `crossmint`. - */ - function setCrossmintAddress(address crossmintAddress) external onlyOwner { - _crossmintAddress = crossmintAddress; - emit SetCrossmintAddress(crossmintAddress); - } - - /** - * @dev Sets stages in the format of an array of `MintStageInfo`. - * - * Following is an example of launch with two stages. The first stage is exclusive for whitelisted wallets - * specified by merkle root. - * [{ - * price: 10000000000000000000, - * mintFee: 1000000000000000000, - * maxStageSupply: 2000, - * walletLimit: 1, - * merkleRoot: 0x559fadeb887449800b7b320bf1e92d309f329b9641ac238bebdb74e15c0a5218, - * startTimeUnixSeconds: 1667768000, - * endTimeUnixSeconds: 1667771600, - * }, - * { - * price: 20000000000000000000, - * mintFee: 0, - * maxStageSupply: 3000, - * walletLimit: 2, - * merkleRoot: 0, - * startTimeUnixSeconds: 1667771600, - * endTimeUnixSeconds: 1667775200, - * } - * ] - */ - function setStages(MintStageInfo[] calldata newStages) external onlyOwner { - delete _mintStages; - - for (uint256 i = 0; i < newStages.length;) { - if (i >= 1) { - if (newStages[i].startTimeUnixSeconds < newStages[i - 1].endTimeUnixSeconds + _timestampExpirySeconds) { - revert InsufficientStageTimeGap(); - } - } - _assertValidStartAndEndTimestamp(newStages[i].startTimeUnixSeconds, newStages[i].endTimeUnixSeconds); - _mintStages.push( - MintStageInfo({ - price: newStages[i].price, - mintFee: newStages[i].mintFee, - walletLimit: newStages[i].walletLimit, - merkleRoot: newStages[i].merkleRoot, - maxStageSupply: newStages[i].maxStageSupply, - startTimeUnixSeconds: newStages[i].startTimeUnixSeconds, - endTimeUnixSeconds: newStages[i].endTimeUnixSeconds - }) - ); - emit UpdateStage( - i, - newStages[i].price, - newStages[i].mintFee, - newStages[i].walletLimit, - newStages[i].merkleRoot, - newStages[i].maxStageSupply, - newStages[i].startTimeUnixSeconds, - newStages[i].endTimeUnixSeconds - ); - - unchecked { - ++i; - } - } - } - - /** - * @dev Gets whether mintable. - */ - function getMintable() external view returns (bool) { - return _mintable; - } - - /** - * @dev Sets mintable. - */ - function setMintable(bool mintable) external onlyOwner { - _mintable = mintable; - emit SetMintable(mintable); - } - - /** - * @dev Returns number of stages. - */ - function getNumberStages() external view override returns (uint256) { - return _mintStages.length; - } - - /** - * @dev Returns maximum mintable supply. - */ - function getMaxMintableSupply() external view override returns (uint256) { - return _maxMintableSupply; - } - - /** - * @dev Sets maximum mintable supply. - * - * New supply cannot be larger than the old. - */ - function setMaxMintableSupply(uint256 maxMintableSupply) external virtual onlyOwner { - if (maxMintableSupply > _maxMintableSupply) { - revert CannotIncreaseMaxMintableSupply(); - } - _maxMintableSupply = maxMintableSupply; - emit SetMaxMintableSupply(maxMintableSupply); - } - - /** - * @dev Returns global wallet limit. This is the max number of tokens can be minted by one wallet. - */ - function getGlobalWalletLimit() external view override returns (uint256) { - return _globalWalletLimit; - } - - /** - * @dev Sets global wallet limit. - */ - function setGlobalWalletLimit(uint256 globalWalletLimit) external onlyOwner { - if (globalWalletLimit > _maxMintableSupply) { - revert GlobalWalletLimitOverflow(); - } - _globalWalletLimit = globalWalletLimit; - emit SetGlobalWalletLimit(globalWalletLimit); - } - - /** - * @dev Returns number of minted token for a given address. - */ - function totalMintedByAddress(address a) external view virtual override returns (uint256) { - return _numberMinted(a); - } - - /** - * @dev Returns info for one stage specified by index (starting from 0). - */ - function getStageInfo(uint256 index) external view override returns (MintStageInfo memory, uint32, uint256) { - if (index >= _mintStages.length) { - revert("InvalidStage"); - } - uint32 walletMinted = _stageMintedCountsPerWallet[index][msg.sender]; - uint256 stageMinted = _stageMintedCounts[index]; - return (_mintStages[index], walletMinted, stageMinted); - } - - /** - * @dev Returns mint currency address. - */ - function getMintCurrency() external view returns (address) { - return _mintCurrency; - } - - /** - * @dev Mints token(s). - * - * qty - number of tokens to mint - * proof - the merkle proof generated on client side. This applies if using whitelist. - * timestamp - the current timestamp - * signature - the signature from cosigner if using cosigner. - */ - function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) - external - payable - virtual - nonReentrant - { - _mintInternal(qty, msg.sender, 0, proof); - } - - /** - * @dev Mints token(s) with limit. - * - * qty - number of tokens to mint - * limit - limit for the given minter - * proof - the merkle proof generated on client side. This applies if using whitelist. - * timestamp - the current timestamp - * signature - the signature from cosigner if using cosigner. - */ - function mintWithLimit( - uint32 qty, - uint32 limit, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { - _mintInternal(qty, msg.sender, limit, proof); - } - - /** - * @dev Mints token(s) through crossmint. This function is supposed to be called by crossmint. - * - * qty - number of tokens to mint - * to - the address to mint tokens to - * proof - the merkle proof generated on client side. This applies if using whitelist. - * timestamp - the current timestamp - * signature - the signature from cosigner if using cosigner. - */ - function crossmint(uint32 qty, address to, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) - external - payable - nonReentrant - { - if (_crossmintAddress == address(0)) revert CrossmintAddressNotSet(); - - // Check the caller is Crossmint - if (msg.sender != _crossmintAddress) revert CrossmintOnly(); - - _mintInternal(qty, to, 0, proof); - } - - /** - * @dev Implementation of minting. - */ - function _mintInternal(uint32 qty, address to, uint32 limit, bytes32[] calldata proof) - internal - canMint - hasSupply(qty) - { - uint64 stageTimestamp = uint64(block.timestamp); - - MintStageInfo memory stage; - - uint256 activeStage = getActiveStageFromTimestamp(stageTimestamp); - - stage = _mintStages[activeStage]; - - // Check value if minting with ETH - if (_mintCurrency == address(0) && msg.value < (stage.price + stage.mintFee) * qty) revert NotEnoughValue(); - - // Check stage supply if applicable - if (stage.maxStageSupply > 0) { - if (_stageMintedCounts[activeStage] + qty > stage.maxStageSupply) { - revert StageSupplyExceeded(); - } - } - - // Check global wallet limit if applicable - if (_globalWalletLimit > 0) { - if (_numberMinted(to) + qty > _globalWalletLimit) { - revert WalletGlobalLimitExceeded(); - } - } - - // Check wallet limit for stage if applicable, limit == 0 means no limit enforced - if (stage.walletLimit > 0) { - if (_stageMintedCountsPerWallet[activeStage][to] + qty > stage.walletLimit) { - revert WalletStageLimitExceeded(); - } - } - - // Check merkle proof if applicable, merkleRoot == 0x00...00 means no proof required - if (stage.merkleRoot != 0) { - if (MerkleProof.processProof(proof, keccak256(abi.encodePacked(to, limit))) != stage.merkleRoot) { - revert InvalidProof(); - } - - // Verify merkle proof mint limit - if (limit > 0 && _stageMintedCountsPerWallet[activeStage][to] + qty > limit) { - revert WalletStageLimitExceeded(); - } - } - - if (_mintCurrency != address(0)) { - IERC20(_mintCurrency).safeTransferFrom(msg.sender, address(this), (stage.price + stage.mintFee) * qty); - } - - _totalMintFee += stage.mintFee * qty; - - _stageMintedCountsPerWallet[activeStage][to] += qty; - _stageMintedCounts[activeStage] += qty; - _safeMint(to, qty); - } - - /** - * @dev Mints token(s) by owner. - * - * NOTE: This function bypasses validations thus only available for owner. - * This is typically used for owner to pre-mint or mint the remaining of the supply. - */ - function ownerMint(uint32 qty, address to) external onlyOwner hasSupply(qty) { - _safeMint(to, qty); - } - - /** - * @dev Withdraws funds by owner. - */ - function withdraw() external onlyOwner { - (bool success,) = MINT_FEE_RECEIVER.call{value: _totalMintFee}(""); - if (!success) revert TransferFailed(); - _totalMintFee = 0; - - uint256 remainingValue = address(this).balance; - (success,) = FUND_RECEIVER.call{value: remainingValue}(""); - if (!success) revert WithdrawFailed(); - - emit Withdraw(_totalMintFee + remainingValue); - } - - /** - * @dev Withdraws ERC-20 funds by owner. - */ - function withdrawERC20() external onlyOwner { - if (_mintCurrency == address(0)) revert WrongMintCurrency(); - - IERC20(_mintCurrency).safeTransfer(MINT_FEE_RECEIVER, _totalMintFee); - _totalMintFee = 0; - - uint256 remaining = IERC20(_mintCurrency).balanceOf(address(this)); - IERC20(_mintCurrency).safeTransfer(FUND_RECEIVER, remaining); - - emit WithdrawERC20(_mintCurrency, _totalMintFee + remaining); - } - - /** - * @dev Sets token base URI. - */ - function setBaseURI(string calldata baseURI) external onlyOwner { - _currentBaseURI = baseURI; - emit SetBaseURI(baseURI); - } - - /** - * @dev Sets token URI suffix. e.g. ".json". - */ - function setTokenURISuffix(string calldata suffix) external onlyOwner { - _tokenURISuffix = suffix; - } - - /** - * @dev Returns token URI for a given token id. - */ - function tokenURI(uint256 tokenId) - public - view - override(ERC721AUpgradeable, IERC721AUpgradeable) - returns (string memory) - { - if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); - - string memory baseURI = _currentBaseURI; - return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, _toString(tokenId), _tokenURISuffix)) : ""; - } - - /** - * @dev Returns URI for the collection-level metadata. - */ - function contractURI() public view returns (string memory) { - return _contractURI; - } - - /** - * @dev Set the URI for the collection-level metadata. - */ - function setContractURI(string calldata uri) external onlyOwner { - _contractURI = uri; - } - - /** - * @dev Returns the current active stage based on timestamp. - */ - function getActiveStageFromTimestamp(uint64 timestamp) public view returns (uint256) { - for (uint256 i = 0; i < _mintStages.length;) { - if (timestamp >= _mintStages[i].startTimeUnixSeconds && timestamp < _mintStages[i].endTimeUnixSeconds) { - return i; - } - unchecked { - ++i; - } - } - revert InvalidStage(); - } - - /** - * @dev Validates the start timestamp is before end timestamp. Used when updating stages. - */ - function _assertValidStartAndEndTimestamp(uint64 start, uint64 end) internal pure { - if (start >= end) revert InvalidStartAndEndTimestamp(); - } - - /** - * @dev Returns chain id. - */ - function _chainID() private view returns (uint256) { - uint256 chainID; - /// @solidity memory-safe-assembly - assembly { - chainID := chainid() - } - return chainID; - } - - function _requireCallerIsContractOwner() internal view virtual override(OwnableInitializable, OwnablePermissions) { - _checkOwner(); - } - - /** - * @notice Returns the function selector for the transfer validator's validation function to be called - * @notice for transaction simulation. - */ - function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { - functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); - isViewFunction = true; - } -} diff --git a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol b/contracts/nft/erc721m/ERC721CMInitializableV1_0_0.sol similarity index 96% rename from contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol rename to contracts/nft/erc721m/ERC721CMInitializableV1_0_0.sol index dc7b4ce4..77349315 100644 --- a/contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol +++ b/contracts/nft/erc721m/ERC721CMInitializableV1_0_0.sol @@ -12,19 +12,19 @@ import { ERC721ACQueryableInitializable, ERC721AUpgradeable, IERC721AUpgradeable -} from "../../creator-token-standards/ERC721ACQueryableInitializable.sol"; -import {MINT_FEE_RECEIVER} from "../../../utils/Constants.sol"; -import {MintStageInfo} from "../../../common/Structs.sol"; -import {IERC721MInitializable} from "../interfaces/IERC721MInitializable.sol"; -import {Cosignable} from "../../../common/Cosignable.sol"; -import {AuthorizedMinterControl} from "../../../common/AuthorizedMinterControl.sol"; +} from "../creator-token-standards/ERC721ACQueryableInitializable.sol"; +import {MINT_FEE_RECEIVER} from "../../utils/Constants.sol"; +import {MintStageInfo} from "../../common/Structs.sol"; +import {IERC721MInitializable} from "./interfaces/IERC721MInitializable.sol"; +import {Cosignable} from "../../common/Cosignable.sol"; +import {AuthorizedMinterControl} from "../../common/AuthorizedMinterControl.sol"; /** - * @title ERC721CMInitializableV2 + * @title ERC721CMInitializableV1_0_0 * @dev This contract is not meant for use in Upgradeable Proxy contracts though it may base on Upgradeable contract. The purpose of this * contract is for use with EIP-1167 Minimal Proxies (Clones). */ -contract ERC721CMInitializableV2 is +contract ERC721CMInitializableV1_0_0 is IERC721MInitializable, ERC721ACQueryableInitializable, ERC2981, @@ -72,8 +72,6 @@ contract ERC721CMInitializableV2 is // Fund receiver address private _fundReceiver; - uint256 public constant VERSION = 2; - constructor() { _disableInitializers(); } @@ -600,4 +598,8 @@ contract ERC721CMInitializableV2 is function _requireCallerIsContractOwner() internal view override { return _checkOwner(); } + + function contractNameAndVersion() public pure returns (string memory, string memory) { + return ("ERC721CMInitializable", "1.0.0"); + } } diff --git a/contracts/nft/erc721m/ERC721M.sol b/contracts/nft/erc721m/ERC721M.sol index 2bf73585..4125b594 100644 --- a/contracts/nft/erc721m/ERC721M.sol +++ b/contracts/nft/erc721m/ERC721M.sol @@ -68,8 +68,6 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosign // Fund receiver address public immutable FUND_RECEIVER; - uint256 public constant VERSION = 1; - constructor( string memory collectionName, string memory collectionSymbol, diff --git a/test/erc721m/ERC721CMInitializableV2Test.t.sol b/test/erc721m/ERC721CMInitializableV2Test.t.sol index 658bfb45..225b5086 100644 --- a/test/erc721m/ERC721CMInitializableV2Test.t.sol +++ b/test/erc721m/ERC721CMInitializableV2Test.t.sol @@ -6,12 +6,13 @@ import {Ownable} from "solady/src/auth/Ownable.sol"; import {IERC721A} from "erc721a/contracts/IERC721A.sol"; import {Test} from "forge-std/Test.sol"; -import {ERC721CMInitializableV2} from "../../contracts/nft/erc721m/v2/ERC721CMInitializableV2.sol"; +import {ERC721CMInitializableV1_0_0 as ERC721CMInitializable} from + "../../contracts/nft/erc721m/ERC721CMInitializableV1_0_0.sol"; import {MintStageInfo} from "../../contracts/common/Structs.sol"; import {ErrorsAndEvents} from "../../contracts/common/ErrorsAndEvents.sol"; -contract ERC721CMInitializableV2Test is Test { - ERC721CMInitializableV2 public nft; +contract ERC721CMInitializableTest is Test { + ERC721CMInitializable public nft; address public owner; address public minter; address public fundReceiver; @@ -31,8 +32,8 @@ contract ERC721CMInitializableV2Test is Test { vm.deal(minter, 2 ether); vm.deal(crossmintAddress, 1 ether); - address clone = LibClone.deployERC1967(address(new ERC721CMInitializableV2())); - nft = ERC721CMInitializableV2(clone); + address clone = LibClone.deployERC1967(address(new ERC721CMInitializable())); + nft = ERC721CMInitializable(clone); nft.initialize("Test", "TEST", owner); nft.setup( ".json", diff --git a/test/factory/MagicDropCloneFactoryTest.t.sol b/test/factory/MagicDropCloneFactoryTest.t.sol index adf7ba35..e283c1e2 100644 --- a/test/factory/MagicDropCloneFactoryTest.t.sol +++ b/test/factory/MagicDropCloneFactoryTest.t.sol @@ -8,8 +8,6 @@ import {LibClone} from "solady/src/utils/LibClone.sol"; import {MagicDropCloneFactory} from "../../contracts/factory/MagicDropCloneFactory.sol"; import {MagicDropTokenImplRegistry} from "../../contracts/registry/MagicDropTokenImplRegistry.sol"; -import {MagicDropERC721Initializable} from "../../contracts/nft/MagicDropERC721Initializable.sol"; -import {MagicDropERC1155Initializable} from "../../contracts/nft/MagicDropERC1155Initializable.sol"; import {TokenStandard} from "../../contracts/common/Structs.sol"; contract MockERC721Initializable is MockERC721 { From 3aeaf35b9fa002980f9429697175e60e4485a14a Mon Sep 17 00:00:00 2001 From: Wolfy Date: Sun, 6 Oct 2024 16:56:47 -0400 Subject: [PATCH 047/145] remove mint with limit Signed-off-by: Wolfy --- contracts/mocks/TestReentrantExploit.sol | 4 +- contracts/nft/erc721m/ERC721CM.sol | 21 +- .../erc721m/ERC721CMInitializableV1_0_0.sol | 21 +- contracts/nft/erc721m/ERC721M.sol | 21 +- contracts/nft/erc721m/interfaces/IERC721M.sol | 12 +- .../interfaces/IERC721MInitializable.sol | 10 +- test/erc1155m/ERC1155M.test.ts | 428 ++++++++++-------- test/erc721m/ERC721CM.test.ts | 16 +- test/erc721m/ERC721M.test.ts | 16 +- 9 files changed, 254 insertions(+), 295 deletions(-) diff --git a/contracts/mocks/TestReentrantExploit.sol b/contracts/mocks/TestReentrantExploit.sol index 37b6960a..1c8644d3 100644 --- a/contracts/mocks/TestReentrantExploit.sol +++ b/contracts/mocks/TestReentrantExploit.sol @@ -11,7 +11,7 @@ contract TestReentrantExploit { } function exploit(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) public payable { - ERC721M(_targetContract).mint{value: msg.value}(qty, proof, timestamp, signature); + ERC721M(_targetContract).mint{value: msg.value}(qty, 0, proof, timestamp, signature); } function onERC721Received( @@ -21,7 +21,7 @@ contract TestReentrantExploit { bytes calldata // unused param: data ) public payable returns (bytes4) { bytes32[] memory proof; - ERC721M(_targetContract).mint{value: msg.value}(1, proof, 0, ""); + ERC721M(_targetContract).mint{value: msg.value}(1, 0, proof, 0, ""); return ERC721A__IERC721Receiver(operator).onERC721Received.selector; } } diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index 8c3b0759..6597ae24 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -309,31 +309,12 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard, Cosi * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + function mint(uint32 qty, uint32 limit, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable virtual nonReentrant { - _mintInternal(qty, msg.sender, 0, proof, timestamp, signature); - } - - /** - * @dev Mints token(s) with limit. - * - * qty - number of tokens to mint - * limit - limit for the given minter - * proof - the merkle proof generated on client side. This applies if using whitelist. - * timestamp - the current timestamp - * signature - the signature from cosigner if using cosigner. - */ - function mintWithLimit( - uint32 qty, - uint32 limit, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { _mintInternal(qty, msg.sender, limit, proof, timestamp, signature); } diff --git a/contracts/nft/erc721m/ERC721CMInitializableV1_0_0.sol b/contracts/nft/erc721m/ERC721CMInitializableV1_0_0.sol index 77349315..676b592d 100644 --- a/contracts/nft/erc721m/ERC721CMInitializableV1_0_0.sol +++ b/contracts/nft/erc721m/ERC721CMInitializableV1_0_0.sol @@ -305,31 +305,12 @@ contract ERC721CMInitializableV1_0_0 is * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + function mint(uint32 qty, uint32 limit, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable virtual nonReentrant { - _mintInternal(qty, msg.sender, 0, proof, timestamp, signature); - } - - /** - * @dev Mints token(s) with limit. - * - * qty - number of tokens to mint - * limit - limit for the given minter - * proof - the merkle proof generated on client side. This applies if using whitelist. - * timestamp - the current timestamp - * signature - the signature from cosigner if using cosigner. - */ - function mintWithLimit( - uint32 qty, - uint32 limit, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { _mintInternal(qty, msg.sender, limit, proof, timestamp, signature); } diff --git a/contracts/nft/erc721m/ERC721M.sol b/contracts/nft/erc721m/ERC721M.sol index 4125b594..3e811b12 100644 --- a/contracts/nft/erc721m/ERC721M.sol +++ b/contracts/nft/erc721m/ERC721M.sol @@ -304,31 +304,12 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard, Cosign * timestamp - the current timestamp * signature - the signature from cosigner if using cosigner. */ - function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + function mint(uint32 qty, uint32 limit, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable virtual nonReentrant { - _mintInternal(qty, msg.sender, 0, proof, timestamp, signature); - } - - /** - * @dev Mints token(s) with limit. - * - * qty - number of tokens to mint - * limit - limit for the given minter - * proof - the merkle proof generated on client side. This applies if using whitelist. - * timestamp - the current timestamp - * signature - the signature from cosigner if using cosigner. - */ - function mintWithLimit( - uint32 qty, - uint32 limit, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable virtual nonReentrant { _mintInternal(qty, msg.sender, limit, proof, timestamp, signature); } diff --git a/contracts/nft/erc721m/interfaces/IERC721M.sol b/contracts/nft/erc721m/interfaces/IERC721M.sol index 2dcf46f0..2993f8d6 100644 --- a/contracts/nft/erc721m/interfaces/IERC721M.sol +++ b/contracts/nft/erc721m/interfaces/IERC721M.sol @@ -16,15 +16,9 @@ interface IERC721M is IERC721AQueryable, ERC721MErrorsAndEvents { function getStageInfo(uint256 index) external view returns (MintStageInfo memory, uint32, uint256); - function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable; - - function mintWithLimit( - uint32 qty, - uint32 limit, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable; + function mint(uint32 qty, uint32 limit, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) + external + payable; function crossmint(uint32 qty, address to, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external diff --git a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol index 785b3f48..2391876c 100644 --- a/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol +++ b/contracts/nft/erc721m/interfaces/IERC721MInitializable.sol @@ -21,15 +21,7 @@ interface IERC721MInitializable is IERC721AQueryableUpgradeable, ERC721MErrorsAn function getStageInfo(uint256 index) external view returns (MintStageInfo memory, uint32, uint256); - function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable; - - function mintWithLimit( - uint32 qty, - uint32 limit, - bytes32[] calldata proof, - uint64 timestamp, - bytes calldata signature - ) external payable; + function mint(uint32 qty, uint32 limit, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable; function crossmint(uint32 qty, address to, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external diff --git a/test/erc1155m/ERC1155M.test.ts b/test/erc1155m/ERC1155M.test.ts index 23da5295..9bedadd4 100644 --- a/test/erc1155m/ERC1155M.test.ts +++ b/test/erc1155m/ERC1155M.test.ts @@ -442,16 +442,9 @@ describe('ERC1155M', function () { // Reset stages to empty await contract.setStages([]); - const mint = contract.mint( - 0, - 1, - [ZERO_PROOF], - 0, - '0x00', - { - value: parseEther('0.5'), - }, - ); + const mint = contract.mint(0, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.5'), + }); await expect(mint).to.be.revertedWith('InvalidStage'); }); @@ -533,16 +526,9 @@ describe('ERC1155M', function () { ]); // Mint 101 tokens (1 over MaxMintableSupply) - const mint = contract.mint( - 0, - 101, - [ZERO_PROOF], - 0, - '0x00', - { - value: parseEther('1'), - }, - ); + const mint = contract.mint(0, 101, [ZERO_PROOF], 0, '0x00', { + value: parseEther('1'), + }); await expect(mint).to.be.revertedWith('NoSupplyLeft'); }); @@ -565,16 +551,11 @@ describe('ERC1155M', function () { }); // Mint one more should fail - await expect(contract.mint( - 0, - 1, - [ZERO_PROOF], - 0, - '0x00', - { + await expect( + contract.mint(0, 1, [ZERO_PROOF], 0, '0x00', { value: parseEther('0.01'), - }, - )).to.be.revertedWith('WalletStageLimitExceeded'); + }), + ).to.be.revertedWith('WalletStageLimitExceeded'); }); it('mint with limited stage supply', async () => { @@ -596,16 +577,9 @@ describe('ERC1155M', function () { }); // Mint one more should fail - const mint = contract.mint( - 0, - 1, - [ZERO_PROOF], - 0, - '0x00', - { - value: parseEther('0.01'), - }, - ); + const mint = contract.mint(0, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.01'), + }); await expect(mint).to.be.revertedWith('StageSupplyExceeded'); }); @@ -623,30 +597,31 @@ describe('ERC1155M', function () { }, ]); - const contractBalanceInitial = await ethers.provider.getBalance(contract.address); - const mintFeeReceiverBalanceInitial = await ethers.provider.getBalance(MINT_FEE_RECEIVER); - - await readonlyContract.mint( - 0, - 1, - [ZERO_PROOF], - 0, - '0x00', - { - value: parseEther('0'), - }, + const contractBalanceInitial = await ethers.provider.getBalance( + contract.address, ); + const mintFeeReceiverBalanceInitial = + await ethers.provider.getBalance(MINT_FEE_RECEIVER); + + await readonlyContract.mint(0, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0'), + }); const [stageInfo, walletMintedCount, stagedMintedCount] = await readonlyContract.getStageInfo(0); expect(stageInfo.maxStageSupply).to.eql([100]); expect(walletMintedCount).to.eql([BigNumber.from(1)]); expect(stagedMintedCount).to.eql([BigNumber.from(1)]); - const contractBalancePost = await ethers.provider.getBalance(contract.address) + const contractBalancePost = await ethers.provider.getBalance( + contract.address, + ); expect(contractBalancePost.sub(contractBalanceInitial)).to.equal(0); - const mintFeeReceiverBalancePost = await ethers.provider.getBalance(MINT_FEE_RECEIVER) - expect(mintFeeReceiverBalancePost.sub(mintFeeReceiverBalanceInitial)).to.equal(0); + const mintFeeReceiverBalancePost = + await ethers.provider.getBalance(MINT_FEE_RECEIVER); + expect( + mintFeeReceiverBalancePost.sub(mintFeeReceiverBalanceInitial), + ).to.equal(0); }); it('mint with free stage with mint fee', async () => { @@ -662,19 +637,15 @@ describe('ERC1155M', function () { }, ]); - const contractBalanceInitial = await ethers.provider.getBalance(contract.address); - const mintFeeReceiverBalanceInitial = await ethers.provider.getBalance(MINT_FEE_RECEIVER); - - await readonlyContract.mint( - 0, - 1, - [ZERO_PROOF], - 0, - '0x00', - { - value: parseEther('0.1'), - }, + const contractBalanceInitial = await ethers.provider.getBalance( + contract.address, ); + const mintFeeReceiverBalanceInitial = + await ethers.provider.getBalance(MINT_FEE_RECEIVER); + + await readonlyContract.mint(0, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.1'), + }); await contract.withdraw(); @@ -684,11 +655,16 @@ describe('ERC1155M', function () { expect(walletMintedCount).to.eql([BigNumber.from(1)]); expect(stagedMintedCount).to.eql([BigNumber.from(1)]); - const contractBalancePost = await ethers.provider.getBalance(contract.address) + const contractBalancePost = await ethers.provider.getBalance( + contract.address, + ); expect(contractBalancePost.sub(contractBalanceInitial)).to.equal(0); - const mintFeeReceiverBalancePost = await ethers.provider.getBalance(MINT_FEE_RECEIVER) - expect(mintFeeReceiverBalancePost.sub(mintFeeReceiverBalanceInitial)).to.equal(parseEther('0.1')); + const mintFeeReceiverBalancePost = + await ethers.provider.getBalance(MINT_FEE_RECEIVER); + expect( + mintFeeReceiverBalancePost.sub(mintFeeReceiverBalanceInitial), + ).to.equal(parseEther('0.1')); }); it('mint with mint fee waived', async () => { @@ -710,16 +686,18 @@ describe('ERC1155M', function () { false, ); - await expect(readonlyContract.mint( - 0, - 1, - [ethers.utils.hexZeroPad('0x', 32)], - timestamp, - sig, - { - value: ethers.utils.parseEther('0.4'), // price = 0.4, mintFee = 0.1 - }, - )).to.be.rejectedWith('NotEnoughValue');; + await expect( + readonlyContract.mint( + 0, + 1, + [ethers.utils.hexZeroPad('0x', 32)], + timestamp, + sig, + { + value: ethers.utils.parseEther('0.4'), // price = 0.4, mintFee = 0.1 + }, + ), + ).to.be.rejectedWith('NotEnoughValue'); sig = getCosignSignature( contract, @@ -737,7 +715,7 @@ describe('ERC1155M', function () { timestamp, sig, { - value: ethers.utils.parseEther('0.4'), // price = 0.4, mintFee = 0.1 + value: ethers.utils.parseEther('0.4'), // price = 0.4, mintFee = 0.1 }, ); const [stageInfo, walletMintedCount, stagedMintedCount] = @@ -861,54 +839,58 @@ describe('ERC1155M', function () { ]); // Owner mints 1 token with valid proof - await contract.mintWithLimit(0, 1, 2, ownerProof, 0, '0x00', { + await contract.mint(0, 1, 2, ownerProof, 0, '0x00', { value: parseEther('0.1'), }); - expect(await contract.totalMintedByAddress(owner.getAddress())).to.eql([BigNumber.from(1)]); + expect(await contract.totalMintedByAddress(owner.getAddress())).to.eql([ + BigNumber.from(1), + ]); // Owner mints 1 token with wrong limit and should be reverted. await expect( - contract.mintWithLimit(0, 1, 3, ownerProof, 0, '0x00', { + contract.mint(0, 1, 3, ownerProof, 0, '0x00', { value: parseEther('0.1'), }), ).to.be.rejectedWith('InvalidProof'); // Owner mints 2 tokens with valid proof and reverts. await expect( - contract.mintWithLimit(0, 2, 2, ownerProof, 0, '0x00', { + contract.mint(0, 2, 2, ownerProof, 0, '0x00', { value: parseEther('0.2'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Owner mints 1 token with valid proof. Now owner reaches the limit. - await contract.mintWithLimit(0, 1, 2, ownerProof, 0, '0x00', { + await contract.mint(0, 1, 2, ownerProof, 0, '0x00', { value: parseEther('0.1'), }); - expect((await contract.totalMintedByAddress(owner.getAddress()))).to.eql([BigNumber.from(2)]); + expect(await contract.totalMintedByAddress(owner.getAddress())).to.eql([ + BigNumber.from(2), + ]); // Owner tries to mint more and reverts. await expect( - contract.mintWithLimit(0, 1, 2, ownerProof, 0, '0x00', { + contract.mint(0, 1, 2, ownerProof, 0, '0x00', { value: parseEther('0.1'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Reader mints 6 tokens with valid proof and reverts. await expect( - readonlyContract.mintWithLimit(0, 6, 5, readerProof, 0, '0x00', { + readonlyContract.mint(0, 6, 5, readerProof, 0, '0x00', { value: parseEther('0.6'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Reader mints 5 tokens with valid proof. - await readonlyContract.mintWithLimit(0, 5, 5, readerProof, 0, '0x00', { + await readonlyContract.mint(0, 5, 5, readerProof, 0, '0x00', { value: parseEther('0.5'), }); // Reader mints 1 token with valid proof and reverts. await expect( - readonlyContract.mintWithLimit(0, 1, 5, readerProof, 0, '0x00', { + readonlyContract.mint(0, 1, 5, readerProof, 0, '0x00', { value: parseEther('0.1'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); @@ -918,7 +900,8 @@ describe('ERC1155M', function () { const [owner, address1] = await ethers.getSigners(); await contract.ownerMint(owner.address, 0, 5); - const [, walletMintedCount, stageMintedCount] = await contract.getStageInfo(0); + const [, walletMintedCount, stageMintedCount] = + await contract.getStageInfo(0); expect(walletMintedCount).to.eql([BigNumber.from(0)]); expect(stageMintedCount).to.eql([BigNumber.from(0)]); @@ -927,16 +910,17 @@ describe('ERC1155M', function () { expect(ownerBalance.toNumber()).to.equal(5); await contract.ownerMint(address1.address, 0, 5); - const [, address1Minted, address1StageMintedCount] = await readonlyContract.getStageInfo(0, { - from: address1.address, - }); + const [, address1Minted, address1StageMintedCount] = + await readonlyContract.getStageInfo(0, { + from: address1.address, + }); expect(address1Minted).to.eql([BigNumber.from(0)]); expect(address1StageMintedCount).to.eql([BigNumber.from(0)]); const address1Balance = await contract.balanceOf(address1.address, 0); expect(address1Balance.toNumber()).to.equal(5); - expect(await contract["totalSupply()"].apply(0)).to.equal(10); + expect(await contract['totalSupply()'].apply(0)).to.equal(10); }); it('mints by owner - invalid cases', async () => { @@ -1004,7 +988,7 @@ describe('ERC1155M', function () { { value: ethers.utils.parseEther('0.5'), }, - ) + ), ).to.be.revertedWith('InvalidCosignSignature'); // invalid because of unexptected sig @@ -1018,7 +1002,7 @@ describe('ERC1155M', function () { { value: ethers.utils.parseEther('0.5'), }, - ) + ), ).to.be.revertedWith('InvalidCosignSignature'); await expect( @@ -1031,7 +1015,7 @@ describe('ERC1155M', function () { { value: ethers.utils.parseEther('0.5'), }, - ) + ), ).to.be.revertedWith('InvalidCosignSignature'); await expect( @@ -1044,7 +1028,7 @@ describe('ERC1155M', function () { { value: ethers.utils.parseEther('0.5'), }, - ) + ), ).to.be.rejectedWith('invalid arrayify'); await expect( @@ -1057,7 +1041,7 @@ describe('ERC1155M', function () { { value: ethers.utils.parseEther('0.5'), }, - ) + ), ).to.be.rejectedWith('invalid arrayify'); }); }); @@ -1124,20 +1108,23 @@ describe('ERC1155M', function () { }); // Mint one more should fail - await expect(contract.mint(0, 1, [ZERO_PROOF], 0, '0x00', { - value: parseEther('0.2'), - }, - )).to.be.revertedWith('WalletStageLimitExceeded'); + await expect( + contract.mint(0, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.2'), + }), + ).to.be.revertedWith('WalletStageLimitExceeded'); - await expect(contract.mint(1, 1, [ZERO_PROOF], 0, '0x00', { - value: parseEther('0.3'), - }, - )).to.be.revertedWith('WalletStageLimitExceeded'); + await expect( + contract.mint(1, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.3'), + }), + ).to.be.revertedWith('WalletStageLimitExceeded'); - await expect(contract.mint(2, 1, [ZERO_PROOF], 0, '0x00', { - value: parseEther('0.3'), - }, - )).to.be.revertedWith('WalletStageLimitExceeded'); + await expect( + contract.mint(2, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.3'), + }), + ).to.be.revertedWith('WalletStageLimitExceeded'); }); it('mint with limited stage supply', async () => { @@ -1169,20 +1156,23 @@ describe('ERC1155M', function () { }); // Mint one more should fail - await expect(contract.mint(0, 1, [ZERO_PROOF], 0, '0x00', { - value: parseEther('0.2'), - }, - )).to.be.revertedWith('StageSupplyExceeded'); + await expect( + contract.mint(0, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.2'), + }), + ).to.be.revertedWith('StageSupplyExceeded'); - await expect(contract.mint(1, 1, [ZERO_PROOF], 0, '0x00', { - value: parseEther('0.3'), - }, - )).to.be.revertedWith('StageSupplyExceeded'); + await expect( + contract.mint(1, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.3'), + }), + ).to.be.revertedWith('StageSupplyExceeded'); - await expect(contract.mint(2, 1, [ZERO_PROOF], 0, '0x00', { - value: parseEther('0.3'), - }, - )).to.be.revertedWith('StageSupplyExceeded'); + await expect( + contract.mint(2, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.3'), + }), + ).to.be.revertedWith('StageSupplyExceeded'); }); it('mint with free stage', async () => { @@ -1198,30 +1188,44 @@ describe('ERC1155M', function () { }, ]); - const contractBalanceInitial = await ethers.provider.getBalance(contract.address); - const mintFeeReceiverBalanceInitial = await ethers.provider.getBalance(MINT_FEE_RECEIVER); + const contractBalanceInitial = await ethers.provider.getBalance( + contract.address, + ); + const mintFeeReceiverBalanceInitial = + await ethers.provider.getBalance(MINT_FEE_RECEIVER); await readonlyContract.mint(1, 5, [ZERO_PROOF], 0, '0x00', { value: parseEther('0'), - }, - ); + }); await readonlyContract.mint(2, 10, [ZERO_PROOF], 0, '0x00', { value: parseEther('0'), - }, - ); + }); const [stageInfo, walletMintedCount, stagedMintedCount] = await readonlyContract.getStageInfo(0); expect(stageInfo.maxStageSupply).to.eql([10, 10, 0]); - expect(walletMintedCount).to.eql([BigNumber.from(0), BigNumber.from(5), BigNumber.from(10)]); - expect(stagedMintedCount).to.eql([BigNumber.from(0), BigNumber.from(5), BigNumber.from(10)]); + expect(walletMintedCount).to.eql([ + BigNumber.from(0), + BigNumber.from(5), + BigNumber.from(10), + ]); + expect(stagedMintedCount).to.eql([ + BigNumber.from(0), + BigNumber.from(5), + BigNumber.from(10), + ]); - const contractBalancePost = await ethers.provider.getBalance(contract.address) + const contractBalancePost = await ethers.provider.getBalance( + contract.address, + ); expect(contractBalancePost.sub(contractBalanceInitial)).to.equal(0); - const mintFeeReceiverBalancePost = await ethers.provider.getBalance(MINT_FEE_RECEIVER) - expect(mintFeeReceiverBalancePost.sub(mintFeeReceiverBalanceInitial)).to.equal(0); + const mintFeeReceiverBalancePost = + await ethers.provider.getBalance(MINT_FEE_RECEIVER); + expect( + mintFeeReceiverBalancePost.sub(mintFeeReceiverBalanceInitial), + ).to.equal(0); }); it('mint with free stage with mint fee', async () => { @@ -1237,54 +1241,49 @@ describe('ERC1155M', function () { }, ]); - const contractBalanceInitial = await ethers.provider.getBalance(contract.address); - const mintFeeReceiverBalanceInitial = await ethers.provider.getBalance(MINT_FEE_RECEIVER); - - await readonlyContract.mint( - 0, - 1, - [ZERO_PROOF], - 0, - '0x00', - { - value: parseEther('0.1'), - }, + const contractBalanceInitial = await ethers.provider.getBalance( + contract.address, ); + const mintFeeReceiverBalanceInitial = + await ethers.provider.getBalance(MINT_FEE_RECEIVER); - await readonlyContract.mint( - 1, - 1, - [ZERO_PROOF], - 0, - '0x00', - { - value: parseEther('0.2'), - }, - ); + await readonlyContract.mint(0, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.1'), + }); - await readonlyContract.mint( - 2, - 1, - [ZERO_PROOF], - 0, - '0x00', - { - value: parseEther('0.3'), - }, - ); + await readonlyContract.mint(1, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.2'), + }); + + await readonlyContract.mint(2, 1, [ZERO_PROOF], 0, '0x00', { + value: parseEther('0.3'), + }); await contract.withdraw(); const [_, walletMintedCount, stagedMintedCount] = await readonlyContract.getStageInfo(0); - expect(walletMintedCount).to.eql([BigNumber.from(1), BigNumber.from(1), BigNumber.from(1)]); - expect(stagedMintedCount).to.eql([BigNumber.from(1), BigNumber.from(1), BigNumber.from(1)]); + expect(walletMintedCount).to.eql([ + BigNumber.from(1), + BigNumber.from(1), + BigNumber.from(1), + ]); + expect(stagedMintedCount).to.eql([ + BigNumber.from(1), + BigNumber.from(1), + BigNumber.from(1), + ]); - const contractBalancePost = await ethers.provider.getBalance(contract.address) + const contractBalancePost = await ethers.provider.getBalance( + contract.address, + ); expect(contractBalancePost.sub(contractBalanceInitial)).to.equal(0); - const mintFeeReceiverBalancePost = await ethers.provider.getBalance(MINT_FEE_RECEIVER) - expect(mintFeeReceiverBalancePost.sub(mintFeeReceiverBalanceInitial)).to.equal(parseEther('0.6')); + const mintFeeReceiverBalancePost = + await ethers.provider.getBalance(MINT_FEE_RECEIVER); + expect( + mintFeeReceiverBalancePost.sub(mintFeeReceiverBalanceInitial), + ).to.equal(parseEther('0.6')); }); it('mint with mint fee waived', async () => { @@ -1316,16 +1315,18 @@ describe('ERC1155M', function () { false, ); - await expect(readonlyContract.mint( - 2, - 1, - [ethers.utils.hexZeroPad('0x', 32)], - timestamp, - sig, - { - value: ethers.utils.parseEther('0'), // price = 0, mintFee = 0.3 - }, - )).to.be.rejectedWith('NotEnoughValue');; + await expect( + readonlyContract.mint( + 2, + 1, + [ethers.utils.hexZeroPad('0x', 32)], + timestamp, + sig, + { + value: ethers.utils.parseEther('0'), // price = 0, mintFee = 0.3 + }, + ), + ).to.be.rejectedWith('NotEnoughValue'); sig = getCosignSignature( contract, @@ -1343,13 +1344,21 @@ describe('ERC1155M', function () { timestamp, sig, { - value: ethers.utils.parseEther('0'), // price = 0, mintFee = 0.3 + value: ethers.utils.parseEther('0'), // price = 0, mintFee = 0.3 }, ); const [stageInfo, walletMintedCount, stagedMintedCount] = await readonlyContract.getStageInfo(0); - expect(walletMintedCount).to.eql([BigNumber.from(0), BigNumber.from(0), BigNumber.from(1)]); - expect(walletMintedCount).to.eql([BigNumber.from(0), BigNumber.from(0), BigNumber.from(1)]); + expect(walletMintedCount).to.eql([ + BigNumber.from(0), + BigNumber.from(0), + BigNumber.from(1), + ]); + expect(walletMintedCount).to.eql([ + BigNumber.from(0), + BigNumber.from(0), + BigNumber.from(1), + ]); }); it('enforces Merkle proof if required', async () => { @@ -1401,7 +1410,11 @@ describe('ERC1155M', function () { }); const totalMinted = await contract.totalMintedByAddress(signerAddress); - expect(totalMinted).to.eql([BigNumber.from(1), BigNumber.from(1), BigNumber.from(1)]); + expect(totalMinted).to.eql([ + BigNumber.from(1), + BigNumber.from(1), + BigNumber.from(1), + ]); // Mint 1 token B with someone's else proof should be reverted await expect( @@ -1455,54 +1468,62 @@ describe('ERC1155M', function () { ]); // Owner mints 1 token B with valid proof - await contract.mintWithLimit(1, 1, 2, ownerProof, 0, '0x00', { + await contract.mint(1, 1, 2, ownerProof, 0, '0x00', { value: parseEther('0.1'), }); - expect(await contract.totalMintedByAddress(owner.getAddress())).to.eql([BigNumber.from(0), BigNumber.from(1), BigNumber.from(0)]); + expect(await contract.totalMintedByAddress(owner.getAddress())).to.eql([ + BigNumber.from(0), + BigNumber.from(1), + BigNumber.from(0), + ]); // Owner mints 1 token B with wrong limit and should be reverted. await expect( - contract.mintWithLimit(1, 1, 3, ownerProof, 0, '0x00', { + contract.mint(1, 1, 3, ownerProof, 0, '0x00', { value: parseEther('0.1'), }), ).to.be.rejectedWith('InvalidProof'); // Owner mints 2 token B with valid proof and reverts. await expect( - contract.mintWithLimit(1, 2, 2, ownerProof, 0, '0x00', { + contract.mint(1, 2, 2, ownerProof, 0, '0x00', { value: parseEther('0.2'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Owner mints 1 token B with valid proof. Now owner reaches the limit. - await contract.mintWithLimit(1, 1, 2, ownerProof, 0, '0x00', { + await contract.mint(1, 1, 2, ownerProof, 0, '0x00', { value: parseEther('0.1'), }); - expect((await contract.totalMintedByAddress(owner.getAddress()))).to.eql([BigNumber.from(0), BigNumber.from(2), BigNumber.from(0)]); + expect(await contract.totalMintedByAddress(owner.getAddress())).to.eql([ + BigNumber.from(0), + BigNumber.from(2), + BigNumber.from(0), + ]); // Owner tries to mint more and reverts. await expect( - contract.mintWithLimit(1, 1, 2, ownerProof, 0, '0x00', { + contract.mint(1, 1, 2, ownerProof, 0, '0x00', { value: parseEther('0.1'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Reader mints 6 token B with valid proof and reverts. await expect( - readonlyContract.mintWithLimit(1, 6, 5, readerProof, 0, '0x00', { + readonlyContract.mint(1, 6, 5, readerProof, 0, '0x00', { value: parseEther('0.6'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Reader mints 5 token B with valid proof. - await readonlyContract.mintWithLimit(1, 5, 5, readerProof, 0, '0x00', { + await readonlyContract.mint(1, 5, 5, readerProof, 0, '0x00', { value: parseEther('0.5'), }); // Reader mints 1 token B with valid proof and reverts. await expect( - readonlyContract.mintWithLimit(1, 1, 5, readerProof, 0, '0x00', { + readonlyContract.mint(1, 1, 5, readerProof, 0, '0x00', { value: parseEther('0.1'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); @@ -1512,10 +1533,19 @@ describe('ERC1155M', function () { const [owner] = await ethers.getSigners(); await contract.ownerMint(owner.address, 1, 5); - const [, walletMintedCount, stageMintedCount] = await contract.getStageInfo(0); + const [, walletMintedCount, stageMintedCount] = + await contract.getStageInfo(0); - expect(walletMintedCount).to.eql([BigNumber.from(0), BigNumber.from(0), BigNumber.from(0)]); - expect(stageMintedCount).to.eql([BigNumber.from(0), BigNumber.from(0), BigNumber.from(0)]); + expect(walletMintedCount).to.eql([ + BigNumber.from(0), + BigNumber.from(0), + BigNumber.from(0), + ]); + expect(stageMintedCount).to.eql([ + BigNumber.from(0), + BigNumber.from(0), + BigNumber.from(0), + ]); expect(await contract.balanceOf(owner.address, 0)).to.equal(0); expect(await contract.balanceOf(owner.address, 1)).to.equal(5); diff --git a/test/erc721m/ERC721CM.test.ts b/test/erc721m/ERC721CM.test.ts index 6f56ff89..507cc6a0 100644 --- a/test/erc721m/ERC721CM.test.ts +++ b/test/erc721m/ERC721CM.test.ts @@ -1297,7 +1297,7 @@ describe('ERC721CM', function () { // Setup the test context: Update block.timestamp to comply to the stage being active await ethers.provider.send('evm_mine', [stageStart - 1]); // Owner mints 1 token with valid proof - await contract.mintWithLimit(1, 2, ownerProof, 0, '0x00', { + await contract.mint(1, 2, ownerProof, 0, '0x00', { value: ethers.utils.parseEther('0.1'), }); expect( @@ -1306,20 +1306,20 @@ describe('ERC721CM', function () { // Owner mints 1 token with wrong limit and should be reverted. await expect( - contract.mintWithLimit(1, 3, ownerProof, 0, '0x00', { + contract.mint(1, 3, ownerProof, 0, '0x00', { value: ethers.utils.parseEther('0.1'), }), ).to.be.rejectedWith('InvalidProof'); // Owner mints 2 tokens with valid proof and reverts. await expect( - contract.mintWithLimit(2, 2, ownerProof, 0, '0x00', { + contract.mint(2, 2, ownerProof, 0, '0x00', { value: ethers.utils.parseEther('0.2'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Owner mints 1 token with valid proof. Now owner reaches the limit. - await contract.mintWithLimit(1, 2, ownerProof, 0, '0x00', { + await contract.mint(1, 2, ownerProof, 0, '0x00', { value: ethers.utils.parseEther('0.1'), }); expect( @@ -1328,26 +1328,26 @@ describe('ERC721CM', function () { // Owner tries to mint more and reverts. await expect( - contract.mintWithLimit(1, 2, ownerProof, 0, '0x00', { + contract.mint(1, 2, ownerProof, 0, '0x00', { value: ethers.utils.parseEther('0.1'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Reader mints 6 tokens with valid proof and reverts. await expect( - readonlyContract.mintWithLimit(6, 5, readerProof, 0, '0x00', { + readonlyContract.mint(6, 5, readerProof, 0, '0x00', { value: ethers.utils.parseEther('0.6'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Reader mints 5 tokens with valid proof. - await readonlyContract.mintWithLimit(5, 5, readerProof, 0, '0x00', { + await readonlyContract.mint(5, 5, readerProof, 0, '0x00', { value: ethers.utils.parseEther('0.5'), }); // Reader mints 1 token with valid proof and reverts. await expect( - readonlyContract.mintWithLimit(1, 5, readerProof, 0, '0x00', { + readonlyContract.mint(1, 5, readerProof, 0, '0x00', { value: ethers.utils.parseEther('0.1'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); diff --git a/test/erc721m/ERC721M.test.ts b/test/erc721m/ERC721M.test.ts index 72f4509f..7f768f9d 100644 --- a/test/erc721m/ERC721M.test.ts +++ b/test/erc721m/ERC721M.test.ts @@ -1296,7 +1296,7 @@ describe('ERC721M', function () { await ethers.provider.send('evm_mine', [stageStart - 1]); // Owner mints 1 token with valid proof - await contract.mintWithLimit(1, 2, ownerProof, 0, '0x00', { + await contract.mint(1, 2, ownerProof, 0, '0x00', { value: ethers.utils.parseEther('0.1'), }); expect( @@ -1305,20 +1305,20 @@ describe('ERC721M', function () { // Owner mints 1 token with wrong limit and should be reverted. await expect( - contract.mintWithLimit(1, 3, ownerProof, 0, '0x00', { + contract.mint(1, 3, ownerProof, 0, '0x00', { value: ethers.utils.parseEther('0.1'), }), ).to.be.rejectedWith('InvalidProof'); // Owner mints 2 tokens with valid proof and reverts. await expect( - contract.mintWithLimit(2, 2, ownerProof, 0, '0x00', { + contract.mint(2, 2, ownerProof, 0, '0x00', { value: ethers.utils.parseEther('0.2'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Owner mints 1 token with valid proof. Now owner reaches the limit. - await contract.mintWithLimit(1, 2, ownerProof, 0, '0x00', { + await contract.mint(1, 2, ownerProof, 0, '0x00', { value: ethers.utils.parseEther('0.1'), }); expect( @@ -1327,26 +1327,26 @@ describe('ERC721M', function () { // Owner tries to mint more and reverts. await expect( - contract.mintWithLimit(1, 2, ownerProof, 0, '0x00', { + contract.mint(1, 2, ownerProof, 0, '0x00', { value: ethers.utils.parseEther('0.1'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Reader mints 6 tokens with valid proof and reverts. await expect( - readonlyContract.mintWithLimit(6, 5, readerProof, 0, '0x00', { + readonlyContract.mint(6, 5, readerProof, 0, '0x00', { value: ethers.utils.parseEther('0.6'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); // Reader mints 5 tokens with valid proof. - await readonlyContract.mintWithLimit(5, 5, readerProof, 0, '0x00', { + await readonlyContract.mint(5, 5, readerProof, 0, '0x00', { value: ethers.utils.parseEther('0.5'), }); // Reader mints 1 token with valid proof and reverts. await expect( - readonlyContract.mintWithLimit(1, 5, readerProof, 0, '0x00', { + readonlyContract.mint(1, 5, readerProof, 0, '0x00', { value: ethers.utils.parseEther('0.1'), }), ).to.be.rejectedWith('WalletStageLimitExceeded'); From 3fd8ca0eaa23476cce02c93c28e008d304c6c3ca Mon Sep 17 00:00:00 2001 From: Wolfy Date: Sun, 6 Oct 2024 17:11:30 -0400 Subject: [PATCH 048/145] update mint funcs Signed-off-by: Wolfy --- hardhat.config.ts | 28 --------- scripts/deploy/deployClone.ts | 82 ------------------------- scripts/deploy/deployCloneFactory.ts | 60 ------------------ scripts/index.ts | 1 - scripts/mint/mint.ts | 2 +- test/erc1155m/ERC1155M.test.ts | 92 +++++++++++++++------------- test/erc721m/ERC721CM.test.ts | 73 +++++++++++----------- test/erc721m/ERC721M.test.ts | 57 +++++++++++------ test/erc721m/mintCurrency.test.ts | 12 ++-- 9 files changed, 133 insertions(+), 274 deletions(-) delete mode 100644 scripts/deploy/deployClone.ts delete mode 100644 scripts/deploy/deployCloneFactory.ts diff --git a/hardhat.config.ts b/hardhat.config.ts index 07e5044c..70459e7c 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -14,7 +14,6 @@ import { setMintable, deploy, deploy1155, - deployClone, setBaseURI, setCrossmintAddress, mint, @@ -44,7 +43,6 @@ import { cleanWhitelist, ownerMint1155, } from './scripts'; -import { deployCloneFactory } from './scripts/deploy/deployCloneFactory'; const config: HardhatUserConfig = { solidity: { @@ -468,30 +466,4 @@ task('cleanWhitelist', 'Clean up whitelist') .addOptionalParam('variablewalletlimitpath', 'variable wallet limit whitelist path') .setAction(cleanWhitelist) -task('deployCloneFactory', 'Deploy 721CMRoyalties clone factory') - .addOptionalParam('gaspricegwei', 'Set gas price in Gwei') - .addOptionalParam('gaslimit', 'Set maximum gas units to spend on transaction') - .setAction(deployCloneFactory) - -task('deployClone', 'Create 721CMRoyalties cline') - .addParam('name', 'name') - .addParam('symbol', 'symbol') - .addParam('maxsupply', 'max supply') - .addParam('tokenurisuffix', 'token uri suffix', '.json') - .addParam('globalwalletlimit', 'global wallet limit', '0') - .addParam('timestampexpiryseconds', 'timestamp expiry in seconds', '300') - .addParam('mintcurrency','ERC-20 contract address. 0x0 if using native token','0x0000000000000000000000000000000000000000') - .addParam('fundreceiver', 'The treasury wallet to receive mint fund') - .addParam('royaltyreceiver', 'erc2198 royalty receiver address') - .addParam('royaltyfeenumerator', 'erc2198 royalty fee numerator') - .addParam( - 'openedition', - 'whether or not a open edition mint (unlimited supply, 999,999,999)', - false, - types.boolean, - ) - .addOptionalParam('gaspricegwei', 'Set gas price in Gwei') - .addOptionalParam('gaslimit', 'Set maximum gas units to spend on transaction') - .setAction(deployClone); - export default config; diff --git a/scripts/deploy/deployClone.ts b/scripts/deploy/deployClone.ts deleted file mode 100644 index 0bff8910..00000000 --- a/scripts/deploy/deployClone.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { confirm } from '@inquirer/prompts'; -import { HardhatRuntimeEnvironment } from 'hardhat/types'; -import { - ContractDetails, - ERC721CMRoyaltiesCloneFactoryContract, -} from '../common/constants'; -import { estimateGas } from '../utils/helper'; -import { Overrides } from 'ethers'; - -export interface IDeployCloneParams { - name: string; - symbol: string; - tokenurisuffix: string; - maxsupply: string; - globalwalletlimit: string; - timestampexpiryseconds: number; - mintcurrency: string; - fundreceiver: string; - royaltyreceiver: string; - royaltyfeenumerator: number; - openedition: boolean; - gaspricegwei?: number; - gaslimit?: number; -} - -export const deployClone = async ( - args: IDeployCloneParams, - hre: HardhatRuntimeEnvironment, -) => { - const { ethers } = hre; - const factory = await ethers.getContractFactory( - ContractDetails.ERC721CMRoyaltiesCloneFactory.name, - ); - const factoryContract = factory.attach(ERC721CMRoyaltiesCloneFactoryContract); - - if (args.openedition) { - args.maxsupply = '999999999'; - } - - const overrides: Overrides = {}; - if (args.gaspricegwei) { - overrides.gasPrice = ethers.BigNumber.from(args.gaspricegwei * 1e9); - } - if (args.gaslimit) { - overrides.gasLimit = ethers.BigNumber.from(args.gaslimit); - } - - const tx = await factoryContract.populateTransaction.create( - args.name, - args.symbol, - args.tokenurisuffix, - args.maxsupply, - args.globalwalletlimit, - args.timestampexpiryseconds, - args.mintcurrency, - args.fundreceiver, - args.royaltyreceiver, - args.royaltyfeenumerator, - ); - - if (!(await estimateGas(hre, tx, overrides))) return; - console.log(`Going to create a clone.`); - if (!(await confirm({ message: 'Continue?' }))) return; - - const signedTx = await factoryContract.create( - args.name, - args.symbol, - args.tokenurisuffix, - args.maxsupply, - args.globalwalletlimit, - args.timestampexpiryseconds, - args.mintcurrency, - args.fundreceiver, - args.royaltyreceiver, - args.royaltyfeenumerator, - overrides, - ); - - console.log(`Submitted tx ${signedTx.hash}`); - const receipt = await signedTx.wait(); - console.log(`Clone deployed at ${receipt.logs[0].address}`); -}; diff --git a/scripts/deploy/deployCloneFactory.ts b/scripts/deploy/deployCloneFactory.ts deleted file mode 100644 index e225e741..00000000 --- a/scripts/deploy/deployCloneFactory.ts +++ /dev/null @@ -1,60 +0,0 @@ -// We require the Hardhat Runtime Environment explicitly here. This is optional -// but useful for running the script in a standalone fashion through `node