From 581b8c71b836746c5647af6854c36be9a3af4c1e Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Mon, 1 May 2023 07:49:17 +1200 Subject: [PATCH 01/49] Prepare repo --- .eslintrc.js | 23 + .gitignore | 11 + .gitmodules | 3 + .prettierrc.js | 6 + .vscode/settings.json | 8 + LICENSE | 219 ++++++++ cache/solidity-files-cache.json | 930 ++++++++++++++++++++++++++++++++ foundry.toml | 5 + lib/forge-std | 1 + package.json | 27 + remappings.txt | 4 + script/Counter.s.sol | 12 + src/Counter.sol | 14 + test/Counter.t.sol | 23 + yarn.lock | 487 +++++++++++++++++ 15 files changed, 1773 insertions(+) create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .prettierrc.js create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 cache/solidity-files-cache.json create mode 100644 foundry.toml create mode 160000 lib/forge-std create mode 100644 package.json create mode 100644 remappings.txt create mode 100644 script/Counter.s.sol create mode 100644 src/Counter.sol create mode 100644 test/Counter.t.sol create mode 100644 yarn.lock diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..6ce781d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,23 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: true, + }, + + extends: [ + 'eslint:recommended', + 'plugin:import/recommended', + 'plugin:import/typescript', + 'plugin:@typescript-eslint/recommended', + 'prettier', + ], + plugins: ['@typescript-eslint', 'simple-import-sort'], + + rules: { + '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/consistent-type-imports': 'error', + }, + + ignorePatterns: ['dist', 'node_modules'], +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6d6fa4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +out/ + +# Ignore .DS_Store files on macOS +.DS_Store + +# Yarn +yarn-error.log + +# Env vars +.env diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..888d42d --- /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/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..9050f2a --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,6 @@ +module.exports = { + arrowParens: 'avoid', + semi: false, + singleQuote: true, + trailingComma: 'all', +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9f270f1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.formatOnSave": true, + "[solidity]": { + "editor.defaultFormatter": "JuanBlanco.solidity" + }, + "solidity.formatter": "forge", + "solidity.compileUsingRemoteVersion": "v0.8.17" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..df07b30 --- /dev/null +++ b/LICENSE @@ -0,0 +1,219 @@ + Copyright (c) 2023-present Horizon Blockchain Games Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + ------------------------------------------------------------------------ + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/cache/solidity-files-cache.json b/cache/solidity-files-cache.json new file mode 100644 index 0000000..aea6507 --- /dev/null +++ b/cache/solidity-files-cache.json @@ -0,0 +1,930 @@ +{ + "_format": "ethers-rs-sol-cache-3", + "paths": { + "artifacts": "out", + "build_infos": "out/build-info", + "sources": "src", + "tests": "test", + "scripts": "script", + "libraries": [ + "lib" + ] + }, + "files": { + "lib/forge-std/lib/ds-test/src/test.sol": { + "lastModificationDate": 1682560435533, + "contentHash": "2df678a5b2611e0d7bc9ef6745d19157", + "sourceName": "lib/forge-std/lib/ds-test/src/test.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [], + "versionRequirement": ">=0.5.0", + "artifacts": { + "DSTest": { + "0.8.17+commit.8df45f5f.Windows.msvc": "test.sol\\DSTest.json" + } + } + }, + "lib/forge-std/src/Base.sol": { + "lastModificationDate": 1682560430784, + "contentHash": "2a76a316dab98751b739247982ac8576", + "sourceName": "lib/forge-std/src/Base.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/src/StdStorage.sol", + "lib/forge-std/src/Vm.sol" + ], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "CommonBase": { + "0.8.17+commit.8df45f5f.Windows.msvc": "Base.sol\\CommonBase.json" + }, + "ScriptBase": { + "0.8.17+commit.8df45f5f.Windows.msvc": "Base.sol\\ScriptBase.json" + }, + "TestBase": { + "0.8.17+commit.8df45f5f.Windows.msvc": "Base.sol\\TestBase.json" + } + } + }, + "lib/forge-std/src/Script.sol": { + "lastModificationDate": 1682560430785, + "contentHash": "c10ed2980061827d4a5ce78c310ae01b", + "sourceName": "lib/forge-std/src/Script.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/src/Base.sol", + "lib/forge-std/src/StdChains.sol", + "lib/forge-std/src/StdCheats.sol", + "lib/forge-std/src/StdJson.sol", + "lib/forge-std/src/StdMath.sol", + "lib/forge-std/src/StdStorage.sol", + "lib/forge-std/src/StdUtils.sol", + "lib/forge-std/src/Vm.sol", + "lib/forge-std/src/console.sol", + "lib/forge-std/src/console2.sol", + "lib/forge-std/src/interfaces/IMulticall3.sol" + ], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "Script": { + "0.8.17+commit.8df45f5f.Windows.msvc": "Script.sol\\Script.json" + } + } + }, + "lib/forge-std/src/StdAssertions.sol": { + "lastModificationDate": 1682560430787, + "contentHash": "d136ed3be15b16e8875196a38f225c2f", + "sourceName": "lib/forge-std/src/StdAssertions.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/lib/ds-test/src\\test.sol", + "lib/forge-std/src/StdMath.sol" + ], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "StdAssertions": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdAssertions.sol\\StdAssertions.json" + } + } + }, + "lib/forge-std/src/StdChains.sol": { + "lastModificationDate": 1682560430787, + "contentHash": "782a4e3904d3ec80179253920e1f3098", + "sourceName": "lib/forge-std/src/StdChains.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/src/Vm.sol" + ], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "StdChains": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdChains.sol\\StdChains.json" + } + } + }, + "lib/forge-std/src/StdCheats.sol": { + "lastModificationDate": 1682560430788, + "contentHash": "27f7037d3ce568cac654c20032ed71e3", + "sourceName": "lib/forge-std/src/StdCheats.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/src/StdStorage.sol", + "lib/forge-std/src/Vm.sol" + ], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "StdCheats": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdCheats.sol\\StdCheats.json" + }, + "StdCheatsSafe": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdCheats.sol\\StdCheatsSafe.json" + } + } + }, + "lib/forge-std/src/StdError.sol": { + "lastModificationDate": 1682560430788, + "contentHash": "8c35ad419c5b8748575080db5fd58cae", + "sourceName": "lib/forge-std/src/StdError.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "stdError": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdError.sol\\stdError.json" + } + } + }, + "lib/forge-std/src/StdInvariant.sol": { + "lastModificationDate": 1682560430788, + "contentHash": "3525c75dbb49f9af908a943d9ea635cf", + "sourceName": "lib/forge-std/src/StdInvariant.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "StdInvariant": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdInvariant.sol\\StdInvariant.json" + } + } + }, + "lib/forge-std/src/StdJson.sol": { + "lastModificationDate": 1682560430788, + "contentHash": "71c2c9083583f2adbe0d07a725689def", + "sourceName": "lib/forge-std/src/StdJson.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/src/Vm.sol" + ], + "versionRequirement": ">=0.6.0, <0.9.0", + "artifacts": { + "stdJson": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdJson.sol\\stdJson.json" + } + } + }, + "lib/forge-std/src/StdMath.sol": { + "lastModificationDate": 1682560430788, + "contentHash": "fc64b149e462e64dea89f50e9cc0318f", + "sourceName": "lib/forge-std/src/StdMath.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "stdMath": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdMath.sol\\stdMath.json" + } + } + }, + "lib/forge-std/src/StdStorage.sol": { + "lastModificationDate": 1682560430788, + "contentHash": "956a40f1cecbf085f687f818356dc22a", + "sourceName": "lib/forge-std/src/StdStorage.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/src/Vm.sol" + ], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "stdStorage": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdStorage.sol\\stdStorage.json" + }, + "stdStorageSafe": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdStorage.sol\\stdStorageSafe.json" + } + } + }, + "lib/forge-std/src/StdStyle.sol": { + "lastModificationDate": 1682560430793, + "contentHash": "bc32c9e9afd157bb02836b279d2223c5", + "sourceName": "lib/forge-std/src/StdStyle.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/src/Vm.sol" + ], + "versionRequirement": ">=0.4.22, <0.9.0", + "artifacts": { + "StdStyle": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdStyle.sol\\StdStyle.json" + } + } + }, + "lib/forge-std/src/StdUtils.sol": { + "lastModificationDate": 1682560430794, + "contentHash": "2b2cfd9859c68fa78b9e2aff05a80d87", + "sourceName": "lib/forge-std/src/StdUtils.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/src/Vm.sol", + "lib/forge-std/src/interfaces/IMulticall3.sol" + ], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "StdUtils": { + "0.8.17+commit.8df45f5f.Windows.msvc": "StdUtils.sol\\StdUtils.json" + } + } + }, + "lib/forge-std/src/Test.sol": { + "lastModificationDate": 1682560430795, + "contentHash": "53342ae9785fc6ae53971b57c6ab41d3", + "sourceName": "lib/forge-std/src/Test.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/lib/ds-test/src\\test.sol", + "lib/forge-std/src/Base.sol", + "lib/forge-std/src/StdAssertions.sol", + "lib/forge-std/src/StdChains.sol", + "lib/forge-std/src/StdCheats.sol", + "lib/forge-std/src/StdError.sol", + "lib/forge-std/src/StdInvariant.sol", + "lib/forge-std/src/StdJson.sol", + "lib/forge-std/src/StdMath.sol", + "lib/forge-std/src/StdStorage.sol", + "lib/forge-std/src/StdStyle.sol", + "lib/forge-std/src/StdUtils.sol", + "lib/forge-std/src/Vm.sol", + "lib/forge-std/src/console.sol", + "lib/forge-std/src/console2.sol", + "lib/forge-std/src/interfaces/IMulticall3.sol" + ], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "Test": { + "0.8.17+commit.8df45f5f.Windows.msvc": "Test.sol\\Test.json" + } + } + }, + "lib/forge-std/src/Vm.sol": { + "lastModificationDate": 1682560430798, + "contentHash": "0e488a43bb880f32d1ce6f1b86f24359", + "sourceName": "lib/forge-std/src/Vm.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "Vm": { + "0.8.17+commit.8df45f5f.Windows.msvc": "Vm.sol\\Vm.json" + }, + "VmSafe": { + "0.8.17+commit.8df45f5f.Windows.msvc": "Vm.sol\\VmSafe.json" + } + } + }, + "lib/forge-std/src/console.sol": { + "lastModificationDate": 1682560430799, + "contentHash": "c8cf989d04bc2e3069e3d91facf3b69c", + "sourceName": "lib/forge-std/src/console.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [], + "versionRequirement": ">=0.4.22, <0.9.0", + "artifacts": { + "console": { + "0.8.17+commit.8df45f5f.Windows.msvc": "console.sol\\console.json" + } + } + }, + "lib/forge-std/src/console2.sol": { + "lastModificationDate": 1682560430799, + "contentHash": "f0634a1969c1d0ebfc2ea47dafeafb02", + "sourceName": "lib/forge-std/src/console2.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [], + "versionRequirement": ">=0.4.22, <0.9.0", + "artifacts": { + "console2": { + "0.8.17+commit.8df45f5f.Windows.msvc": "console2.sol\\console2.json" + } + } + }, + "lib/forge-std/src/interfaces/IMulticall3.sol": { + "lastModificationDate": 1682560430807, + "contentHash": "5de707a0d82c8f56049fbd3ba28944ed", + "sourceName": "lib/forge-std/src/interfaces/IMulticall3.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [], + "versionRequirement": ">=0.6.2, <0.9.0", + "artifacts": { + "IMulticall3": { + "0.8.17+commit.8df45f5f.Windows.msvc": "IMulticall3.sol\\IMulticall3.json" + } + } + }, + "script/Counter.s.sol": { + "lastModificationDate": 1682560427326, + "contentHash": "0705c52104730a78aef4aa6694175c81", + "sourceName": "script/Counter.s.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/src/Base.sol", + "lib/forge-std/src\\Script.sol", + "lib/forge-std/src/StdChains.sol", + "lib/forge-std/src/StdCheats.sol", + "lib/forge-std/src/StdJson.sol", + "lib/forge-std/src/StdMath.sol", + "lib/forge-std/src/StdStorage.sol", + "lib/forge-std/src/StdUtils.sol", + "lib/forge-std/src/Vm.sol", + "lib/forge-std/src/console.sol", + "lib/forge-std/src/console2.sol", + "lib/forge-std/src/interfaces/IMulticall3.sol" + ], + "versionRequirement": "^0.8.13", + "artifacts": { + "CounterScript": { + "0.8.17+commit.8df45f5f.Windows.msvc": "Counter.s.sol\\CounterScript.json" + } + } + }, + "src/Counter.sol": { + "lastModificationDate": 1682560427326, + "contentHash": "ae6c800a2b4c57768024d6e9423d39e8", + "sourceName": "src/Counter.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [], + "versionRequirement": "^0.8.13", + "artifacts": { + "Counter": { + "0.8.17+commit.8df45f5f.Windows.msvc": "Counter.sol\\Counter.json" + } + } + }, + "test/Counter.t.sol": { + "lastModificationDate": 1682560427326, + "contentHash": "5122f4f87ee8fbf9a2468a4c9c780b6a", + "sourceName": "test/Counter.t.sol", + "solcConfig": { + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "evmVersion": "london", + "libraries": {} + } + }, + "imports": [ + "lib/forge-std/lib/ds-test/src\\test.sol", + "lib/forge-std/src/Base.sol", + "lib/forge-std/src/StdAssertions.sol", + "lib/forge-std/src/StdChains.sol", + "lib/forge-std/src/StdCheats.sol", + "lib/forge-std/src/StdError.sol", + "lib/forge-std/src/StdInvariant.sol", + "lib/forge-std/src/StdJson.sol", + "lib/forge-std/src/StdMath.sol", + "lib/forge-std/src/StdStorage.sol", + "lib/forge-std/src/StdStyle.sol", + "lib/forge-std/src/StdUtils.sol", + "lib/forge-std/src\\Test.sol", + "lib/forge-std/src/Vm.sol", + "lib/forge-std/src/console.sol", + "lib/forge-std/src/console2.sol", + "lib/forge-std/src/interfaces/IMulticall3.sol", + "src\\Counter.sol" + ], + "versionRequirement": "^0.8.13", + "artifacts": { + "CounterTest": { + "0.8.17+commit.8df45f5f.Windows.msvc": "Counter.t.sol\\CounterTest.json" + } + } + } + } +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..dcfcb1d --- /dev/null +++ b/foundry.toml @@ -0,0 +1,5 @@ +[profile.default] +src = 'src' +out = 'out' +libs = ['lib'] +solc = "0.8.17" diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..73d44ec --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 73d44ec7d124e3831bc5f832267889ffb6f9bc3f diff --git a/package.json b/package.json new file mode 100644 index 0000000..62e46fe --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "@0xsequence/contracts-library", + "version": "1.0.0", + "description": "Solidity Contract Library for 0xSequence", + "repository": "https://github.com/0xsequence/contract-library.git", + "bugs": { + "url": "https://github.com/0xsequence/contract-library/issues" + }, + "homepage": "https://github.com/0xsequence/contract-library#README.md", + "source": "src/index.ts", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "author": "Horizon Blockchain Games", + "license": "Apache-2.0", + "scripts": { + "build": "forge build" + }, + "files": [ + "src", + "dist" + ], + "dependencies": { + "@0xsequence/erc-1155": "^4.0.1", + "@0xsequence/erc20-meta-token": "^4.0.1", + "erc721a": "^4.2.3" + } +} diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..17deda0 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,4 @@ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ +@0xsequence/erc20-meta-token/=node_modules/@0xsequence/erc20-meta-token/ +@0xsequence/erc-1155/=node_modules/@0xsequence/erc-1155/ diff --git a/script/Counter.s.sol b/script/Counter.s.sol new file mode 100644 index 0000000..0e546ab --- /dev/null +++ b/script/Counter.s.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; + +contract CounterScript is Script { + function setUp() public {} + + function run() public { + vm.broadcast(); + } +} diff --git a/src/Counter.sol b/src/Counter.sol new file mode 100644 index 0000000..aded799 --- /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 0000000..fc5accf --- /dev/null +++ b/test/Counter.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function testIncrement() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testSetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..334f784 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,487 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@0xsequence/erc-1155@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@0xsequence/erc-1155/-/erc-1155-4.0.1.tgz#c991fe06b6ae385146e69cd9d1c4982b06487c10" + integrity sha512-KFLxBfiocOuHmPUkGYiWw5fLZ8uCDhXhcyzFFv8oe/KWXdxL37NTD7n6CmMSRiUxr4qaXuFV5u38vPFWFdOY0g== + optionalDependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + ethers "^5.7.2" + +"@0xsequence/erc20-meta-token@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@0xsequence/erc20-meta-token/-/erc20-meta-token-4.0.1.tgz#44fe77822af0ff3b1111466615c9958e92aca782" + integrity sha512-q3yIR5OwsTK+HnTQVXTDlknMo2kE65rBwZN6ymPeoe1CW+RE9XqUM3QCvxTlLGuAnsIaL6/ABG2ePX4crMvbaw== + dependencies: + "@0xsequence/erc-1155" "^4.0.1" + +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + +"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + +"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + +"@ethersproject/contracts@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + +"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.2": + version "5.7.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" + integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + hash.js "1.1.7" + +"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/solidity@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + +"@ethersproject/units@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/wallet@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +aes-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" + integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== + +bech32@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +elliptic@6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +erc721a@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/erc721a/-/erc721a-4.2.3.tgz#ca6469b0e54afb0f614272c2147dc4cb49ff223f" + integrity sha512-0deF0hOOK1XI1Vxv3NKDh2E9sgzRlENuOoexjXRJIRfYCsLlqi9ejl2RF6Wcd9HfH0ldqC03wleQ2WDjxoOUvA== + +ethers@^5.7.2: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.1" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.2" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" + +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +scrypt-js@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" + integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== + +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== From 7207fe77f97aa83b4c556f24d194d44398c16723 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Mon, 1 May 2023 08:06:24 +1200 Subject: [PATCH 02/49] Add linting --- .gitignore | 4 +- .husky/pre-commit | 4 + cache/solidity-files-cache.json | 930 -------------------------------- package.json | 11 +- test/Counter.t.sol | 5 +- yarn.lock | 493 +++++++++++++++++ 6 files changed, 513 insertions(+), 934 deletions(-) create mode 100644 .husky/pre-commit delete mode 100644 cache/solidity-files-cache.json diff --git a/.gitignore b/.gitignore index f6d6fa4..646835d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,12 @@ -node_modules/ +# Forge out/ +cache/ # Ignore .DS_Store files on macOS .DS_Store # Yarn +node_modules/ yarn-error.log # Env vars diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..5a182ef --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn lint-staged diff --git a/cache/solidity-files-cache.json b/cache/solidity-files-cache.json deleted file mode 100644 index aea6507..0000000 --- a/cache/solidity-files-cache.json +++ /dev/null @@ -1,930 +0,0 @@ -{ - "_format": "ethers-rs-sol-cache-3", - "paths": { - "artifacts": "out", - "build_infos": "out/build-info", - "sources": "src", - "tests": "test", - "scripts": "script", - "libraries": [ - "lib" - ] - }, - "files": { - "lib/forge-std/lib/ds-test/src/test.sol": { - "lastModificationDate": 1682560435533, - "contentHash": "2df678a5b2611e0d7bc9ef6745d19157", - "sourceName": "lib/forge-std/lib/ds-test/src/test.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [], - "versionRequirement": ">=0.5.0", - "artifacts": { - "DSTest": { - "0.8.17+commit.8df45f5f.Windows.msvc": "test.sol\\DSTest.json" - } - } - }, - "lib/forge-std/src/Base.sol": { - "lastModificationDate": 1682560430784, - "contentHash": "2a76a316dab98751b739247982ac8576", - "sourceName": "lib/forge-std/src/Base.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/src/StdStorage.sol", - "lib/forge-std/src/Vm.sol" - ], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "CommonBase": { - "0.8.17+commit.8df45f5f.Windows.msvc": "Base.sol\\CommonBase.json" - }, - "ScriptBase": { - "0.8.17+commit.8df45f5f.Windows.msvc": "Base.sol\\ScriptBase.json" - }, - "TestBase": { - "0.8.17+commit.8df45f5f.Windows.msvc": "Base.sol\\TestBase.json" - } - } - }, - "lib/forge-std/src/Script.sol": { - "lastModificationDate": 1682560430785, - "contentHash": "c10ed2980061827d4a5ce78c310ae01b", - "sourceName": "lib/forge-std/src/Script.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/src/Base.sol", - "lib/forge-std/src/StdChains.sol", - "lib/forge-std/src/StdCheats.sol", - "lib/forge-std/src/StdJson.sol", - "lib/forge-std/src/StdMath.sol", - "lib/forge-std/src/StdStorage.sol", - "lib/forge-std/src/StdUtils.sol", - "lib/forge-std/src/Vm.sol", - "lib/forge-std/src/console.sol", - "lib/forge-std/src/console2.sol", - "lib/forge-std/src/interfaces/IMulticall3.sol" - ], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "Script": { - "0.8.17+commit.8df45f5f.Windows.msvc": "Script.sol\\Script.json" - } - } - }, - "lib/forge-std/src/StdAssertions.sol": { - "lastModificationDate": 1682560430787, - "contentHash": "d136ed3be15b16e8875196a38f225c2f", - "sourceName": "lib/forge-std/src/StdAssertions.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/lib/ds-test/src\\test.sol", - "lib/forge-std/src/StdMath.sol" - ], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "StdAssertions": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdAssertions.sol\\StdAssertions.json" - } - } - }, - "lib/forge-std/src/StdChains.sol": { - "lastModificationDate": 1682560430787, - "contentHash": "782a4e3904d3ec80179253920e1f3098", - "sourceName": "lib/forge-std/src/StdChains.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/src/Vm.sol" - ], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "StdChains": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdChains.sol\\StdChains.json" - } - } - }, - "lib/forge-std/src/StdCheats.sol": { - "lastModificationDate": 1682560430788, - "contentHash": "27f7037d3ce568cac654c20032ed71e3", - "sourceName": "lib/forge-std/src/StdCheats.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/src/StdStorage.sol", - "lib/forge-std/src/Vm.sol" - ], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "StdCheats": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdCheats.sol\\StdCheats.json" - }, - "StdCheatsSafe": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdCheats.sol\\StdCheatsSafe.json" - } - } - }, - "lib/forge-std/src/StdError.sol": { - "lastModificationDate": 1682560430788, - "contentHash": "8c35ad419c5b8748575080db5fd58cae", - "sourceName": "lib/forge-std/src/StdError.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "stdError": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdError.sol\\stdError.json" - } - } - }, - "lib/forge-std/src/StdInvariant.sol": { - "lastModificationDate": 1682560430788, - "contentHash": "3525c75dbb49f9af908a943d9ea635cf", - "sourceName": "lib/forge-std/src/StdInvariant.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "StdInvariant": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdInvariant.sol\\StdInvariant.json" - } - } - }, - "lib/forge-std/src/StdJson.sol": { - "lastModificationDate": 1682560430788, - "contentHash": "71c2c9083583f2adbe0d07a725689def", - "sourceName": "lib/forge-std/src/StdJson.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/src/Vm.sol" - ], - "versionRequirement": ">=0.6.0, <0.9.0", - "artifacts": { - "stdJson": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdJson.sol\\stdJson.json" - } - } - }, - "lib/forge-std/src/StdMath.sol": { - "lastModificationDate": 1682560430788, - "contentHash": "fc64b149e462e64dea89f50e9cc0318f", - "sourceName": "lib/forge-std/src/StdMath.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "stdMath": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdMath.sol\\stdMath.json" - } - } - }, - "lib/forge-std/src/StdStorage.sol": { - "lastModificationDate": 1682560430788, - "contentHash": "956a40f1cecbf085f687f818356dc22a", - "sourceName": "lib/forge-std/src/StdStorage.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/src/Vm.sol" - ], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "stdStorage": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdStorage.sol\\stdStorage.json" - }, - "stdStorageSafe": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdStorage.sol\\stdStorageSafe.json" - } - } - }, - "lib/forge-std/src/StdStyle.sol": { - "lastModificationDate": 1682560430793, - "contentHash": "bc32c9e9afd157bb02836b279d2223c5", - "sourceName": "lib/forge-std/src/StdStyle.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/src/Vm.sol" - ], - "versionRequirement": ">=0.4.22, <0.9.0", - "artifacts": { - "StdStyle": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdStyle.sol\\StdStyle.json" - } - } - }, - "lib/forge-std/src/StdUtils.sol": { - "lastModificationDate": 1682560430794, - "contentHash": "2b2cfd9859c68fa78b9e2aff05a80d87", - "sourceName": "lib/forge-std/src/StdUtils.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/src/Vm.sol", - "lib/forge-std/src/interfaces/IMulticall3.sol" - ], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "StdUtils": { - "0.8.17+commit.8df45f5f.Windows.msvc": "StdUtils.sol\\StdUtils.json" - } - } - }, - "lib/forge-std/src/Test.sol": { - "lastModificationDate": 1682560430795, - "contentHash": "53342ae9785fc6ae53971b57c6ab41d3", - "sourceName": "lib/forge-std/src/Test.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/lib/ds-test/src\\test.sol", - "lib/forge-std/src/Base.sol", - "lib/forge-std/src/StdAssertions.sol", - "lib/forge-std/src/StdChains.sol", - "lib/forge-std/src/StdCheats.sol", - "lib/forge-std/src/StdError.sol", - "lib/forge-std/src/StdInvariant.sol", - "lib/forge-std/src/StdJson.sol", - "lib/forge-std/src/StdMath.sol", - "lib/forge-std/src/StdStorage.sol", - "lib/forge-std/src/StdStyle.sol", - "lib/forge-std/src/StdUtils.sol", - "lib/forge-std/src/Vm.sol", - "lib/forge-std/src/console.sol", - "lib/forge-std/src/console2.sol", - "lib/forge-std/src/interfaces/IMulticall3.sol" - ], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "Test": { - "0.8.17+commit.8df45f5f.Windows.msvc": "Test.sol\\Test.json" - } - } - }, - "lib/forge-std/src/Vm.sol": { - "lastModificationDate": 1682560430798, - "contentHash": "0e488a43bb880f32d1ce6f1b86f24359", - "sourceName": "lib/forge-std/src/Vm.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "Vm": { - "0.8.17+commit.8df45f5f.Windows.msvc": "Vm.sol\\Vm.json" - }, - "VmSafe": { - "0.8.17+commit.8df45f5f.Windows.msvc": "Vm.sol\\VmSafe.json" - } - } - }, - "lib/forge-std/src/console.sol": { - "lastModificationDate": 1682560430799, - "contentHash": "c8cf989d04bc2e3069e3d91facf3b69c", - "sourceName": "lib/forge-std/src/console.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [], - "versionRequirement": ">=0.4.22, <0.9.0", - "artifacts": { - "console": { - "0.8.17+commit.8df45f5f.Windows.msvc": "console.sol\\console.json" - } - } - }, - "lib/forge-std/src/console2.sol": { - "lastModificationDate": 1682560430799, - "contentHash": "f0634a1969c1d0ebfc2ea47dafeafb02", - "sourceName": "lib/forge-std/src/console2.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [], - "versionRequirement": ">=0.4.22, <0.9.0", - "artifacts": { - "console2": { - "0.8.17+commit.8df45f5f.Windows.msvc": "console2.sol\\console2.json" - } - } - }, - "lib/forge-std/src/interfaces/IMulticall3.sol": { - "lastModificationDate": 1682560430807, - "contentHash": "5de707a0d82c8f56049fbd3ba28944ed", - "sourceName": "lib/forge-std/src/interfaces/IMulticall3.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [], - "versionRequirement": ">=0.6.2, <0.9.0", - "artifacts": { - "IMulticall3": { - "0.8.17+commit.8df45f5f.Windows.msvc": "IMulticall3.sol\\IMulticall3.json" - } - } - }, - "script/Counter.s.sol": { - "lastModificationDate": 1682560427326, - "contentHash": "0705c52104730a78aef4aa6694175c81", - "sourceName": "script/Counter.s.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/src/Base.sol", - "lib/forge-std/src\\Script.sol", - "lib/forge-std/src/StdChains.sol", - "lib/forge-std/src/StdCheats.sol", - "lib/forge-std/src/StdJson.sol", - "lib/forge-std/src/StdMath.sol", - "lib/forge-std/src/StdStorage.sol", - "lib/forge-std/src/StdUtils.sol", - "lib/forge-std/src/Vm.sol", - "lib/forge-std/src/console.sol", - "lib/forge-std/src/console2.sol", - "lib/forge-std/src/interfaces/IMulticall3.sol" - ], - "versionRequirement": "^0.8.13", - "artifacts": { - "CounterScript": { - "0.8.17+commit.8df45f5f.Windows.msvc": "Counter.s.sol\\CounterScript.json" - } - } - }, - "src/Counter.sol": { - "lastModificationDate": 1682560427326, - "contentHash": "ae6c800a2b4c57768024d6e9423d39e8", - "sourceName": "src/Counter.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [], - "versionRequirement": "^0.8.13", - "artifacts": { - "Counter": { - "0.8.17+commit.8df45f5f.Windows.msvc": "Counter.sol\\Counter.json" - } - } - }, - "test/Counter.t.sol": { - "lastModificationDate": 1682560427326, - "contentHash": "5122f4f87ee8fbf9a2468a4c9c780b6a", - "sourceName": "test/Counter.t.sol", - "solcConfig": { - "settings": { - "optimizer": { - "enabled": true, - "runs": 200 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "outputSelection": { - "*": { - "": [ - "ast" - ], - "*": [ - "abi", - "evm.bytecode", - "evm.deployedBytecode", - "evm.methodIdentifiers", - "metadata" - ] - } - }, - "evmVersion": "london", - "libraries": {} - } - }, - "imports": [ - "lib/forge-std/lib/ds-test/src\\test.sol", - "lib/forge-std/src/Base.sol", - "lib/forge-std/src/StdAssertions.sol", - "lib/forge-std/src/StdChains.sol", - "lib/forge-std/src/StdCheats.sol", - "lib/forge-std/src/StdError.sol", - "lib/forge-std/src/StdInvariant.sol", - "lib/forge-std/src/StdJson.sol", - "lib/forge-std/src/StdMath.sol", - "lib/forge-std/src/StdStorage.sol", - "lib/forge-std/src/StdStyle.sol", - "lib/forge-std/src/StdUtils.sol", - "lib/forge-std/src\\Test.sol", - "lib/forge-std/src/Vm.sol", - "lib/forge-std/src/console.sol", - "lib/forge-std/src/console2.sol", - "lib/forge-std/src/interfaces/IMulticall3.sol", - "src\\Counter.sol" - ], - "versionRequirement": "^0.8.13", - "artifacts": { - "CounterTest": { - "0.8.17+commit.8df45f5f.Windows.msvc": "Counter.t.sol\\CounterTest.json" - } - } - } - } -} \ No newline at end of file diff --git a/package.json b/package.json index 62e46fe..e7f5bc3 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "author": "Horizon Blockchain Games", "license": "Apache-2.0", "scripts": { - "build": "forge build" + "build": "forge build", + "lint:init": "husky install", + "lint:sol": "forge fmt" }, "files": [ "src", @@ -23,5 +25,12 @@ "@0xsequence/erc-1155": "^4.0.1", "@0xsequence/erc20-meta-token": "^4.0.1", "erc721a": "^4.2.3" + }, + "lint-staged": { + "**/*.sol": "yarn lint:sol" + }, + "devDependencies": { + "husky": "^8.0.3", + "lint-staged": "^13.2.2" } } diff --git a/test/Counter.t.sol b/test/Counter.t.sol index fc5accf..30235e8 100644 --- a/test/Counter.t.sol +++ b/test/Counter.t.sol @@ -6,9 +6,10 @@ import "../src/Counter.sol"; contract CounterTest is Test { Counter public counter; + function setUp() public { - counter = new Counter(); - counter.setNumber(0); + counter = new Counter(); + counter.setNumber(0); } function testIncrement() public { diff --git a/yarn.lock b/yarn.lock index 334f784..538974f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -365,6 +365,48 @@ aes-js@3.0.0: resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + bech32@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" @@ -380,11 +422,94 @@ bn.js@^5.2.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +chalk@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" + integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^2.0.19: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + elliptic@6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -398,6 +523,16 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + erc721a@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/erc721a/-/erc721a-4.2.3.tgz#ca6469b0e54afb0f614272c2147dc4cb49ff223f" @@ -439,6 +574,33 @@ ethers@^5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +execa@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-7.1.1.tgz#3eb3c83d239488e7b409d48e8813b76bb55c9c43" + integrity sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -456,16 +618,127 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +human-signals@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" + integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== + +husky@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + js-sha3@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== +lilconfig@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + +lint-staged@^13.2.2: + version "13.2.2" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.2.2.tgz#5e711d3139c234f73402177be2f8dd312e6508ca" + integrity sha512-71gSwXKy649VrSU09s10uAT0rWCcY3aewhMaHyl2N84oBk4Xs9HgxvUp3AYu+bNsK4NrOYYxvSgg7FyGJ+jGcA== + dependencies: + chalk "5.2.0" + cli-truncate "^3.1.0" + commander "^10.0.0" + debug "^4.3.4" + execa "^7.0.0" + lilconfig "2.1.0" + listr2 "^5.0.7" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-inspect "^1.12.3" + pidtree "^0.6.0" + string-argv "^0.3.1" + yaml "^2.2.2" + +listr2@^5.0.7: + version "5.0.8" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-5.0.8.tgz#a9379ffeb4bd83a68931a65fb223a11510d6ba23" + integrity sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.19" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.8.0" + through "^2.3.8" + wrap-ansi "^7.0.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -476,12 +749,232 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + +object-inspect@^1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pidtree@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + +rxjs@^7.8.0: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + scrypt-js@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.2, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + +string-argv@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tslib@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + ws@7.4.6: version "7.4.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + +yaml@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" + integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== From 699e1b5e59a2580dfbdc1c03dac40aff7bcca71c Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Thu, 11 May 2023 07:50:06 +1200 Subject: [PATCH 03/49] Add build script --- foundry.toml | 2 + package.json | 8 ++- scripts/build.ts | 55 +++++++++++++++++++++ scripts/constants.ts | 5 ++ yarn.lock | 114 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 scripts/build.ts create mode 100644 scripts/constants.ts diff --git a/foundry.toml b/foundry.toml index dcfcb1d..c8a7cb1 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,3 +3,5 @@ src = 'src' out = 'out' libs = ['lib'] solc = "0.8.17" +via_ir = true +optimizer-runs = 20_000 diff --git a/package.json b/package.json index e7f5bc3..163f897 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "author": "Horizon Blockchain Games", "license": "Apache-2.0", "scripts": { - "build": "forge build", + "build": "ts-node scripts/build.ts", + "test": "forge test", "lint:init": "husky install", "lint:sol": "forge fmt" }, @@ -30,7 +31,10 @@ "**/*.sol": "yarn lint:sol" }, "devDependencies": { + "@types/node": "^20.1.0", "husky": "^8.0.3", - "lint-staged": "^13.2.2" + "lint-staged": "^13.2.2", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" } } diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..a047eee --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,55 @@ +import { exec as execNonPromise } from 'child_process' +import { copyFile, mkdir, readFile, rmdir, writeFile } from 'fs/promises' +import { join } from 'path' +import util from 'util' +import { BUILD_DIR, DEPLOYABLE_CONTRACT_NAMES } from './constants' +const exec = util.promisify(execNonPromise) + +const main = async () => { + // Clean + await rmdir(BUILD_DIR, { recursive: true }) + + // Build with forge + console.log('Building contracts') + await exec('forge build') + console.log('Contracts built') + + await mkdir(BUILD_DIR, { recursive: true }) + + // Create the compiler input files + for (const solFile of DEPLOYABLE_CONTRACT_NAMES) { + const forgeOutputDir = `out/${solFile}.sol` + const compilerDetails = JSON.parse( + await readFile(join(forgeOutputDir, `${solFile}.metadata.json`), 'utf8'), + ) + + // Replace source urls with file contents + for (const sourceKey of Object.keys(compilerDetails.sources)) { + compilerDetails.sources[sourceKey].contents = await readFile( + join(sourceKey), + 'utf8', + ) + } + + // Write the compiler input file + await writeFile( + join(BUILD_DIR, `${solFile}.input.json`), + JSON.stringify(compilerDetails), + ) + + // Copy the compiler output too + await copyFile( + `${forgeOutputDir}/${solFile}.json`, + `${BUILD_DIR}/${solFile}.json`, + ) + } +} + +main() + .then(() => { + console.log('Done') + }) + .catch(err => { + console.error(err) + process.exit(1) + }) diff --git a/scripts/constants.ts b/scripts/constants.ts new file mode 100644 index 0000000..8f4826b --- /dev/null +++ b/scripts/constants.ts @@ -0,0 +1,5 @@ +export const BUILD_DIR = 'build' +export const DEPLOYABLE_CONTRACT_NAMES = [ + 'ERC1155SaleFactory', + 'ERC721SaleFactory', +] diff --git a/yarn.lock b/yarn.lock index 538974f..2ce08dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,6 +18,13 @@ dependencies: "@0xsequence/erc-1155" "^4.0.1" +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" @@ -360,6 +367,59 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@types/node@^20.1.0": + version "20.1.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.0.tgz#258805edc37c327cf706e64c6957f241ca4c4c20" + integrity sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + aes-js@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" @@ -402,6 +462,11 @@ ansi-styles@^6.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" @@ -489,6 +554,11 @@ commander@^10.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -505,6 +575,11 @@ debug@^4.3.4: dependencies: ms "2.1.2" +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -716,6 +791,11 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -934,6 +1014,25 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +ts-node@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + tslib@^2.1.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" @@ -944,6 +1043,16 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +typescript@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" + integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -978,3 +1087,8 @@ yaml@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== From 098bd645b348d5dfa4c039782e2fe50ba1da8dcd Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 2 May 2023 10:32:18 +1200 Subject: [PATCH 04/49] Base token implementations --- package.json | 6 +- remappings.txt | 3 + script/Counter.s.sol | 12 - src/Counter.sol | 14 - src/proxies/ERC1967/IERC1967.sol | 8 + src/proxies/ERC1967/Proxy.sol | 62 ++++ src/proxies/ERC1967/ProxyDeployer.sol | 72 ++++ src/proxies/ERC1967/ProxyDeployerErrors.sol | 10 + src/tokens/ERC1155/ERC1155Token.sol | 147 ++++++++ src/tokens/ERC1155/ERC1155TokenFactory.sol | 36 ++ src/tokens/ERC1155/IERC1155TokenFactory.sol | 22 ++ .../ERC1155/Packed/ERC1155PackedToken.sol | 153 +++++++++ .../Packed/ERC1155PackedTokenFactory.sol | 36 ++ .../Packed/IERC1155PackedTokenFactory.sol | 22 ++ src/tokens/ERC721/ERC721Token.sol | 154 +++++++++ src/tokens/ERC721/ERC721TokenFactory.sol | 37 ++ src/tokens/ERC721/IERC721TokenFactory.sol | 23 ++ src/utils/StorageSlot.sol | 64 ++++ test/Counter.t.sol | 24 -- test/tokens/ERC1155/ERC1155Token.t.sol | 319 ++++++++++++++++++ .../ERC1155/Packed/ERC1155PackedToken.t.sol | 312 +++++++++++++++++ test/tokens/ERC721/ERC721Token.t.sol | 265 +++++++++++++++ yarn.lock | 23 ++ 23 files changed, 1772 insertions(+), 52 deletions(-) delete mode 100644 script/Counter.s.sol delete mode 100644 src/Counter.sol create mode 100644 src/proxies/ERC1967/IERC1967.sol create mode 100644 src/proxies/ERC1967/Proxy.sol create mode 100644 src/proxies/ERC1967/ProxyDeployer.sol create mode 100644 src/proxies/ERC1967/ProxyDeployerErrors.sol create mode 100644 src/tokens/ERC1155/ERC1155Token.sol create mode 100644 src/tokens/ERC1155/ERC1155TokenFactory.sol create mode 100644 src/tokens/ERC1155/IERC1155TokenFactory.sol create mode 100644 src/tokens/ERC1155/Packed/ERC1155PackedToken.sol create mode 100644 src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol create mode 100644 src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol create mode 100644 src/tokens/ERC721/ERC721Token.sol create mode 100644 src/tokens/ERC721/ERC721TokenFactory.sol create mode 100644 src/tokens/ERC721/IERC721TokenFactory.sol create mode 100644 src/utils/StorageSlot.sol delete mode 100644 test/Counter.t.sol create mode 100644 test/tokens/ERC1155/ERC1155Token.t.sol create mode 100644 test/tokens/ERC1155/Packed/ERC1155PackedToken.t.sol create mode 100644 test/tokens/ERC721/ERC721Token.t.sol diff --git a/package.json b/package.json index 163f897..8506b7d 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,11 @@ "dist" ], "dependencies": { - "@0xsequence/erc-1155": "^4.0.1", + "@0xsequence/erc-1155": "^4.0.3", "@0xsequence/erc20-meta-token": "^4.0.1", - "erc721a": "^4.2.3" + "@openzeppelin/contracts": "^4.8.3", + "erc721a": "^4.2.3", + "erc721a-upgradeable": "^4.2.3" }, "lint-staged": { "**/*.sol": "yarn lint:sol" diff --git a/remappings.txt b/remappings.txt index 17deda0..8504885 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,3 +2,6 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ @0xsequence/erc20-meta-token/=node_modules/@0xsequence/erc20-meta-token/ @0xsequence/erc-1155/=node_modules/@0xsequence/erc-1155/ +erc721a/=node_modules/erc721a/ +erc721a-upgradeable/=node_modules/erc721a-upgradeable/ +@openzeppelin/=node_modules/@openzeppelin/ diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index 0e546ab..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- 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/src/proxies/ERC1967/IERC1967.sol b/src/proxies/ERC1967/IERC1967.sol new file mode 100644 index 0000000..058f89b --- /dev/null +++ b/src/proxies/ERC1967/IERC1967.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC1967 { + event Upgraded(address indexed implementation); + event AdminChanged(address previousAdmin, address newAdmin); + event BeaconUpgraded(address indexed beacon); +} diff --git a/src/proxies/ERC1967/Proxy.sol b/src/proxies/ERC1967/Proxy.sol new file mode 100644 index 0000000..833e49e --- /dev/null +++ b/src/proxies/ERC1967/Proxy.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {IERC1967} from "./IERC1967.sol"; +import {StorageSlot} from "../../utils/StorageSlot.sol"; + +contract Proxy is IERC1967 { + bytes32 internal constant _IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); + + /** + * Initializes the contract, setting proxy implementation address. + */ + constructor(address _implementation) { + _setImplementation(_implementation); + emit Upgraded(_implementation); + } + + /** + * Forward calls to the proxy implementation contract. + */ + receive() external payable { + proxy(); + } + + /** + * Forward calls to the proxy implementation contract. + */ + fallback() external payable { + proxy(); + } + + /** + * Forward calls to the proxy implementation contract. + */ + function proxy() private { + address target = _getImplementation(); + assembly { + let ptr := mload(0x40) + calldatacopy(ptr, 0, calldatasize()) + let result := delegatecall(gas(), target, ptr, calldatasize(), 0, 0) + let size := returndatasize() + returndatacopy(ptr, 0, size) + switch result + case 0 { revert(ptr, size) } + default { return(ptr, size) } + } + } + + /** + * Set the implementation address. + */ + function _setImplementation(address _implementation) internal { + StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = _implementation; + } + + /** + * Returns the address of the current implementation. + */ + function _getImplementation() internal view returns (address) { + return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; + } +} diff --git a/src/proxies/ERC1967/ProxyDeployer.sol b/src/proxies/ERC1967/ProxyDeployer.sol new file mode 100644 index 0000000..a61f8a6 --- /dev/null +++ b/src/proxies/ERC1967/ProxyDeployer.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ProxyDeployerErrors} from "./ProxyDeployerErrors.sol"; +import {Proxy} from "./Proxy.sol"; + +abstract contract ProxyDeployer is ProxyDeployerErrors { + /** + * Creates a proxy contract for a given implementation + * @param implAddr The address of the proxy implementation + * @param salt The deployment salt + * @return proxyAddr The address of the deployed proxy + */ + function _deployProxy(address implAddr, bytes32 salt) internal returns (address proxyAddr) { + bytes memory code = _getProxyCode(implAddr); + + // Deploy it + assembly { + proxyAddr := create2(0, add(code, 32), mload(code), salt) + } + if (proxyAddr == address(0)) { + revert ProxyCreationFailed(); + } + return proxyAddr; + } + + /** + * Predict the deployed wrapper proxy address for a given implementation. + * @param implAddr The address of the proxy implementation + * @param salt The deployment salt + * @return proxyAddr The address of the deployed wrapper + */ + function predictProxyAddress(address implAddr, bytes32 salt) public view returns (address proxyAddr) { + bytes memory code = _getProxyCode(implAddr); + return _predictProxyAddress(code, salt); + } + + /** + * Predict the deployed wrapper proxy address for a given implementation. + * @param code The code of the wrapper implementation + * @param salt The deployment salt + * @return proxyAddr The address of the deployed wrapper + */ + function _predictProxyAddress(bytes memory code, bytes32 salt) private view returns (address proxyAddr) { + address deployer = address(this); + bytes32 _data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(code))); + return address(uint160(uint256(_data))); + } + + /** + * Returns the code of the proxy contract for a given implementation + * @param implAddr The address of the proxy implementation + * @return code The code of the proxy contract + */ + function _getProxyCode(address implAddr) private pure returns (bytes memory code) { + return abi.encodePacked(type(Proxy).creationCode, abi.encode(implAddr)); + } + + /** + * Checks if an address is a contract + * @param addr The address to check + * @return result True if the address is a contract + */ + function _isContract(address addr) internal view returns (bool result) { + uint256 csize; + // solhint-disable-next-line no-inline-assembly + assembly { + csize := extcodesize(addr) + } + return csize != 0; + } +} diff --git a/src/proxies/ERC1967/ProxyDeployerErrors.sol b/src/proxies/ERC1967/ProxyDeployerErrors.sol new file mode 100644 index 0000000..cdab2c9 --- /dev/null +++ b/src/proxies/ERC1967/ProxyDeployerErrors.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +/** + * Errors for the Proxy Deployer contract. + */ +abstract contract ProxyDeployerErrors { + // Factories + error ProxyCreationFailed(); +} diff --git a/src/tokens/ERC1155/ERC1155Token.sol b/src/tokens/ERC1155/ERC1155Token.sol new file mode 100644 index 0000000..f81ed5c --- /dev/null +++ b/src/tokens/ERC1155/ERC1155Token.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC1155, ERC1155MintBurn} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155MintBurn.sol"; +import {ERC1155Meta} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Meta.sol"; +import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; +import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +error InvalidInitialization(); + +/** + * A ready made implementation of ERC-1155. + */ +contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981, AccessControl { + bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); + + address private immutable _initializer; + bool private _initialized; + + /** + * Initialize contract. + */ + constructor() ERC1155Metadata("", "") { + _initializer = msg.sender; + } + + /** + * Initialize the contract. + * @param _owner Owner address. + * @param _name Token name. + * @param _baseURI Base URI for token metadata. + * @dev This should be called immediately after deployment. + */ + function initialize(address _owner, string memory _name, string memory _baseURI) public { + if (msg.sender != _initializer || _initialized) { + revert InvalidInitialization(); + } + _initialized = true; + + name = _name; + baseURI = _baseURI; + + _setupRole(DEFAULT_ADMIN_ROLE, _owner); + _setupRole(MINTER_ROLE, _owner); + _setupRole(ROYALTY_ADMIN_ROLE, _owner); + _setupRole(METADATA_ADMIN_ROLE, _owner); + } + + // + // Minting + // + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _tokenId Token ID to mint. + * @param _amount Amount of tokens to mint. + * @param _data Data to pass if receiver is contract. + */ + function mint(address _to, uint256 _tokenId, uint256 _amount, bytes memory _data) external onlyRole(MINTER_ROLE) { + _mint(_to, _tokenId, _amount, _data); + } + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _tokenIds Token IDs to mint. + * @param _amounts Amounts of tokens to mint. + * @param _data Data to pass if receiver is contract. + */ + function batchMint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + external + onlyRole(MINTER_ROLE) + { + _batchMint(_to, _tokenIds, _amounts, _data); + } + + // + // Royalty + // + + /** + * Sets the royalty information that all ids in this contract will default to. + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + */ + function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) external onlyRole(ROYALTY_ADMIN_ROLE) { + _setDefaultRoyalty(_receiver, _feeNumerator); + } + + /** + * Sets the royalty information that a given token id in this contract will use. + * @param _tokenId The token id to set the royalty information for + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @notice This overrides the default royalty information for this token id + */ + function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) + external + onlyRole(ROYALTY_ADMIN_ROLE) + { + _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + } + + // + // Metadata + // + + /** + * Update the base URL of token's URI. + * @param _baseMetadataURI New base URL of token's URI + */ + function setBaseMetadataURI(string memory _baseMetadataURI) external onlyRole(METADATA_ADMIN_ROLE) { + _setBaseMetadataURI(_baseMetadataURI); + } + + /** + * Update the name of the contract. + * @param _name New contract name + */ + function setContractName(string memory _name) external onlyRole(METADATA_ADMIN_ROLE) { + _setContractName(_name); + } + + // + // Views + // + + /** + * Check interface support. + * @param _interfaceId Interface id + * @return True if supported + */ + function supportsInterface(bytes4 _interfaceId) + public + view + override (ERC1155, ERC1155Metadata, ERC2981, AccessControl) + returns (bool) + { + return ERC1155.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId) + || ERC2981.supportsInterface(_interfaceId) || AccessControl.supportsInterface(_interfaceId) + || super.supportsInterface(_interfaceId); + } +} diff --git a/src/tokens/ERC1155/ERC1155TokenFactory.sol b/src/tokens/ERC1155/ERC1155TokenFactory.sol new file mode 100644 index 0000000..850f559 --- /dev/null +++ b/src/tokens/ERC1155/ERC1155TokenFactory.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC1155Token} from "./ERC1155Token.sol"; +import {IERC1155TokenFactory} from "./IERC1155TokenFactory.sol"; +import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; + +contract ERC1155TokenFactory is IERC1155TokenFactory, ProxyDeployer { + address private immutable _implAddr; + + /** + * Creates an ERC-1155 Token Factory. + */ + constructor() { + ERC1155Token proxyImpl = new ERC1155Token(); + _implAddr = address(proxyImpl); + } + + /** + * Creates an ERC-1155 Token proxy. + * @param _owner The owner of the ERC-1155 Token proxy + * @param _name The name of the ERC-1155 Token proxy + * @param _baseURI The base URI of the ERC-1155 Token proxy + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-1155 Token Proxy + */ + function deploy(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + external + returns (address proxyAddr) + { + proxyAddr = _deployProxy(_implAddr, _salt); + ERC1155Token(proxyAddr).initialize(_owner, _name, _baseURI); + emit ERC1155TokenDeployed(proxyAddr); + return proxyAddr; + } +} diff --git a/src/tokens/ERC1155/IERC1155TokenFactory.sol b/src/tokens/ERC1155/IERC1155TokenFactory.sol new file mode 100644 index 0000000..23b79a0 --- /dev/null +++ b/src/tokens/ERC1155/IERC1155TokenFactory.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC1155TokenFactory { + /** + * Event emitted when a new ERC-1155 Token proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC1155TokenDeployed(address proxyAddr); + + /** + * Creates an ERC-1155 Token proxy. + * @param _owner The owner of the ERC-1155 Token proxy + * @param _name The name of the ERC-1155 Token proxy + * @param _baseURI The base URI of the ERC-1155 Token proxy + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-1155 Token Proxy + */ + function deploy(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + external + returns (address proxyAddr); +} diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol new file mode 100644 index 0000000..1a41db1 --- /dev/null +++ b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import { + ERC1155PackedBalance, + ERC1155MintBurnPackedBalance +} from "@0xsequence/erc-1155/contracts/tokens/ERC1155PackedBalance/ERC1155MintBurnPackedBalance.sol"; +import {ERC1155MetaPackedBalance} from + "@0xsequence/erc-1155/contracts/tokens/ERC1155PackedBalance/ERC1155MetaPackedBalance.sol"; +import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; +import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +error InvalidInitialization(); + +/** + * A ready made implementation of ERC-1155. + */ +contract ERC1155PackedToken is + ERC1155MintBurnPackedBalance, + ERC1155MetaPackedBalance, + ERC1155Metadata, + ERC2981, + AccessControl +{ + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); + bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); + + bool private _initialized; + + /** + * Initialize contract. + */ + constructor() ERC1155Metadata("", "") {} + + /** + * Initialize the contract. + * @param _owner Owner address. + * @param _name Token name. + * @param _baseURI Base URI for token metadata. + * @dev This should be called immediately after deployment. + */ + function initialize(address _owner, string memory _name, string memory _baseURI) public { + if (_initialized) { + revert InvalidInitialization(); + } + _initialized = true; + name = _name; + baseURI = _baseURI; + + _setupRole(DEFAULT_ADMIN_ROLE, _owner); + _setupRole(MINTER_ROLE, _owner); + _setupRole(ROYALTY_ADMIN_ROLE, _owner); + _setupRole(METADATA_ADMIN_ROLE, _owner); + } + + // + // Minting + // + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _tokenId Token ID to mint. + * @param _amount Amount of tokens to mint. + * @param _data Data to pass if receiver is contract. + */ + function mint(address _to, uint256 _tokenId, uint256 _amount, bytes memory _data) external onlyRole(MINTER_ROLE) { + _mint(_to, _tokenId, _amount, _data); + } + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _tokenIds Token IDs to mint. + * @param _amounts Amounts of tokens to mint. + * @param _data Data to pass if receiver is contract. + */ + function batchMint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + external + onlyRole(MINTER_ROLE) + { + _batchMint(_to, _tokenIds, _amounts, _data); + } + + // + // Royalty + // + + /** + * Sets the royalty information that all ids in this contract will default to. + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + */ + function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) external onlyRole(ROYALTY_ADMIN_ROLE) { + _setDefaultRoyalty(_receiver, _feeNumerator); + } + + /** + * Sets the royalty information that a given token id in this contract will use. + * @param _tokenId The token id to set the royalty information for + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @notice This overrides the default royalty information for this token id + */ + function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) + external + onlyRole(ROYALTY_ADMIN_ROLE) + { + _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + } + + // + // Metadata + // + + /** + * Update the base URL of token's URI. + * @param _baseMetadataURI New base URL of token's URI + */ + function setBaseMetadataURI(string memory _baseMetadataURI) external onlyRole(METADATA_ADMIN_ROLE) { + _setBaseMetadataURI(_baseMetadataURI); + } + + /** + * Update the name of the contract. + * @param _name New contract name + */ + function setContractName(string memory _name) external onlyRole(METADATA_ADMIN_ROLE) { + _setContractName(_name); + } + + // + // Views + // + + /** + * Check interface support. + * @param _interfaceId Interface id + * @return True if supported + */ + function supportsInterface(bytes4 _interfaceId) + public + view + override (ERC1155PackedBalance, ERC1155Metadata, ERC2981, AccessControl) + returns (bool) + { + return ERC1155PackedBalance.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId) + || ERC2981.supportsInterface(_interfaceId) || AccessControl.supportsInterface(_interfaceId) + || super.supportsInterface(_interfaceId); + } +} diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol b/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol new file mode 100644 index 0000000..f10dd9c --- /dev/null +++ b/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC1155PackedToken} from "./ERC1155PackedToken.sol"; +import {IERC1155PackedTokenFactory} from "./IERC1155PackedTokenFactory.sol"; +import {ProxyDeployer} from "../../../proxies/ERC1967/ProxyDeployer.sol"; + +contract ERC1155PackedTokenFactory is IERC1155PackedTokenFactory, ProxyDeployer { + address private immutable _implAddr; + + /** + * Creates an ERC-1155 Token Factory. + */ + constructor() { + ERC1155PackedToken proxyImpl = new ERC1155PackedToken(); + _implAddr = address(proxyImpl); + } + + /** + * Creates an ERC-1155 Packed Token proxy. + * @param _owner The owner of the ERC-1155 Packed Token proxy + * @param _name The name of the ERC-1155 Packed Token proxy + * @param _baseURI The base URI of the ERC-1155 Packed Token proxy + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-1155 Packed Token Proxy + */ + function deploy(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + external + returns (address proxyAddr) + { + proxyAddr = _deployProxy(_implAddr, _salt); + ERC1155PackedToken(proxyAddr).initialize(_owner, _name, _baseURI); + emit ERC1155PackedTokenDeployed(proxyAddr); + return proxyAddr; + } +} diff --git a/src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol b/src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol new file mode 100644 index 0000000..e4f7ef1 --- /dev/null +++ b/src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC1155PackedTokenFactory { + /** + * Event emitted when a new ERC-1155 Packed Token proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC1155PackedTokenDeployed(address proxyAddr); + + /** + * Creates an ERC-1155 Packed Token proxy. + * @param _owner The owner of the ERC-1155 Packed Token proxy + * @param _name The name of the ERC-1155 Packed Token proxy + * @param _baseURI The base URI of the ERC-1155 Packed Token proxy + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-1155 Packed Token Proxy + */ + function deploy(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + external + returns (address proxyAddr); +} diff --git a/src/tokens/ERC721/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol new file mode 100644 index 0000000..934637b --- /dev/null +++ b/src/tokens/ERC721/ERC721Token.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import { + ERC721AQueryable, IERC721AQueryable, ERC721A, IERC721A +} from "erc721a/contracts/extensions/ERC721AQueryable.sol"; +import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +error InvalidInitialization(); + +/** + * A ready made implementation of ERC-721. + */ +contract ERC721Token is ERC721AQueryable, ERC2981, AccessControl { + bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); + + string private baseURI; // Missing _ due to _baseURI() function in ERC721A + string private _name; + string private _symbol; + + address private immutable _initializer; + bool private _initialized; + + /** + * Deploy contract. + */ + constructor() ERC721A("", "") { + _initializer = msg.sender; + } + + /** + * Initialize contract. + * @param owner_ The owner of the contract + * @param name_ Name of the token + * @param symbol_ Symbol of the token + * @param baseURI_ Base URI of the token + * @dev This should be called immediately after deployment. + */ + function initialize(address owner_, string memory name_, string memory symbol_, string memory baseURI_) external { + if (msg.sender != _initializer || _initialized) { + revert InvalidInitialization(); + } + _initialized = true; + + _name = name_; + _symbol = symbol_; + baseURI = baseURI_; + + _setupRole(DEFAULT_ADMIN_ROLE, owner_); + _setupRole(METADATA_ADMIN_ROLE, owner_); + _setupRole(MINTER_ROLE, owner_); + _setupRole(ROYALTY_ADMIN_ROLE, owner_); + } + + // + // Minting + // + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _amount Amount of tokens to mint. + */ + function mint(address _to, uint256 _amount) external onlyRole(MINTER_ROLE) { + _mint(_to, _amount); + } + + // + // Royalty + // + + /** + * Sets the royalty information that all ids in this contract will default to. + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + */ + function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) external onlyRole(ROYALTY_ADMIN_ROLE) { + _setDefaultRoyalty(_receiver, _feeNumerator); + } + + /** + * Sets the royalty information that a given token id in this contract will use. + * @param _tokenId The token id to set the royalty information for + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @notice This overrides the default royalty information for this token id + */ + function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) + external + onlyRole(ROYALTY_ADMIN_ROLE) + { + _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + } + + // + // Metadata + // + + /** + * Update the base URL of token's URI. + * @param _baseMetadataURI New base URL of token's URI + */ + function setBaseMetadataURI(string memory _baseMetadataURI) external onlyRole(METADATA_ADMIN_ROLE) { + baseURI = _baseMetadataURI; + } + + // + // Views + // + + /** + * Check interface support. + * @param _interfaceId Interface id + * @return True if supported + */ + function supportsInterface(bytes4 _interfaceId) + public + view + override (ERC721A, IERC721A, ERC2981, AccessControl) + returns (bool) + { + return _interfaceId == type(IERC721A).interfaceId || _interfaceId == type(IERC721AQueryable).interfaceId + || ERC721A.supportsInterface(_interfaceId) || ERC2981.supportsInterface(_interfaceId) + || AccessControl.supportsInterface(_interfaceId) || super.supportsInterface(_interfaceId); + } + + // + // ERC721A Overrides + // + + /** + * Override the ERC721A baseURI function. + */ + function _baseURI() internal view override returns (string memory) { + return baseURI; + } + + /** + * Override the ERC721A name function. + */ + function name() public view override (ERC721A, IERC721A) returns (string memory) { + return _name; + } + + /** + * Override the ERC721A name function. + */ + function symbol() public view override (ERC721A, IERC721A) returns (string memory) { + return _symbol; + } +} diff --git a/src/tokens/ERC721/ERC721TokenFactory.sol b/src/tokens/ERC721/ERC721TokenFactory.sol new file mode 100644 index 0000000..ff20e99 --- /dev/null +++ b/src/tokens/ERC721/ERC721TokenFactory.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC721Token} from "./ERC721Token.sol"; +import {IERC721TokenFactory} from "./IERC721TokenFactory.sol"; +import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; + +contract ERC721TokenFactory is IERC721TokenFactory, ProxyDeployer { + address private immutable _implAddr; + + /** + * Creates an ERC-721 Token Factory. + */ + constructor() { + ERC721Token proxyImpl = new ERC721Token(); + _implAddr = address(proxyImpl); + } + + /** + * Creates an ERC-721 Token proxy. + * @param _owner The owner of the ERC-721 Token proxy + * @param _name The name of the ERC-721 Token proxy + * @param _symbol The symbol of the ERC-721 Token proxy + * @param _baseURI The base URI of the ERC-721 Token proxy + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-721 Token Proxy + */ + function deploy(address _owner, string memory _name, string memory _symbol, string memory _baseURI, bytes32 _salt) + external + returns (address proxyAddr) + { + proxyAddr = _deployProxy(_implAddr, _salt); + ERC721Token(proxyAddr).initialize(_owner, _name, _symbol, _baseURI); + emit ERC721TokenDeployed(proxyAddr); + return proxyAddr; + } +} diff --git a/src/tokens/ERC721/IERC721TokenFactory.sol b/src/tokens/ERC721/IERC721TokenFactory.sol new file mode 100644 index 0000000..ed78642 --- /dev/null +++ b/src/tokens/ERC721/IERC721TokenFactory.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC721TokenFactory { + /** + * Event emitted when a new ERC-721 Token proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC721TokenDeployed(address proxyAddr); + + /** + * Creates an ERC-721 Token proxy. + * @param _owner The owner of the ERC-721 Token proxy + * @param _name The name of the ERC-721 Token proxy + * @param _symbol The symbol of the ERC-721 Token proxy + * @param _baseURI The base URI of the ERC-721 Token proxy + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-721 Token Proxy + */ + function deploy(address _owner, string memory _name, string memory _symbol, string memory _baseURI, bytes32 _salt) + external + returns (address proxyAddr); +} diff --git a/src/utils/StorageSlot.sol b/src/utils/StorageSlot.sol new file mode 100644 index 0000000..775a4b6 --- /dev/null +++ b/src/utils/StorageSlot.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +/** + * @dev Library for reading and writing primitive types to specific storage slots. + * + * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. + * This library helps with reading and writing to such slots without the need for inline assembly. + * + * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. + */ +library StorageSlot { + struct AddressSlot { + address value; + } + + struct BooleanSlot { + bool value; + } + + struct Bytes32Slot { + bytes32 value; + } + + struct Uint256Slot { + uint256 value; + } + + /** + * @dev Returns an `AddressSlot` with member `value` located at `slot`. + */ + function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `BooleanSlot` with member `value` located at `slot`. + */ + function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. + */ + function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `Uint256Slot` with member `value` located at `slot`. + */ + function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { + assembly { + r.slot := slot + } + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 30235e8..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function testIncrement() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testSetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/tokens/ERC1155/ERC1155Token.t.sol b/test/tokens/ERC1155/ERC1155Token.t.sol new file mode 100644 index 0000000..9b6b84a --- /dev/null +++ b/test/tokens/ERC1155/ERC1155Token.t.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {ERC1155Token, InvalidInitialization} from "src/tokens/ERC1155/ERC1155Token.sol"; +import {ERC1155TokenFactory} from "src/tokens/ERC1155/ERC1155TokenFactory.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +// Interfaces +import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; +import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; +import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; + +contract ERC1155TokenTest is Test { + // Redeclare events + event TransferSingle( + address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _amount + ); + event TransferBatch( + address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts + ); + + ERC1155Token private token; + + address owner; + + function setUp() public { + owner = makeAddr("owner"); + + vm.deal(address(this), 100 ether); + vm.deal(owner, 100 ether); + + ERC1155TokenFactory factory = new ERC1155TokenFactory(); + token = ERC1155Token(factory.deploy(owner, "name", "baseURI", 0x0)); + } + + function testReinitializeFails() public { + vm.expectRevert(InvalidInitialization.selector); + token.initialize(owner, "name", "baseURI"); + } + + function testSupportsInterface() public { + assertTrue(token.supportsInterface(type(IERC165).interfaceId)); + assertTrue(token.supportsInterface(type(IERC1155).interfaceId)); + assertTrue(token.supportsInterface(type(IERC1155Metadata).interfaceId)); + } + + function testOwnerHasRoles() public { + assertTrue(token.hasRole(token.DEFAULT_ADMIN_ROLE(), owner)); + assertTrue(token.hasRole(token.METADATA_ADMIN_ROLE(), owner)); + assertTrue(token.hasRole(token.MINTER_ROLE(), owner)); + assertTrue(token.hasRole(token.ROYALTY_ADMIN_ROLE(), owner)); + } + + // + // Minting + // + function testMintInvalidRole(address caller) public { + vm.assume(caller != owner); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.MINTER_ROLE()), 32) + ) + ); + vm.prank(caller); + token.mint(caller, 1, 1, ""); + + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = 1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.MINTER_ROLE()), 32) + ) + ); + vm.prank(caller); + token.batchMint(caller, tokenIds, amounts, ""); + } + + function testMintOwner(uint256 tokenId, uint256 amount) public { + vm.assume(amount > 0); + + vm.expectEmit(true, true, true, true, address(token)); + emit TransferSingle(owner, address(0), owner, tokenId, amount); + vm.prank(owner); + token.mint(owner, tokenId, amount, ""); + + assertEq(token.balanceOf(owner, tokenId), amount); + } + + function testBatchMintOwner(uint256[] memory tokenIds, uint256[] memory amounts) public { + tokenIds = boundArrayLength(tokenIds, 10); + amounts = boundArrayLength(amounts, 10); + vm.assume(tokenIds.length == amounts.length); + for (uint256 i; i < amounts.length; i++) { + vm.assume(amounts[i] > 0); + } + // Unique ids + for (uint256 i; i < tokenIds.length; i++) { + for (uint256 j; j < tokenIds.length; j++) { + if (i != j) { + vm.assume(tokenIds[i] != tokenIds[j]); + } + } + } + + vm.expectEmit(true, true, true, true, address(token)); + emit TransferBatch(owner, address(0), owner, tokenIds, amounts); + vm.prank(owner); + token.batchMint(owner, tokenIds, amounts, ""); + } + + function testMintWithRole(address minter, uint256 tokenId, uint256 amount) public { + vm.assume(minter != owner); + vm.assume(minter != address(0)); + // Give role + vm.startPrank(owner); + token.grantRole(token.MINTER_ROLE(), minter); + vm.stopPrank(); + + vm.expectEmit(true, true, true, true, address(token)); + emit TransferSingle(minter, address(0), owner, tokenId, amount); + + vm.prank(minter); + token.mint(owner, tokenId, amount, ""); + + assertEq(token.balanceOf(owner, tokenId), amount); + } + + function testBatchMintWithRole(address minter, uint256[] memory tokenIds, uint256[] memory amounts) public { + vm.assume(minter != owner); + vm.assume(minter != address(0)); + tokenIds = boundArrayLength(tokenIds, 10); + amounts = boundArrayLength(amounts, 10); + vm.assume(tokenIds.length == amounts.length); + for (uint256 i; i < amounts.length; i++) { + vm.assume(amounts[i] > 0); + } + // Unique ids + for (uint256 i; i < tokenIds.length; i++) { + for (uint256 j; j < tokenIds.length; j++) { + if (i != j) { + vm.assume(tokenIds[i] != tokenIds[j]); + } + } + } + + // Give role + vm.startPrank(owner); + token.grantRole(token.MINTER_ROLE(), minter); + vm.stopPrank(); + + vm.expectEmit(true, true, true, true, address(token)); + emit TransferBatch(minter, address(0), owner, tokenIds, amounts); + vm.prank(minter); + token.batchMint(owner, tokenIds, amounts, ""); + } + + // + // Metadata + // + function testMetadataOwner() public { + vm.prank(owner); + token.setBaseMetadataURI("ipfs://newURI/"); + + assertEq(token.uri(0), "ipfs://newURI/0.json"); + assertEq(token.uri(1), "ipfs://newURI/1.json"); + } + + function testMetadataInvalid(address caller) public { + vm.assume(caller != owner); + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.METADATA_ADMIN_ROLE()), 32) + ) + ); + vm.prank(caller); + token.setBaseMetadataURI("ipfs://newURI/"); + } + + function testMetadataWithRole(address caller) public { + vm.assume(caller != owner); + vm.assume(caller != address(0)); + // Give role + vm.startPrank(owner); + token.grantRole(token.METADATA_ADMIN_ROLE(), caller); + vm.stopPrank(); + + vm.prank(caller); + token.setBaseMetadataURI("ipfs://newURI/"); + } + + // + // Royalty + // + function testDefaultRoyalty(address receiver, uint96 feeNumerator, uint256 salePrice) public { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.prank(owner); + token.setDefaultRoyalty(receiver, feeNumerator); + + (address receiver_, uint256 amount) = token.royaltyInfo(1, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + } + + function testTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator, uint256 salePrice) public { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(tokenId != 69); // Other token id for default validation + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.prank(owner); + token.setTokenRoyalty(tokenId, receiver, feeNumerator); + + (address receiver_, uint256 amount) = token.royaltyInfo(tokenId, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + + (receiver_, amount) = token.royaltyInfo(69, salePrice); + assertEq(receiver_, address(0)); + assertEq(amount, 0); + } + + function testRoyaltyWithRole( + address caller, + uint256 tokenId, + address receiver, + uint96 feeNumerator, + uint256 salePrice + ) + public + { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(caller != owner); + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.startPrank(owner); + token.grantRole(token.ROYALTY_ADMIN_ROLE(), caller); + vm.stopPrank(); + + vm.prank(caller); + token.setDefaultRoyalty(receiver, feeNumerator); + + (address receiver_, uint256 amount) = token.royaltyInfo(1, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + + vm.prank(caller); + token.setTokenRoyalty(tokenId, receiver, feeNumerator); + + (receiver_, amount) = token.royaltyInfo(tokenId, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + } + + function testRoyaltyInvalidRole( + address caller, + uint256 tokenId, + address receiver, + uint96 feeNumerator, + uint256 salePrice + ) + public + { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(caller != owner); + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + vm.prank(caller); + token.setDefaultRoyalty(receiver, feeNumerator); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + vm.prank(caller); + token.setTokenRoyalty(tokenId, receiver, feeNumerator); + } + + function boundArrayLength(uint256[] memory arr, uint256 maxSize) private pure returns (uint256[] memory) { + if (arr.length <= maxSize) { + return arr; + } + uint256[] memory result = new uint256[](maxSize); + for (uint256 i; i < maxSize; i++) { + result[i] = arr[i]; + } + return result; + } +} diff --git a/test/tokens/ERC1155/Packed/ERC1155PackedToken.t.sol b/test/tokens/ERC1155/Packed/ERC1155PackedToken.t.sol new file mode 100644 index 0000000..6a8e4c2 --- /dev/null +++ b/test/tokens/ERC1155/Packed/ERC1155PackedToken.t.sol @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {ERC1155PackedToken, InvalidInitialization} from "src/tokens/ERC1155/Packed/ERC1155PackedToken.sol"; +import {ERC1155PackedTokenFactory} from "src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +// Interfaces +import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; +import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; +import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; + +contract ERC1155TokenTest is Test { + // Redeclare events + event TransferSingle( + address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _amount + ); + event TransferBatch( + address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts + ); + + ERC1155PackedToken private token; + + address owner; + + function setUp() public { + owner = makeAddr("owner"); + + vm.deal(address(this), 100 ether); + vm.deal(owner, 100 ether); + + ERC1155PackedTokenFactory factory = new ERC1155PackedTokenFactory(); + token = ERC1155PackedToken(factory.deploy(owner, "name", "baseURI", 0x0)); + } + + function testReinitializeFails() public { + vm.expectRevert(InvalidInitialization.selector); + token.initialize(owner, "name", "baseURI"); + } + + function testSupportsInterface() public { + assertTrue(token.supportsInterface(type(IERC165).interfaceId)); + assertTrue(token.supportsInterface(type(IERC1155).interfaceId)); + assertTrue(token.supportsInterface(type(IERC1155Metadata).interfaceId)); + } + + function testOwnerHasRoles() public { + assertTrue(token.hasRole(token.DEFAULT_ADMIN_ROLE(), owner)); + assertTrue(token.hasRole(token.METADATA_ADMIN_ROLE(), owner)); + assertTrue(token.hasRole(token.MINTER_ROLE(), owner)); + assertTrue(token.hasRole(token.ROYALTY_ADMIN_ROLE(), owner)); + } + + // + // Minting + // + function testMintInvalidRole(address caller) public { + vm.assume(caller != owner); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.MINTER_ROLE()), 32) + ) + ); + vm.prank(caller); + token.mint(caller, 1, 1, ""); + + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = 1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.MINTER_ROLE()), 32) + ) + ); + vm.prank(caller); + token.batchMint(caller, tokenIds, amounts, ""); + } + + function testMintOwner(uint256 tokenId, uint256 amount) public { + vm.assume(amount > 0 && amount < 2 ** 16); + + vm.expectEmit(true, true, true, true, address(token)); + emit TransferSingle(owner, address(0), owner, tokenId, amount); + vm.prank(owner); + token.mint(owner, tokenId, amount, ""); + + assertEq(token.balanceOf(owner, tokenId), amount); + } + + function testBatchMintOwner(uint256[] memory tokenIds, uint256[] memory amounts) public { + tokenIds = boundArrayLength(tokenIds, 10); + amounts = boundArrayLength(amounts, 10); + vm.assume(tokenIds.length == amounts.length); + for (uint256 i; i < amounts.length; i++) { + amounts[i] = _bound(amounts[i], 1, 2 ** 16); + } + + vm.expectEmit(true, true, true, true, address(token)); + emit TransferBatch(owner, address(0), owner, tokenIds, amounts); + vm.prank(owner); + token.batchMint(owner, tokenIds, amounts, ""); + } + + function testMintWithRole(address minter, uint256 tokenId, uint256 amount) public { + vm.assume(minter != owner); + vm.assume(minter != address(0)); + vm.assume(amount > 0 && amount < 2 ** 16); + // Give role + vm.startPrank(owner); + token.grantRole(token.MINTER_ROLE(), minter); + vm.stopPrank(); + + vm.expectEmit(true, true, true, true, address(token)); + emit TransferSingle(minter, address(0), owner, tokenId, amount); + + vm.prank(minter); + token.mint(owner, tokenId, amount, ""); + + assertEq(token.balanceOf(owner, tokenId), amount); + } + + function testBatchMintWithRole(address minter, uint256[] memory tokenIds, uint256[] memory amounts) public { + vm.assume(minter != owner); + vm.assume(minter != address(0)); + tokenIds = boundArrayLength(tokenIds, 10); + amounts = boundArrayLength(amounts, 10); + vm.assume(tokenIds.length == amounts.length); + for (uint256 i; i < amounts.length; i++) { + amounts[i] = _bound(amounts[i], 1, 2 ** 16); + } + // Unique ids + for (uint256 i; i < tokenIds.length; i++) { + for (uint256 j; j < tokenIds.length; j++) { + if (i != j) { + vm.assume(tokenIds[i] != tokenIds[j]); + } + } + } + + // Give role + vm.startPrank(owner); + token.grantRole(token.MINTER_ROLE(), minter); + vm.stopPrank(); + + vm.expectEmit(true, true, true, true, address(token)); + emit TransferBatch(minter, address(0), owner, tokenIds, amounts); + vm.prank(minter); + token.batchMint(owner, tokenIds, amounts, ""); + } + + // + // Metadata + // + function testMetadataOwner() public { + vm.prank(owner); + token.setBaseMetadataURI("ipfs://newURI/"); + + assertEq(token.uri(0), "ipfs://newURI/0.json"); + assertEq(token.uri(1), "ipfs://newURI/1.json"); + } + + function testMetadataInvalid(address caller) public { + vm.assume(caller != owner); + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.METADATA_ADMIN_ROLE()), 32) + ) + ); + vm.prank(caller); + token.setBaseMetadataURI("ipfs://newURI/"); + } + + function testMetadataWithRole(address caller) public { + vm.assume(caller != owner); + vm.assume(caller != address(0)); + // Give role + vm.startPrank(owner); + token.grantRole(token.METADATA_ADMIN_ROLE(), caller); + vm.stopPrank(); + + vm.prank(caller); + token.setBaseMetadataURI("ipfs://newURI/"); + } + + // + // Royalty + // + function testDefaultRoyalty(address receiver, uint96 feeNumerator, uint256 salePrice) public { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.prank(owner); + token.setDefaultRoyalty(receiver, feeNumerator); + + (address receiver_, uint256 amount) = token.royaltyInfo(1, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + } + + function testTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator, uint256 salePrice) public { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(tokenId != 69); // Other token id for default validation + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.prank(owner); + token.setTokenRoyalty(tokenId, receiver, feeNumerator); + + (address receiver_, uint256 amount) = token.royaltyInfo(tokenId, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + + (receiver_, amount) = token.royaltyInfo(69, salePrice); + assertEq(receiver_, address(0)); + assertEq(amount, 0); + } + + function testRoyaltyWithRole( + address caller, + uint256 tokenId, + address receiver, + uint96 feeNumerator, + uint256 salePrice + ) + public + { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(caller != owner); + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.startPrank(owner); + token.grantRole(token.ROYALTY_ADMIN_ROLE(), caller); + vm.stopPrank(); + + vm.prank(caller); + token.setDefaultRoyalty(receiver, feeNumerator); + + (address receiver_, uint256 amount) = token.royaltyInfo(1, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + + vm.prank(caller); + token.setTokenRoyalty(tokenId, receiver, feeNumerator); + + (receiver_, amount) = token.royaltyInfo(tokenId, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + } + + function testRoyaltyInvalidRole( + address caller, + uint256 tokenId, + address receiver, + uint96 feeNumerator, + uint256 salePrice + ) + public + { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(caller != owner); + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + vm.prank(caller); + token.setDefaultRoyalty(receiver, feeNumerator); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + vm.prank(caller); + token.setTokenRoyalty(tokenId, receiver, feeNumerator); + } + + function boundArrayLength(uint256[] memory arr, uint256 maxSize) private pure returns (uint256[] memory) { + if (arr.length <= maxSize) { + return arr; + } + uint256[] memory result = new uint256[](maxSize); + for (uint256 i; i < maxSize; i++) { + result[i] = arr[i]; + } + return result; + } +} diff --git a/test/tokens/ERC721/ERC721Token.t.sol b/test/tokens/ERC721/ERC721Token.t.sol new file mode 100644 index 0000000..a534dbe --- /dev/null +++ b/test/tokens/ERC721/ERC721Token.t.sol @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {ERC721Token, InvalidInitialization} from "src/tokens/ERC721/ERC721Token.sol"; +import {ERC721TokenFactory} from "src/tokens/ERC721/ERC721TokenFactory.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +// Interfaces +import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; +import {IERC721A} from "erc721a/contracts/interfaces/IERC721A.sol"; +import {IERC721AQueryable} from "erc721a/contracts/extensions/IERC721AQueryable.sol"; + +contract ERC721TokenTest is Test { + // Redeclare events + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + ERC721Token private token; + + address owner; + + function setUp() public { + owner = makeAddr("owner"); + + vm.deal(address(this), 100 ether); + vm.deal(owner, 100 ether); + + ERC721TokenFactory factory = new ERC721TokenFactory(); + token = ERC721Token(factory.deploy(owner, "name", "symbol", "baseURI", 0x0)); + } + + function testReinitializeFails() public { + vm.expectRevert(InvalidInitialization.selector); + token.initialize(owner, "name", "symbol", "baseURI"); + } + + function testSupportsInterface() public { + assertTrue(token.supportsInterface(type(IERC165).interfaceId)); + assertTrue(token.supportsInterface(type(IERC721A).interfaceId)); + assertTrue(token.supportsInterface(type(IERC721AQueryable).interfaceId)); + assertTrue(token.supportsInterface(0x80ac58cd)); // ERC721 + assertTrue(token.supportsInterface(0x5b5e139f)); // ERC721Metadata + } + + function testOwnerHasRoles() public { + assertTrue(token.hasRole(token.DEFAULT_ADMIN_ROLE(), owner)); + assertTrue(token.hasRole(token.METADATA_ADMIN_ROLE(), owner)); + assertTrue(token.hasRole(token.MINTER_ROLE(), owner)); + assertTrue(token.hasRole(token.ROYALTY_ADMIN_ROLE(), owner)); + } + + function testNameAndSymbol() public { + assertEq(token.name(), "name"); + assertEq(token.symbol(), "symbol"); + } + + // + // Minting + // + function testMintInvalidRole(address caller) public { + vm.assume(caller != owner); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.MINTER_ROLE()), 32) + ) + ); + vm.prank(caller); + token.mint(caller, 1); + } + + function testMintOwner() public { + vm.expectEmit(true, true, true, true, address(token)); + emit Transfer(address(0), owner, 0); + + vm.prank(owner); + token.mint(owner, 1); + + assertEq(token.balanceOf(owner), 1); + } + + function testMintWithRole(address minter) public { + vm.assume(minter != owner); + vm.assume(minter != address(0)); + // Give role + vm.startPrank(owner); + token.grantRole(token.MINTER_ROLE(), minter); + vm.stopPrank(); + + vm.expectEmit(true, true, true, true, address(token)); + emit Transfer(address(0), owner, 0); + + vm.prank(minter); + token.mint(owner, 1); + + assertEq(token.balanceOf(owner), 1); + } + + function testMintMultiple() public { + vm.expectEmit(true, true, true, true, address(token)); + emit Transfer(address(0), owner, 0); + vm.expectEmit(true, true, true, true, address(token)); + emit Transfer(address(0), owner, 1); + + vm.prank(owner); + token.mint(owner, 2); + + assertEq(token.balanceOf(owner), 2); + assertEq(token.ownerOf(0), owner); + assertEq(token.ownerOf(1), owner); + } + + // + // Metadata + // + function testMetadataOwner() public { + // Mint token + vm.prank(owner); + token.mint(owner, 2); + + vm.prank(owner); + token.setBaseMetadataURI("ipfs://newURI/"); + + assertEq(token.tokenURI(0), "ipfs://newURI/0"); + assertEq(token.tokenURI(1), "ipfs://newURI/1"); + + // Invalid token + vm.expectRevert(IERC721A.URIQueryForNonexistentToken.selector); + token.tokenURI(2); + } + + function testMetadataInvalid(address caller) public { + vm.assume(caller != owner); + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.METADATA_ADMIN_ROLE()), 32) + ) + ); + vm.prank(caller); + token.setBaseMetadataURI("ipfs://newURI/"); + } + + function testMetadataWithRole(address caller) public { + vm.assume(caller != owner); + vm.assume(caller != address(0)); + // Give role + vm.startPrank(owner); + token.grantRole(token.METADATA_ADMIN_ROLE(), caller); + vm.stopPrank(); + + vm.prank(caller); + token.setBaseMetadataURI("ipfs://newURI/"); + } + + // + // Royalty + // + function testDefaultRoyalty(address receiver, uint96 feeNumerator, uint256 salePrice) public { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.prank(owner); + token.setDefaultRoyalty(receiver, feeNumerator); + + (address receiver_, uint256 amount) = token.royaltyInfo(1, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + } + + function testTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator, uint256 salePrice) public { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(tokenId != 69); // Other token id for default validation + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.prank(owner); + token.setTokenRoyalty(tokenId, receiver, feeNumerator); + + (address receiver_, uint256 amount) = token.royaltyInfo(tokenId, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + + (receiver_, amount) = token.royaltyInfo(69, salePrice); + assertEq(receiver_, address(0)); + assertEq(amount, 0); + } + + function testRoyaltyWithRole( + address caller, + uint256 tokenId, + address receiver, + uint96 feeNumerator, + uint256 salePrice + ) + public + { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(caller != owner); + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.startPrank(owner); + token.grantRole(token.ROYALTY_ADMIN_ROLE(), caller); + vm.stopPrank(); + + vm.prank(caller); + token.setDefaultRoyalty(receiver, feeNumerator); + + (address receiver_, uint256 amount) = token.royaltyInfo(1, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + + vm.prank(caller); + token.setTokenRoyalty(tokenId, receiver, feeNumerator); + + (receiver_, amount) = token.royaltyInfo(tokenId, salePrice); + assertEq(receiver_, receiver); + assertEq(amount, salePrice * feeNumerator / 10000); + } + + function testRoyaltyInvalidRole( + address caller, + uint256 tokenId, + address receiver, + uint96 feeNumerator, + uint256 salePrice + ) + public + { + vm.assume(feeNumerator <= 10000); + vm.assume(receiver != address(0)); + vm.assume(caller != owner); + vm.assume(salePrice < type(uint128).max); // Buffer for overflow + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + vm.prank(caller); + token.setDefaultRoyalty(receiver, feeNumerator); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + vm.prank(caller); + token.setTokenRoyalty(tokenId, receiver, feeNumerator); + } +} diff --git a/yarn.lock b/yarn.lock index 2ce08dd..94dc5bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,6 +11,19 @@ "@ethersproject/providers" "^5.7.2" ethers "^5.7.2" +<<<<<<< HEAD +"@0xsequence/erc-1155@^4.0.2": +======= +"@0xsequence/erc-1155@^4.0.3": +>>>>>>> e4c96a7 (Base implementations) + version "4.0.3" + resolved "https://registry.yarnpkg.com/@0xsequence/erc-1155/-/erc-1155-4.0.3.tgz#b9b890c1ced04a84f36be22dd20424b43b9e1da2" + integrity sha512-vDh4OEuq0bR3iIqhsxpuRpczH5GxwH/mjBWm9uP6VYKnKM36ZBLApSNUlIOCXYRCS0DYFxsdvYjAemb3w1l4ow== + optionalDependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + ethers "^5.7.2" + "@0xsequence/erc20-meta-token@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@0xsequence/erc20-meta-token/-/erc20-meta-token-4.0.1.tgz#44fe77822af0ff3b1111466615c9958e92aca782" @@ -385,6 +398,11 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@openzeppelin/contracts@^4.8.3": + version "4.8.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.3.tgz#cbef3146bfc570849405f59cba18235da95a252a" + integrity sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg== + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -608,6 +626,11 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +erc721a-upgradeable@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/erc721a-upgradeable/-/erc721a-upgradeable-4.2.3.tgz#9e67a9d628f8648a0cd64d17a7a13eeba61deba1" + integrity sha512-EaHbOVDau9drDNpi/gWUHHaopCh35NMATa+3+9ZmdHokw9kfPiDD5RhGRlXA1aZA0ZfYvqPEbaKuSH3PaCY2Ug== + erc721a@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/erc721a/-/erc721a-4.2.3.tgz#ca6469b0e54afb0f614272c2147dc4cb49ff223f" From 214cbbb52a2bccd7b2f6e844c6d7aed431a03358 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 7 Jun 2023 12:18:32 +1200 Subject: [PATCH 05/49] Add CI --- .github/workflows/ci.yaml | 96 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..c3f7000 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,96 @@ + +on: [push] + +name: ci + +jobs: + install: + name: Install dependencies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: actions/setup-node@v1 + with: + node-version: 18 + - uses: actions/cache@master + id: yarn-cache + with: + path: | + node_modules + */*/node_modules + key: ${{ runner.os }}-lerna-${{ hashFiles('**/package.json', '**/yarn.lock') }} + - run: yarn install --network-concurrency 1 + if: ${{ steps.yarn-cache.outputs.cache-hit != 'true' }} + + lint-sol: + name: Solidity lint + runs-on: ubuntu-latest + needs: [install] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: actions/setup-node@v1 + with: + node-version: 18 + - uses: actions/cache@master + id: yarn-cache + with: + path: | + node_modules + */*/node_modules + key: ${{ runner.os }}-lerna-${{ hashFiles('**/package.json', '**/yarn.lock') }} + - run: yarn lint:sol + + foundry-tests: + name: Foundry tests + runs-on: ubuntu-latest + needs: [install] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: actions/setup-node@v1 + with: + node-version: 18 + - uses: actions/cache@master + id: yarn-cache + with: + path: | + node_modules + */*/node_modules + key: ${{ runner.os }}-lerna-${{ hashFiles('**/package.json', '**/yarn.lock') }} + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run tests + run: FOUNDRY_FUZZ_RUNS=1024 forge test -vvv + + # coverage: + # name: Coverage + # runs-on: ubuntu-latest + # needs: [install] + # steps: + # - uses: actions/checkout@v3 + # with: + # submodules: recursive + # - uses: actions/setup-node@v1 + # with: + # node-version: 18 + # - uses: actions/cache@master + # id: yarn-cache + # with: + # path: | + # node_modules + # */*/node_modules + # key: ${{ runner.os }}-lerna-${{ hashFiles('**/package.json', '**/yarn.lock') }} + # - run: yarn coverage || true + # - name: Coveralls + # uses: coverallsapp/github-action@master + # with: + # github-token: ${{ secrets.GITHUB_TOKEN }} From 3416682bc3058f0c458b4020e142b93b921d31cd Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 7 Jun 2023 12:44:14 +1200 Subject: [PATCH 06/49] Add ERC20 base token --- src/tokens/ERC20/ERC20Token.sol | 104 +++++++++++++++++++++++ src/tokens/ERC20/ERC20TokenFactory.sol | 37 +++++++++ src/tokens/ERC20/IERC20TokenFactory.sol | 23 ++++++ src/tokens/ERC721/ERC721Token.sol | 2 +- test/tokens/ERC20/ERC20Token.t.sol | 105 ++++++++++++++++++++++++ 5 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 src/tokens/ERC20/ERC20Token.sol create mode 100644 src/tokens/ERC20/ERC20TokenFactory.sol create mode 100644 src/tokens/ERC20/IERC20TokenFactory.sol create mode 100644 test/tokens/ERC20/ERC20Token.t.sol diff --git a/src/tokens/ERC20/ERC20Token.sol b/src/tokens/ERC20/ERC20Token.sol new file mode 100644 index 0000000..a287fff --- /dev/null +++ b/src/tokens/ERC20/ERC20Token.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +error InvalidInitialization(); + +/** + * A ready made implementation of ERC-20. + */ +contract ERC20Token is ERC20, AccessControl { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + string private _name; + string private _symbol; + uint8 private _decimals; + + address private immutable _initializer; + bool private _initialized; + + /** + * Deploy contract. + */ + constructor() ERC20("", "") { + _initializer = msg.sender; + } + + /** + * Initialize contract. + * @param owner_ The owner of the contract + * @param name_ Name of the token + * @param symbol_ Symbol of the token + * @param decimals_ Number of decimals + * @dev This should be called immediately after deployment. + */ + function initialize(address owner_, string memory name_, string memory symbol_, uint8 decimals_) external { + if (msg.sender != _initializer || _initialized) { + revert InvalidInitialization(); + } + _initialized = true; + + _name = name_; + _symbol = symbol_; + _decimals = decimals_; + + _setupRole(DEFAULT_ADMIN_ROLE, owner_); + _setupRole(MINTER_ROLE, owner_); + } + + // + // Minting + // + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _amount Amount of tokens to mint. + */ + function mint(address _to, uint256 _amount) external onlyRole(MINTER_ROLE) { + _mint(_to, _amount); + } + + // + // Views + // + + /** + * Check interface support. + * @param _interfaceId Interface id + * @return True if supported + */ + function supportsInterface(bytes4 _interfaceId) public view override returns (bool) { + return _interfaceId == type(IERC20).interfaceId || _interfaceId == type(IERC20Metadata).interfaceId + || AccessControl.supportsInterface(_interfaceId) || super.supportsInterface(_interfaceId); + } + + // + // ERC20 Overrides + // + + /** + * Override the ERC20 name function. + */ + function name() public view override returns (string memory) { + return _name; + } + + /** + * Override the ERC20 symbol function. + */ + function symbol() public view override returns (string memory) { + return _symbol; + } + + /** + * Override the ERC20 decimals function. + */ + function decimals() public view override returns (uint8) { + return _decimals; + } +} diff --git a/src/tokens/ERC20/ERC20TokenFactory.sol b/src/tokens/ERC20/ERC20TokenFactory.sol new file mode 100644 index 0000000..cb36e84 --- /dev/null +++ b/src/tokens/ERC20/ERC20TokenFactory.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC20Token} from "./ERC20Token.sol"; +import {IERC20TokenFactory} from "./IERC20TokenFactory.sol"; +import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; + +contract ERC20TokenFactory is IERC20TokenFactory, ProxyDeployer { + address private immutable _implAddr; + + /** + * Creates an ERC-20 Token Factory. + */ + constructor() { + ERC20Token proxyImpl = new ERC20Token(); + _implAddr = address(proxyImpl); + } + + /** + * Creates an ERC-20 Token proxy. + * @param _owner The owner of the ERC-20 Token proxy + * @param _name The name of the ERC-20 Token proxy + * @param _symbol The symbol of the ERC-20 Token proxy + * @param _decimals The decimals of the ERC-20 Token proxy + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-20 Token Proxy + */ + function deploy(address _owner, string memory _name, string memory _symbol, uint8 _decimals, bytes32 _salt) + external + returns (address proxyAddr) + { + proxyAddr = _deployProxy(_implAddr, _salt); + ERC20Token(proxyAddr).initialize(_owner, _name, _symbol, _decimals); + emit ERC20TokenDeployed(proxyAddr); + return proxyAddr; + } +} diff --git a/src/tokens/ERC20/IERC20TokenFactory.sol b/src/tokens/ERC20/IERC20TokenFactory.sol new file mode 100644 index 0000000..6fc3ae8 --- /dev/null +++ b/src/tokens/ERC20/IERC20TokenFactory.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC20TokenFactory { + /** + * Event emitted when a new ERC-20 Token proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC20TokenDeployed(address proxyAddr); + + /** + * Creates an ERC-20 Token proxy. + * @param _owner The owner of the ERC-20 Token proxy + * @param _name The name of the ERC-20 Token proxy + * @param _symbol The symbol of the ERC-20 Token proxy + * @param _decimals The decimals of the ERC-20 Token proxy + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-20 Token Proxy + */ + function deploy(address _owner, string memory _name, string memory _symbol, uint8 _decimals, bytes32 _salt) + external + returns (address proxyAddr); +} diff --git a/src/tokens/ERC721/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol index 934637b..0c42a4d 100644 --- a/src/tokens/ERC721/ERC721Token.sol +++ b/src/tokens/ERC721/ERC721Token.sol @@ -146,7 +146,7 @@ contract ERC721Token is ERC721AQueryable, ERC2981, AccessControl { } /** - * Override the ERC721A name function. + * Override the ERC721A symbol function. */ function symbol() public view override (ERC721A, IERC721A) returns (string memory) { return _symbol; diff --git a/test/tokens/ERC20/ERC20Token.t.sol b/test/tokens/ERC20/ERC20Token.t.sol new file mode 100644 index 0000000..d43c662 --- /dev/null +++ b/test/tokens/ERC20/ERC20Token.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {ERC20Token, InvalidInitialization} from "src/tokens/ERC20/ERC20Token.sol"; +import {ERC20TokenFactory} from "src/tokens/ERC20/ERC20TokenFactory.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +// Interfaces +import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +contract ERC20TokenTest is Test { + // Redeclare events + event Transfer(address indexed from, address indexed to, uint256 value); + + ERC20Token private token; + + uint8 private constant DECIMALS = 18; + + address owner; + + function setUp() public { + owner = makeAddr("owner"); + + vm.deal(address(this), 100 ether); + vm.deal(owner, 100 ether); + + ERC20TokenFactory factory = new ERC20TokenFactory(); + token = ERC20Token(factory.deploy(owner, "name", "symbol", DECIMALS, 0x0)); + } + + function testReinitializeFails() public { + vm.expectRevert(InvalidInitialization.selector); + token.initialize(owner, "name", "symbol", DECIMALS); + } + + function testSupportsInterface() public { + assertTrue(token.supportsInterface(type(IERC165).interfaceId)); + assertTrue(token.supportsInterface(type(IERC20).interfaceId)); + assertTrue(token.supportsInterface(type(IERC20Metadata).interfaceId)); + } + + function testOwnerHasRoles() public { + assertTrue(token.hasRole(token.DEFAULT_ADMIN_ROLE(), owner)); + assertTrue(token.hasRole(token.MINTER_ROLE(), owner)); + } + + function testInitValues() public { + assertEq(token.name(), "name"); + assertEq(token.symbol(), "symbol"); + assertEq(token.decimals(), DECIMALS); + } + + // + // Minting + // + function testMintInvalidRole(address caller, uint256 amount) public { + vm.assume(caller != owner); + vm.assume(amount > 0); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(caller), + " is missing role ", + Strings.toHexString(uint256(token.MINTER_ROLE()), 32) + ) + ); + vm.prank(caller); + token.mint(caller, amount); + } + + function testMintOwner(uint256 amount) public { + vm.assume(amount > 0); + + vm.expectEmit(true, true, true, false, address(token)); + emit Transfer(address(0), owner, amount); + + vm.prank(owner); + token.mint(owner, amount); + + assertEq(token.balanceOf(owner), amount); + } + + function testMintWithRole(address minter, uint256 amount) public { + vm.assume(minter != owner); + vm.assume(minter != address(0)); + vm.assume(amount > 0); + // Give role + vm.startPrank(owner); + token.grantRole(token.MINTER_ROLE(), minter); + vm.stopPrank(); + + vm.expectEmit(true, true, true, false, address(token)); + emit Transfer(address(0), owner, amount); + + vm.prank(minter); + token.mint(owner, amount); + + assertEq(token.balanceOf(owner), amount); + } +} From 4250608888615aabeffb754bb7aeb50fd5cb3ae2 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 7 Jun 2023 12:48:27 +1200 Subject: [PATCH 07/49] Update build scripts --- scripts/build.ts | 6 +++++- scripts/constants.ts | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/build.ts b/scripts/build.ts index a047eee..ea61033 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -7,7 +7,11 @@ const exec = util.promisify(execNonPromise) const main = async () => { // Clean - await rmdir(BUILD_DIR, { recursive: true }) + try { + await rmdir(BUILD_DIR, { recursive: true }) + } catch (err) { + // Dir not found, ignore + } // Build with forge console.log('Building contracts') diff --git a/scripts/constants.ts b/scripts/constants.ts index 8f4826b..f8bfe34 100644 --- a/scripts/constants.ts +++ b/scripts/constants.ts @@ -1,5 +1,6 @@ export const BUILD_DIR = 'build' export const DEPLOYABLE_CONTRACT_NAMES = [ - 'ERC1155SaleFactory', - 'ERC721SaleFactory', + 'ERC20TokenFactory', + 'ERC721TokenFactory', + 'ERC1155TokenFactory', ] From 58672df473cc7f2d66a7dd4b687a77995ad9ccb3 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 7 Jun 2023 12:57:07 +1200 Subject: [PATCH 08/49] Fix linting --- .github/workflows/ci.yaml | 9 +- .solhint.json | 8 + package.json | 6 +- src/proxies/ERC1967/Proxy.sol | 2 +- src/proxies/ERC1967/ProxyDeployer.sol | 2 +- .../ERC1155/Packed/ERC1155PackedToken.sol | 2 +- src/utils/StorageSlot.sol | 8 +- yarn.lock | 395 +++++++++++++++++- 8 files changed, 415 insertions(+), 17 deletions(-) create mode 100644 .solhint.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c3f7000..3d021f0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,14 @@ jobs: node_modules */*/node_modules key: ${{ runner.os }}-lerna-${{ hashFiles('**/package.json', '**/yarn.lock') }} - - run: yarn lint:sol + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run linting + run: yarn lint:sol foundry-tests: name: Foundry tests diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..99e2121 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,8 @@ +{ + "extends": ["solhint:recommended"], + "rules": { + "compiler-version": ["error", "^0.8.17"], + "func-visibility": ["warn", { "ignoreConstructors": true }], + "reason-string": ["warn", { "maxLength": 96 }] + } +} diff --git a/package.json b/package.json index 8506b7d..cc3612d 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "build": "ts-node scripts/build.ts", "test": "forge test", "lint:init": "husky install", - "lint:sol": "forge fmt" + "lint:sol": "solhint \"./src/**/*.sol\" \"./tests/**/*.sol\"", + "format:sol": "forge fmt" }, "files": [ "src", @@ -30,12 +31,13 @@ "erc721a-upgradeable": "^4.2.3" }, "lint-staged": { - "**/*.sol": "yarn lint:sol" + "**/*.sol": "yarn lint:sol && yarn format:sol" }, "devDependencies": { "@types/node": "^20.1.0", "husky": "^8.0.3", "lint-staged": "^13.2.2", + "solhint": "^3.4.1", "ts-node": "^10.9.1", "typescript": "^5.0.4" } diff --git a/src/proxies/ERC1967/Proxy.sol b/src/proxies/ERC1967/Proxy.sol index 833e49e..a2da3ee 100644 --- a/src/proxies/ERC1967/Proxy.sol +++ b/src/proxies/ERC1967/Proxy.sol @@ -34,7 +34,7 @@ contract Proxy is IERC1967 { */ function proxy() private { address target = _getImplementation(); - assembly { + assembly { // solhint-disable-line no-inline-assembly let ptr := mload(0x40) calldatacopy(ptr, 0, calldatasize()) let result := delegatecall(gas(), target, ptr, calldatasize(), 0, 0) diff --git a/src/proxies/ERC1967/ProxyDeployer.sol b/src/proxies/ERC1967/ProxyDeployer.sol index a61f8a6..a1e9e0b 100644 --- a/src/proxies/ERC1967/ProxyDeployer.sol +++ b/src/proxies/ERC1967/ProxyDeployer.sol @@ -15,7 +15,7 @@ abstract contract ProxyDeployer is ProxyDeployerErrors { bytes memory code = _getProxyCode(implAddr); // Deploy it - assembly { + assembly { // solhint-disable-line no-inline-assembly proxyAddr := create2(0, add(code, 32), mload(code), salt) } if (proxyAddr == address(0)) { diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol index 1a41db1..0bfde31 100644 --- a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol +++ b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol @@ -32,7 +32,7 @@ contract ERC1155PackedToken is /** * Initialize contract. */ - constructor() ERC1155Metadata("", "") {} + constructor() ERC1155Metadata("", "") {} // solhint-disable-line no-empty-blocks /** * Initialize the contract. diff --git a/src/utils/StorageSlot.sol b/src/utils/StorageSlot.sol index 775a4b6..d0c0ef3 100644 --- a/src/utils/StorageSlot.sol +++ b/src/utils/StorageSlot.sol @@ -30,7 +30,7 @@ library StorageSlot { * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { - assembly { + assembly { // solhint-disable-line no-inline-assembly r.slot := slot } } @@ -39,7 +39,7 @@ library StorageSlot { * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { - assembly { + assembly { // solhint-disable-line no-inline-assembly r.slot := slot } } @@ -48,7 +48,7 @@ library StorageSlot { * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { - assembly { + assembly { // solhint-disable-line no-inline-assembly r.slot := slot } } @@ -57,7 +57,7 @@ library StorageSlot { * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { - assembly { + assembly { // solhint-disable-line no-inline-assembly r.slot := slot } } diff --git a/yarn.lock b/yarn.lock index 94dc5bf..aaa018c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,11 +11,7 @@ "@ethersproject/providers" "^5.7.2" ethers "^5.7.2" -<<<<<<< HEAD -"@0xsequence/erc-1155@^4.0.2": -======= "@0xsequence/erc-1155@^4.0.3": ->>>>>>> e4c96a7 (Base implementations) version "4.0.3" resolved "https://registry.yarnpkg.com/@0xsequence/erc-1155/-/erc-1155-4.0.3.tgz#b9b890c1ced04a84f36be22dd20424b43b9e1da2" integrity sha512-vDh4OEuq0bR3iIqhsxpuRpczH5GxwH/mjBWm9uP6VYKnKM36ZBLApSNUlIOCXYRCS0DYFxsdvYjAemb3w1l4ow== @@ -31,6 +27,27 @@ dependencies: "@0xsequence/erc-1155" "^4.0.1" +"@babel/code-frame@^7.0.0": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/helper-validator-identifier@^7.18.6": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -403,6 +420,13 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.3.tgz#cbef3146bfc570849405f59cba18235da95a252a" integrity sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg== +"@solidity-parser/parser@^0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.0.tgz#1fb418c816ca1fc3a1e94b08bcfe623ec4e1add4" + integrity sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q== + dependencies: + antlr4ts "^0.5.0-alpha.4" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -451,6 +475,26 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ajv@^6.12.6: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.1: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ansi-escapes@^4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -468,7 +512,14 @@ ansi-regex@^6.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== -ansi-styles@^4.0.0: +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -480,16 +531,41 @@ ansi-styles@^6.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== +antlr4@^4.11.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/antlr4/-/antlr4-4.13.0.tgz#25c0b17f0d9216de114303d38bafd6f181d5447f" + integrity sha512-zooUbt+UscjnWyOrsuY/tVFL4rwrAGwOivpQmvmUDE22hy/lUA467Rc1rcixyRwcRUIXFYBwv7+dClDSHdmmew== + +antlr4ts@^0.5.0-alpha.4: + version "0.5.0-alpha.4" + resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" + integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +ast-parents@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/ast-parents/-/ast-parents-0.0.1.tgz#508fd0f05d0c48775d9eccda2e174423261e8dd3" + integrity sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA== + astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + bech32@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" @@ -505,6 +581,13 @@ bn.js@^5.2.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -517,11 +600,33 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + chalk@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -550,6 +655,13 @@ cli-truncate@^3.1.0: slice-ansi "^5.0.0" string-width "^5.0.0" +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -557,6 +669,11 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" @@ -572,6 +689,16 @@ commander@^10.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== +cosmiconfig@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd" + integrity sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ== + dependencies: + import-fresh "^3.2.1" + js-yaml "^4.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -636,6 +763,18 @@ erc721a@^4.2.3: resolved "https://registry.yarnpkg.com/erc721a/-/erc721a-4.2.3.tgz#ca6469b0e54afb0f614272c2147dc4cb49ff223f" integrity sha512-0deF0hOOK1XI1Vxv3NKDh2E9sgzRlENuOoexjXRJIRfYCsLlqi9ejl2RF6Wcd9HfH0ldqC03wleQ2WDjxoOUvA== +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" @@ -687,6 +826,21 @@ execa@^7.0.0: signal-exit "^3.0.7" strip-final-newline "^3.0.0" +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -694,11 +848,37 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +glob@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -726,16 +906,42 @@ husky@^8.0.3: resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== +ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + indent-string@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -inherits@^2.0.3, inherits@^2.0.4: +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -766,11 +972,43 @@ js-sha3@0.8.0: resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + lilconfig@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + lint-staged@^13.2.2: version "13.2.2" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.2.2.tgz#5e711d3139c234f73402177be2f8dd312e6508ca" @@ -804,6 +1042,16 @@ listr2@^5.0.7: through "^2.3.8" wrap-ansi "^7.0.0" +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log-update@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" @@ -852,6 +1100,13 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -874,6 +1129,13 @@ object-inspect@^1.12.3: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + onetime@^5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" @@ -895,6 +1157,23 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" @@ -905,6 +1184,11 @@ path-key@^4.0.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -915,6 +1199,31 @@ pidtree@^0.6.0: resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +prettier@^2.8.3: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -940,6 +1249,11 @@ scrypt-js@3.0.1: resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -983,12 +1297,37 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" +solhint@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/solhint/-/solhint-3.4.1.tgz#8ea15b21c13d1be0b53fd46d605a24d0b36a0c46" + integrity sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg== + dependencies: + "@solidity-parser/parser" "^0.16.0" + ajv "^6.12.6" + antlr4 "^4.11.0" + ast-parents "^0.0.1" + chalk "^4.1.2" + commander "^10.0.0" + cosmiconfig "^8.0.0" + fast-diff "^1.2.0" + glob "^8.0.3" + ignore "^5.2.4" + js-yaml "^4.1.0" + lodash "^4.17.21" + pluralize "^8.0.0" + semver "^6.3.0" + strip-ansi "^6.0.1" + table "^6.8.1" + text-table "^0.2.0" + optionalDependencies: + prettier "^2.8.3" + string-argv@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -1025,6 +1364,36 @@ strip-final-newline@^3.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +table@^6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" + integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -1071,6 +1440,13 @@ typescript@^5.0.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -1101,6 +1477,11 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + ws@7.4.6: version "7.4.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" From e3e6139d5a15621b9e6ed1f305ed627e0c6793c1 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 7 Jun 2023 14:01:23 +1200 Subject: [PATCH 09/49] Hash salt with sender for security --- src/tokens/ERC1155/ERC1155TokenFactory.sol | 3 ++- src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol | 3 ++- src/tokens/ERC20/ERC20TokenFactory.sol | 3 ++- src/tokens/ERC721/ERC721TokenFactory.sol | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tokens/ERC1155/ERC1155TokenFactory.sol b/src/tokens/ERC1155/ERC1155TokenFactory.sol index 850f559..d597f80 100644 --- a/src/tokens/ERC1155/ERC1155TokenFactory.sol +++ b/src/tokens/ERC1155/ERC1155TokenFactory.sol @@ -23,12 +23,13 @@ contract ERC1155TokenFactory is IERC1155TokenFactory, ProxyDeployer { * @param _baseURI The base URI of the ERC-1155 Token proxy * @param _salt The deployment salt * @return proxyAddr The address of the ERC-1155 Token Proxy + * @dev The provided `_salt` is hashed with the caller address for security. */ function deploy(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, _salt); + proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, _salt))); ERC1155Token(proxyAddr).initialize(_owner, _name, _baseURI); emit ERC1155TokenDeployed(proxyAddr); return proxyAddr; diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol b/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol index f10dd9c..f0a92ae 100644 --- a/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol +++ b/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol @@ -23,12 +23,13 @@ contract ERC1155PackedTokenFactory is IERC1155PackedTokenFactory, ProxyDeployer * @param _baseURI The base URI of the ERC-1155 Packed Token proxy * @param _salt The deployment salt * @return proxyAddr The address of the ERC-1155 Packed Token Proxy + * @dev The provided `_salt` is hashed with the caller address for security. */ function deploy(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, _salt); + proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, _salt))); ERC1155PackedToken(proxyAddr).initialize(_owner, _name, _baseURI); emit ERC1155PackedTokenDeployed(proxyAddr); return proxyAddr; diff --git a/src/tokens/ERC20/ERC20TokenFactory.sol b/src/tokens/ERC20/ERC20TokenFactory.sol index cb36e84..17d6ece 100644 --- a/src/tokens/ERC20/ERC20TokenFactory.sol +++ b/src/tokens/ERC20/ERC20TokenFactory.sol @@ -24,12 +24,13 @@ contract ERC20TokenFactory is IERC20TokenFactory, ProxyDeployer { * @param _decimals The decimals of the ERC-20 Token proxy * @param _salt The deployment salt * @return proxyAddr The address of the ERC-20 Token Proxy + * @dev The provided `_salt` is hashed with the caller address for security. */ function deploy(address _owner, string memory _name, string memory _symbol, uint8 _decimals, bytes32 _salt) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, _salt); + proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, _salt))); ERC20Token(proxyAddr).initialize(_owner, _name, _symbol, _decimals); emit ERC20TokenDeployed(proxyAddr); return proxyAddr; diff --git a/src/tokens/ERC721/ERC721TokenFactory.sol b/src/tokens/ERC721/ERC721TokenFactory.sol index ff20e99..f11972f 100644 --- a/src/tokens/ERC721/ERC721TokenFactory.sol +++ b/src/tokens/ERC721/ERC721TokenFactory.sol @@ -24,12 +24,13 @@ contract ERC721TokenFactory is IERC721TokenFactory, ProxyDeployer { * @param _baseURI The base URI of the ERC-721 Token proxy * @param _salt The deployment salt * @return proxyAddr The address of the ERC-721 Token Proxy + * @dev The provided `_salt` is hashed with the caller address for security. */ function deploy(address _owner, string memory _name, string memory _symbol, string memory _baseURI, bytes32 _salt) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, _salt); + proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, _salt))); ERC721Token(proxyAddr).initialize(_owner, _name, _symbol, _baseURI); emit ERC721TokenDeployed(proxyAddr); return proxyAddr; From 2abb41484791369d79fdd9d207742d53c43aa77f Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 7 Jun 2023 14:07:47 +1200 Subject: [PATCH 10/49] Abstract ERC2981 control --- src/tokens/ERC1155/ERC1155Token.sol | 37 ++--------- .../ERC1155/Packed/ERC1155PackedToken.sol | 39 ++---------- src/tokens/ERC721/ERC721Token.sol | 39 ++---------- src/tokens/common/ERC2981Controlled.sol | 62 +++++++++++++++++++ 4 files changed, 75 insertions(+), 102 deletions(-) create mode 100644 src/tokens/common/ERC2981Controlled.sol diff --git a/src/tokens/ERC1155/ERC1155Token.sol b/src/tokens/ERC1155/ERC1155Token.sol index f81ed5c..4a7f178 100644 --- a/src/tokens/ERC1155/ERC1155Token.sol +++ b/src/tokens/ERC1155/ERC1155Token.sol @@ -4,18 +4,16 @@ pragma solidity ^0.8.17; import {ERC1155, ERC1155MintBurn} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155MintBurn.sol"; import {ERC1155Meta} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Meta.sol"; import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; -import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {ERC2981Controlled} from "../common/ERC2981Controlled.sol"; error InvalidInitialization(); /** * A ready made implementation of ERC-1155. */ -contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981, AccessControl { +contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981Controlled { bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); address private immutable _initializer; bool private _initialized; @@ -78,33 +76,6 @@ contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981, _batchMint(_to, _tokenIds, _amounts, _data); } - // - // Royalty - // - - /** - * Sets the royalty information that all ids in this contract will default to. - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - */ - function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) external onlyRole(ROYALTY_ADMIN_ROLE) { - _setDefaultRoyalty(_receiver, _feeNumerator); - } - - /** - * Sets the royalty information that a given token id in this contract will use. - * @param _tokenId The token id to set the royalty information for - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @notice This overrides the default royalty information for this token id - */ - function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) - external - onlyRole(ROYALTY_ADMIN_ROLE) - { - _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); - } - // // Metadata // @@ -137,11 +108,11 @@ contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981, function supportsInterface(bytes4 _interfaceId) public view - override (ERC1155, ERC1155Metadata, ERC2981, AccessControl) + override (ERC1155, ERC1155Metadata, ERC2981Controlled) returns (bool) { return ERC1155.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId) - || ERC2981.supportsInterface(_interfaceId) || AccessControl.supportsInterface(_interfaceId) + || ERC2981Controlled.supportsInterface(_interfaceId) || super.supportsInterface(_interfaceId); } } diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol index 0bfde31..6d0dc70 100644 --- a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol +++ b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol @@ -8,8 +8,7 @@ import { import {ERC1155MetaPackedBalance} from "@0xsequence/erc-1155/contracts/tokens/ERC1155PackedBalance/ERC1155MetaPackedBalance.sol"; import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; -import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {ERC2981Controlled} from "../../common/ERC2981Controlled.sol"; error InvalidInitialization(); @@ -19,13 +18,10 @@ error InvalidInitialization(); contract ERC1155PackedToken is ERC1155MintBurnPackedBalance, ERC1155MetaPackedBalance, - ERC1155Metadata, - ERC2981, - AccessControl + ERC1155Metadata,ERC2981Controlled { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); - bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); bool private _initialized; @@ -84,33 +80,6 @@ contract ERC1155PackedToken is _batchMint(_to, _tokenIds, _amounts, _data); } - // - // Royalty - // - - /** - * Sets the royalty information that all ids in this contract will default to. - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - */ - function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) external onlyRole(ROYALTY_ADMIN_ROLE) { - _setDefaultRoyalty(_receiver, _feeNumerator); - } - - /** - * Sets the royalty information that a given token id in this contract will use. - * @param _tokenId The token id to set the royalty information for - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @notice This overrides the default royalty information for this token id - */ - function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) - external - onlyRole(ROYALTY_ADMIN_ROLE) - { - _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); - } - // // Metadata // @@ -143,11 +112,11 @@ contract ERC1155PackedToken is function supportsInterface(bytes4 _interfaceId) public view - override (ERC1155PackedBalance, ERC1155Metadata, ERC2981, AccessControl) + override (ERC1155PackedBalance, ERC1155Metadata, ERC2981Controlled) returns (bool) { return ERC1155PackedBalance.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId) - || ERC2981.supportsInterface(_interfaceId) || AccessControl.supportsInterface(_interfaceId) + || ERC2981Controlled.supportsInterface(_interfaceId) || super.supportsInterface(_interfaceId); } } diff --git a/src/tokens/ERC721/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol index 0c42a4d..b9e310f 100644 --- a/src/tokens/ERC721/ERC721Token.sol +++ b/src/tokens/ERC721/ERC721Token.sol @@ -4,18 +4,16 @@ pragma solidity ^0.8.17; import { ERC721AQueryable, IERC721AQueryable, ERC721A, IERC721A } from "erc721a/contracts/extensions/ERC721AQueryable.sol"; -import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; -import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {ERC2981Controlled} from "../common/ERC2981Controlled.sol"; error InvalidInitialization(); /** * A ready made implementation of ERC-721. */ -contract ERC721Token is ERC721AQueryable, ERC2981, AccessControl { +contract ERC721Token is ERC721AQueryable, ERC2981Controlled { bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); string private baseURI; // Missing _ due to _baseURI() function in ERC721A string private _name; @@ -68,33 +66,6 @@ contract ERC721Token is ERC721AQueryable, ERC2981, AccessControl { _mint(_to, _amount); } - // - // Royalty - // - - /** - * Sets the royalty information that all ids in this contract will default to. - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - */ - function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) external onlyRole(ROYALTY_ADMIN_ROLE) { - _setDefaultRoyalty(_receiver, _feeNumerator); - } - - /** - * Sets the royalty information that a given token id in this contract will use. - * @param _tokenId The token id to set the royalty information for - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @notice This overrides the default royalty information for this token id - */ - function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) - external - onlyRole(ROYALTY_ADMIN_ROLE) - { - _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); - } - // // Metadata // @@ -119,12 +90,12 @@ contract ERC721Token is ERC721AQueryable, ERC2981, AccessControl { function supportsInterface(bytes4 _interfaceId) public view - override (ERC721A, IERC721A, ERC2981, AccessControl) + override (ERC721A, IERC721A, ERC2981Controlled) returns (bool) { return _interfaceId == type(IERC721A).interfaceId || _interfaceId == type(IERC721AQueryable).interfaceId - || ERC721A.supportsInterface(_interfaceId) || ERC2981.supportsInterface(_interfaceId) - || AccessControl.supportsInterface(_interfaceId) || super.supportsInterface(_interfaceId); + || ERC721A.supportsInterface(_interfaceId) || ERC2981Controlled.supportsInterface(_interfaceId) + || super.supportsInterface(_interfaceId); } // diff --git a/src/tokens/common/ERC2981Controlled.sol b/src/tokens/common/ERC2981Controlled.sol new file mode 100644 index 0000000..17f7854 --- /dev/null +++ b/src/tokens/common/ERC2981Controlled.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +/** + * An implementation of ERC-2981 that allows updates by roles. + */ +abstract contract ERC2981Controlled is + ERC2981, + AccessControl +{ + bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); + + // + // Royalty + // + + /** + * Sets the royalty information that all ids in this contract will default to. + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + */ + function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) external onlyRole(ROYALTY_ADMIN_ROLE) { + _setDefaultRoyalty(_receiver, _feeNumerator); + } + + /** + * Sets the royalty information that a given token id in this contract will use. + * @param _tokenId The token id to set the royalty information for + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @notice This overrides the default royalty information for this token id + */ + function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) + external + onlyRole(ROYALTY_ADMIN_ROLE) + { + _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + } + + // + // Views + // + + /** + * Check interface support. + * @param _interfaceId Interface id + * @return True if supported + */ + function supportsInterface(bytes4 _interfaceId) + public + view + virtual + override (ERC2981, AccessControl) + returns (bool) + { + return ERC2981.supportsInterface(_interfaceId) || AccessControl.supportsInterface(_interfaceId) + || super.supportsInterface(_interfaceId); + } +} From 304f1c53f96ab4431d0a59ee68dd02219cfada0b Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 7 Jun 2023 14:08:53 +1200 Subject: [PATCH 11/49] Keep init rules consistent --- src/tokens/ERC1155/Packed/ERC1155PackedToken.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol index 6d0dc70..f51c7d6 100644 --- a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol +++ b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol @@ -23,12 +23,15 @@ contract ERC1155PackedToken is bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); + address private immutable _initializer; bool private _initialized; /** * Initialize contract. */ - constructor() ERC1155Metadata("", "") {} // solhint-disable-line no-empty-blocks + constructor() ERC1155Metadata("", "") { + _initializer = msg.sender; + } /** * Initialize the contract. @@ -38,10 +41,11 @@ contract ERC1155PackedToken is * @dev This should be called immediately after deployment. */ function initialize(address _owner, string memory _name, string memory _baseURI) public { - if (_initialized) { + if (msg.sender != _initializer || _initialized) { revert InvalidInitialization(); } _initialized = true; + name = _name; baseURI = _baseURI; From 2bc9bfdb251e8315877195555fad055a1932cb8d Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Thu, 8 Jun 2023 13:58:03 +1200 Subject: [PATCH 12/49] Variable naming convention update --- src/proxies/ERC1967/Proxy.sol | 14 ++-- src/proxies/ERC1967/ProxyDeployer.sol | 4 +- src/tokens/ERC1155/ERC1155Token.sol | 76 +++++++++---------- src/tokens/ERC1155/ERC1155TokenFactory.sol | 20 ++--- src/tokens/ERC1155/IERC1155TokenFactory.sol | 10 +-- .../ERC1155/Packed/ERC1155PackedToken.sol | 76 +++++++++---------- .../Packed/ERC1155PackedTokenFactory.sol | 20 ++--- .../Packed/IERC1155PackedTokenFactory.sol | 10 +-- src/tokens/ERC20/ERC20Token.sol | 58 +++++++------- src/tokens/ERC20/ERC20TokenFactory.sol | 22 +++--- src/tokens/ERC20/IERC20TokenFactory.sol | 12 +-- src/tokens/ERC721/ERC721Token.sol | 64 ++++++++-------- src/tokens/ERC721/ERC721TokenFactory.sol | 22 +++--- src/tokens/ERC721/IERC721TokenFactory.sol | 12 +-- src/tokens/common/ERC2981Controlled.sol | 26 +++---- 15 files changed, 223 insertions(+), 223 deletions(-) diff --git a/src/proxies/ERC1967/Proxy.sol b/src/proxies/ERC1967/Proxy.sol index a2da3ee..1cb44d6 100644 --- a/src/proxies/ERC1967/Proxy.sol +++ b/src/proxies/ERC1967/Proxy.sol @@ -5,14 +5,14 @@ import {IERC1967} from "./IERC1967.sol"; import {StorageSlot} from "../../utils/StorageSlot.sol"; contract Proxy is IERC1967 { - bytes32 internal constant _IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); + bytes32 internal constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); /** * Initializes the contract, setting proxy implementation address. */ - constructor(address _implementation) { - _setImplementation(_implementation); - emit Upgraded(_implementation); + constructor(address implementation) { + _setImplementation(implementation); + emit Upgraded(implementation); } /** @@ -49,14 +49,14 @@ contract Proxy is IERC1967 { /** * Set the implementation address. */ - function _setImplementation(address _implementation) internal { - StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = _implementation; + function _setImplementation(address implementation) internal { + StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = implementation; } /** * Returns the address of the current implementation. */ function _getImplementation() internal view returns (address) { - return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; + return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value; } } diff --git a/src/proxies/ERC1967/ProxyDeployer.sol b/src/proxies/ERC1967/ProxyDeployer.sol index a1e9e0b..e590c4f 100644 --- a/src/proxies/ERC1967/ProxyDeployer.sol +++ b/src/proxies/ERC1967/ProxyDeployer.sol @@ -43,8 +43,8 @@ abstract contract ProxyDeployer is ProxyDeployerErrors { */ function _predictProxyAddress(bytes memory code, bytes32 salt) private view returns (address proxyAddr) { address deployer = address(this); - bytes32 _data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(code))); - return address(uint160(uint256(_data))); + bytes32 data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(code))); + return address(uint160(uint256(data))); } /** diff --git a/src/tokens/ERC1155/ERC1155Token.sol b/src/tokens/ERC1155/ERC1155Token.sol index 4a7f178..c27e085 100644 --- a/src/tokens/ERC1155/ERC1155Token.sol +++ b/src/tokens/ERC1155/ERC1155Token.sol @@ -15,36 +15,36 @@ contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981C bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - address private immutable _initializer; - bool private _initialized; + address private immutable initializer; + bool private initialized; /** * Initialize contract. */ constructor() ERC1155Metadata("", "") { - _initializer = msg.sender; + initializer = msg.sender; } /** * Initialize the contract. - * @param _owner Owner address. - * @param _name Token name. - * @param _baseURI Base URI for token metadata. + * @param owner Owner address. + * @param name_ Token name. + * @param baseURI_ Base URI for token metadata. * @dev This should be called immediately after deployment. */ - function initialize(address _owner, string memory _name, string memory _baseURI) public { - if (msg.sender != _initializer || _initialized) { + function initialize(address owner, string memory name_, string memory baseURI_) public { + if (msg.sender != initializer || initialized) { revert InvalidInitialization(); } - _initialized = true; + initialized = true; - name = _name; - baseURI = _baseURI; + name = name_; + baseURI = baseURI_; - _setupRole(DEFAULT_ADMIN_ROLE, _owner); - _setupRole(MINTER_ROLE, _owner); - _setupRole(ROYALTY_ADMIN_ROLE, _owner); - _setupRole(METADATA_ADMIN_ROLE, _owner); + _setupRole(DEFAULT_ADMIN_ROLE, owner); + _setupRole(MINTER_ROLE, owner); + _setupRole(ROYALTY_ADMIN_ROLE, owner); + _setupRole(METADATA_ADMIN_ROLE, owner); } // @@ -53,27 +53,27 @@ contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981C /** * Mint tokens. - * @param _to Address to mint tokens to. - * @param _tokenId Token ID to mint. - * @param _amount Amount of tokens to mint. - * @param _data Data to pass if receiver is contract. + * @param to Address to mint tokens to. + * @param tokenId Token ID to mint. + * @param amount Amount of tokens to mint. + * @param data Data to pass if receiver is contract. */ - function mint(address _to, uint256 _tokenId, uint256 _amount, bytes memory _data) external onlyRole(MINTER_ROLE) { - _mint(_to, _tokenId, _amount, _data); + function mint(address to, uint256 tokenId, uint256 amount, bytes memory data) external onlyRole(MINTER_ROLE) { + _mint(to, tokenId, amount, data); } /** * Mint tokens. - * @param _to Address to mint tokens to. - * @param _tokenIds Token IDs to mint. - * @param _amounts Amounts of tokens to mint. - * @param _data Data to pass if receiver is contract. + * @param to Address to mint tokens to. + * @param tokenIds Token IDs to mint. + * @param amounts Amounts of tokens to mint. + * @param data Data to pass if receiver is contract. */ - function batchMint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + function batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data) external onlyRole(MINTER_ROLE) { - _batchMint(_to, _tokenIds, _amounts, _data); + _batchMint(to, tokenIds, amounts, data); } // @@ -82,18 +82,18 @@ contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981C /** * Update the base URL of token's URI. - * @param _baseMetadataURI New base URL of token's URI + * @param baseURI_ New base URL of token's URI */ - function setBaseMetadataURI(string memory _baseMetadataURI) external onlyRole(METADATA_ADMIN_ROLE) { - _setBaseMetadataURI(_baseMetadataURI); + function setBaseMetadataURI(string memory baseURI_) external onlyRole(METADATA_ADMIN_ROLE) { + _setBaseMetadataURI(baseURI_); } /** * Update the name of the contract. - * @param _name New contract name + * @param name_ New contract name */ - function setContractName(string memory _name) external onlyRole(METADATA_ADMIN_ROLE) { - _setContractName(_name); + function setContractName(string memory name_) external onlyRole(METADATA_ADMIN_ROLE) { + _setContractName(name_); } // @@ -102,17 +102,17 @@ contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981C /** * Check interface support. - * @param _interfaceId Interface id + * @param interfaceId Interface id * @return True if supported */ - function supportsInterface(bytes4 _interfaceId) + function supportsInterface(bytes4 interfaceId) public view override (ERC1155, ERC1155Metadata, ERC2981Controlled) returns (bool) { - return ERC1155.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId) - || ERC2981Controlled.supportsInterface(_interfaceId) - || super.supportsInterface(_interfaceId); + return ERC1155.supportsInterface(interfaceId) || ERC1155Metadata.supportsInterface(interfaceId) + || ERC2981Controlled.supportsInterface(interfaceId) + || super.supportsInterface(interfaceId); } } diff --git a/src/tokens/ERC1155/ERC1155TokenFactory.sol b/src/tokens/ERC1155/ERC1155TokenFactory.sol index d597f80..3c54a80 100644 --- a/src/tokens/ERC1155/ERC1155TokenFactory.sol +++ b/src/tokens/ERC1155/ERC1155TokenFactory.sol @@ -6,31 +6,31 @@ import {IERC1155TokenFactory} from "./IERC1155TokenFactory.sol"; import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; contract ERC1155TokenFactory is IERC1155TokenFactory, ProxyDeployer { - address private immutable _implAddr; + address private immutable implAddr; /** * Creates an ERC-1155 Token Factory. */ constructor() { ERC1155Token proxyImpl = new ERC1155Token(); - _implAddr = address(proxyImpl); + implAddr = address(proxyImpl); } /** * Creates an ERC-1155 Token proxy. - * @param _owner The owner of the ERC-1155 Token proxy - * @param _name The name of the ERC-1155 Token proxy - * @param _baseURI The base URI of the ERC-1155 Token proxy - * @param _salt The deployment salt + * @param owner The owner of the ERC-1155 Token proxy + * @param name The name of the ERC-1155 Token proxy + * @param baseURI The base URI of the ERC-1155 Token proxy + * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Token Proxy - * @dev The provided `_salt` is hashed with the caller address for security. + * @dev The provided `salt` is hashed with the caller address for security. */ - function deploy(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, _salt))); - ERC1155Token(proxyAddr).initialize(_owner, _name, _baseURI); + proxyAddr = _deployProxy(implAddr, keccak256(abi.encode(msg.sender, salt))); + ERC1155Token(proxyAddr).initialize(owner, name, baseURI); emit ERC1155TokenDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC1155/IERC1155TokenFactory.sol b/src/tokens/ERC1155/IERC1155TokenFactory.sol index 23b79a0..a266742 100644 --- a/src/tokens/ERC1155/IERC1155TokenFactory.sol +++ b/src/tokens/ERC1155/IERC1155TokenFactory.sol @@ -10,13 +10,13 @@ interface IERC1155TokenFactory { /** * Creates an ERC-1155 Token proxy. - * @param _owner The owner of the ERC-1155 Token proxy - * @param _name The name of the ERC-1155 Token proxy - * @param _baseURI The base URI of the ERC-1155 Token proxy - * @param _salt The deployment salt + * @param owner The owner of the ERC-1155 Token proxy + * @param name The name of the ERC-1155 Token proxy + * @param baseURI The base URI of the ERC-1155 Token proxy + * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Token Proxy */ - function deploy(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) external returns (address proxyAddr); } diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol index f51c7d6..71b91d3 100644 --- a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol +++ b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol @@ -23,36 +23,36 @@ contract ERC1155PackedToken is bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); - address private immutable _initializer; - bool private _initialized; + address private immutable initializer; + bool private initialized; /** * Initialize contract. */ constructor() ERC1155Metadata("", "") { - _initializer = msg.sender; + initializer = msg.sender; } /** * Initialize the contract. - * @param _owner Owner address. - * @param _name Token name. - * @param _baseURI Base URI for token metadata. + * @param owner Owner address. + * @param name_ Token name. + * @param baseURI_ Base URI for token metadata. * @dev This should be called immediately after deployment. */ - function initialize(address _owner, string memory _name, string memory _baseURI) public { - if (msg.sender != _initializer || _initialized) { + function initialize(address owner, string memory name_, string memory baseURI_) public { + if (msg.sender != initializer || initialized) { revert InvalidInitialization(); } - _initialized = true; + initialized = true; - name = _name; - baseURI = _baseURI; + name = name_; + baseURI = baseURI_; - _setupRole(DEFAULT_ADMIN_ROLE, _owner); - _setupRole(MINTER_ROLE, _owner); - _setupRole(ROYALTY_ADMIN_ROLE, _owner); - _setupRole(METADATA_ADMIN_ROLE, _owner); + _setupRole(DEFAULT_ADMIN_ROLE, owner); + _setupRole(MINTER_ROLE, owner); + _setupRole(ROYALTY_ADMIN_ROLE, owner); + _setupRole(METADATA_ADMIN_ROLE, owner); } // @@ -61,27 +61,27 @@ contract ERC1155PackedToken is /** * Mint tokens. - * @param _to Address to mint tokens to. - * @param _tokenId Token ID to mint. - * @param _amount Amount of tokens to mint. - * @param _data Data to pass if receiver is contract. + * @param to Address to mint tokens to. + * @param tokenId Token ID to mint. + * @param amount Amount of tokens to mint. + * @param data Data to pass if receiver is contract. */ - function mint(address _to, uint256 _tokenId, uint256 _amount, bytes memory _data) external onlyRole(MINTER_ROLE) { - _mint(_to, _tokenId, _amount, _data); + function mint(address to, uint256 tokenId, uint256 amount, bytes memory data) external onlyRole(MINTER_ROLE) { + _mint(to, tokenId, amount, data); } /** * Mint tokens. - * @param _to Address to mint tokens to. - * @param _tokenIds Token IDs to mint. - * @param _amounts Amounts of tokens to mint. - * @param _data Data to pass if receiver is contract. + * @param to Address to mint tokens to. + * @param tokenIds Token IDs to mint. + * @param amounts Amounts of tokens to mint. + * @param data Data to pass if receiver is contract. */ - function batchMint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + function batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data) external onlyRole(MINTER_ROLE) { - _batchMint(_to, _tokenIds, _amounts, _data); + _batchMint(to, tokenIds, amounts, data); } // @@ -90,18 +90,18 @@ contract ERC1155PackedToken is /** * Update the base URL of token's URI. - * @param _baseMetadataURI New base URL of token's URI + * @param baseURI_ New base URL of token's URI */ - function setBaseMetadataURI(string memory _baseMetadataURI) external onlyRole(METADATA_ADMIN_ROLE) { - _setBaseMetadataURI(_baseMetadataURI); + function setBaseMetadataURI(string memory baseURI_) external onlyRole(METADATA_ADMIN_ROLE) { + _setBaseMetadataURI(baseURI_); } /** * Update the name of the contract. - * @param _name New contract name + * @param name_ New contract name */ - function setContractName(string memory _name) external onlyRole(METADATA_ADMIN_ROLE) { - _setContractName(_name); + function setContractName(string memory name_) external onlyRole(METADATA_ADMIN_ROLE) { + _setContractName(name_); } // @@ -110,17 +110,17 @@ contract ERC1155PackedToken is /** * Check interface support. - * @param _interfaceId Interface id + * @param interfaceId Interface id * @return True if supported */ - function supportsInterface(bytes4 _interfaceId) + function supportsInterface(bytes4 interfaceId) public view override (ERC1155PackedBalance, ERC1155Metadata, ERC2981Controlled) returns (bool) { - return ERC1155PackedBalance.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId) - || ERC2981Controlled.supportsInterface(_interfaceId) - || super.supportsInterface(_interfaceId); + return ERC1155PackedBalance.supportsInterface(interfaceId) || ERC1155Metadata.supportsInterface(interfaceId) + || ERC2981Controlled.supportsInterface(interfaceId) + || super.supportsInterface(interfaceId); } } diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol b/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol index f0a92ae..9f483f7 100644 --- a/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol +++ b/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol @@ -6,31 +6,31 @@ import {IERC1155PackedTokenFactory} from "./IERC1155PackedTokenFactory.sol"; import {ProxyDeployer} from "../../../proxies/ERC1967/ProxyDeployer.sol"; contract ERC1155PackedTokenFactory is IERC1155PackedTokenFactory, ProxyDeployer { - address private immutable _implAddr; + address private immutable implAddr; /** * Creates an ERC-1155 Token Factory. */ constructor() { ERC1155PackedToken proxyImpl = new ERC1155PackedToken(); - _implAddr = address(proxyImpl); + implAddr = address(proxyImpl); } /** * Creates an ERC-1155 Packed Token proxy. - * @param _owner The owner of the ERC-1155 Packed Token proxy - * @param _name The name of the ERC-1155 Packed Token proxy - * @param _baseURI The base URI of the ERC-1155 Packed Token proxy - * @param _salt The deployment salt + * @param owner The owner of the ERC-1155 Packed Token proxy + * @param name The name of the ERC-1155 Packed Token proxy + * @param baseURI The base URI of the ERC-1155 Packed Token proxy + * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Packed Token Proxy - * @dev The provided `_salt` is hashed with the caller address for security. + * @dev The provided `salt` is hashed with the caller address for security. */ - function deploy(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, _salt))); - ERC1155PackedToken(proxyAddr).initialize(_owner, _name, _baseURI); + proxyAddr = _deployProxy(implAddr, keccak256(abi.encode(msg.sender, salt))); + ERC1155PackedToken(proxyAddr).initialize(owner, name, baseURI); emit ERC1155PackedTokenDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol b/src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol index e4f7ef1..27ab133 100644 --- a/src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol +++ b/src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol @@ -10,13 +10,13 @@ interface IERC1155PackedTokenFactory { /** * Creates an ERC-1155 Packed Token proxy. - * @param _owner The owner of the ERC-1155 Packed Token proxy - * @param _name The name of the ERC-1155 Packed Token proxy - * @param _baseURI The base URI of the ERC-1155 Packed Token proxy - * @param _salt The deployment salt + * @param owner The owner of the ERC-1155 Packed Token proxy + * @param name The name of the ERC-1155 Packed Token proxy + * @param baseURI The base URI of the ERC-1155 Packed Token proxy + * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Packed Token Proxy */ - function deploy(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) external returns (address proxyAddr); } diff --git a/src/tokens/ERC20/ERC20Token.sol b/src/tokens/ERC20/ERC20Token.sol index a287fff..308579f 100644 --- a/src/tokens/ERC20/ERC20Token.sol +++ b/src/tokens/ERC20/ERC20Token.sol @@ -14,40 +14,40 @@ error InvalidInitialization(); contract ERC20Token is ERC20, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - string private _name; - string private _symbol; - uint8 private _decimals; + string private tokenName; + string private tokenSymbol; + uint8 private tokenDecimals; - address private immutable _initializer; - bool private _initialized; + address private immutable initializer; + bool private initialized; /** * Deploy contract. */ constructor() ERC20("", "") { - _initializer = msg.sender; + initializer = msg.sender; } /** * Initialize contract. - * @param owner_ The owner of the contract - * @param name_ Name of the token - * @param symbol_ Symbol of the token - * @param decimals_ Number of decimals + * @param owner The owner of the contract + * @param tokenName_ Name of the token + * @param tokenSymbol_ Symbol of the token + * @param tokenDecimals_ Number of decimals * @dev This should be called immediately after deployment. */ - function initialize(address owner_, string memory name_, string memory symbol_, uint8 decimals_) external { - if (msg.sender != _initializer || _initialized) { + function initialize(address owner, string memory tokenName_, string memory tokenSymbol_, uint8 tokenDecimals_) external { + if (msg.sender != initializer || initialized) { revert InvalidInitialization(); } - _initialized = true; + initialized = true; - _name = name_; - _symbol = symbol_; - _decimals = decimals_; + tokenName = tokenName_; + tokenSymbol = tokenSymbol_; + tokenDecimals = tokenDecimals_; - _setupRole(DEFAULT_ADMIN_ROLE, owner_); - _setupRole(MINTER_ROLE, owner_); + _setupRole(DEFAULT_ADMIN_ROLE, owner); + _setupRole(MINTER_ROLE, owner); } // @@ -56,11 +56,11 @@ contract ERC20Token is ERC20, AccessControl { /** * Mint tokens. - * @param _to Address to mint tokens to. - * @param _amount Amount of tokens to mint. + * @param to Address to mint tokens to. + * @param amount Amount of tokens to mint. */ - function mint(address _to, uint256 _amount) external onlyRole(MINTER_ROLE) { - _mint(_to, _amount); + function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { + _mint(to, amount); } // @@ -69,12 +69,12 @@ contract ERC20Token is ERC20, AccessControl { /** * Check interface support. - * @param _interfaceId Interface id + * @param interfaceId Interface id * @return True if supported */ - function supportsInterface(bytes4 _interfaceId) public view override returns (bool) { - return _interfaceId == type(IERC20).interfaceId || _interfaceId == type(IERC20Metadata).interfaceId - || AccessControl.supportsInterface(_interfaceId) || super.supportsInterface(_interfaceId); + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return interfaceId == type(IERC20).interfaceId || interfaceId == type(IERC20Metadata).interfaceId + || AccessControl.supportsInterface(interfaceId) || super.supportsInterface(interfaceId); } // @@ -85,20 +85,20 @@ contract ERC20Token is ERC20, AccessControl { * Override the ERC20 name function. */ function name() public view override returns (string memory) { - return _name; + return tokenName; } /** * Override the ERC20 symbol function. */ function symbol() public view override returns (string memory) { - return _symbol; + return tokenSymbol; } /** * Override the ERC20 decimals function. */ function decimals() public view override returns (uint8) { - return _decimals; + return tokenDecimals; } } diff --git a/src/tokens/ERC20/ERC20TokenFactory.sol b/src/tokens/ERC20/ERC20TokenFactory.sol index 17d6ece..716cff5 100644 --- a/src/tokens/ERC20/ERC20TokenFactory.sol +++ b/src/tokens/ERC20/ERC20TokenFactory.sol @@ -6,32 +6,32 @@ import {IERC20TokenFactory} from "./IERC20TokenFactory.sol"; import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; contract ERC20TokenFactory is IERC20TokenFactory, ProxyDeployer { - address private immutable _implAddr; + address private immutable implAddr; /** * Creates an ERC-20 Token Factory. */ constructor() { ERC20Token proxyImpl = new ERC20Token(); - _implAddr = address(proxyImpl); + implAddr = address(proxyImpl); } /** * Creates an ERC-20 Token proxy. - * @param _owner The owner of the ERC-20 Token proxy - * @param _name The name of the ERC-20 Token proxy - * @param _symbol The symbol of the ERC-20 Token proxy - * @param _decimals The decimals of the ERC-20 Token proxy - * @param _salt The deployment salt + * @param owner The owner of the ERC-20 Token proxy + * @param name The name of the ERC-20 Token proxy + * @param symbol The symbol of the ERC-20 Token proxy + * @param decimals The decimals of the ERC-20 Token proxy + * @param salt The deployment salt * @return proxyAddr The address of the ERC-20 Token Proxy - * @dev The provided `_salt` is hashed with the caller address for security. + * @dev The provided `salt` is hashed with the caller address for security. */ - function deploy(address _owner, string memory _name, string memory _symbol, uint8 _decimals, bytes32 _salt) + function deploy(address owner, string memory name, string memory symbol, uint8 decimals, bytes32 salt) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, _salt))); - ERC20Token(proxyAddr).initialize(_owner, _name, _symbol, _decimals); + proxyAddr = _deployProxy(implAddr, keccak256(abi.encode(msg.sender, salt))); + ERC20Token(proxyAddr).initialize(owner, name, symbol, decimals); emit ERC20TokenDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC20/IERC20TokenFactory.sol b/src/tokens/ERC20/IERC20TokenFactory.sol index 6fc3ae8..4594e11 100644 --- a/src/tokens/ERC20/IERC20TokenFactory.sol +++ b/src/tokens/ERC20/IERC20TokenFactory.sol @@ -10,14 +10,14 @@ interface IERC20TokenFactory { /** * Creates an ERC-20 Token proxy. - * @param _owner The owner of the ERC-20 Token proxy - * @param _name The name of the ERC-20 Token proxy - * @param _symbol The symbol of the ERC-20 Token proxy - * @param _decimals The decimals of the ERC-20 Token proxy - * @param _salt The deployment salt + * @param owner The owner of the ERC-20 Token proxy + * @param name The name of the ERC-20 Token proxy + * @param symbol The symbol of the ERC-20 Token proxy + * @param decimals The decimals of the ERC-20 Token proxy + * @param salt The deployment salt * @return proxyAddr The address of the ERC-20 Token Proxy */ - function deploy(address _owner, string memory _name, string memory _symbol, uint8 _decimals, bytes32 _salt) + function deploy(address owner, string memory name, string memory symbol, uint8 decimals, bytes32 salt) external returns (address proxyAddr); } diff --git a/src/tokens/ERC721/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol index b9e310f..91ab807 100644 --- a/src/tokens/ERC721/ERC721Token.sol +++ b/src/tokens/ERC721/ERC721Token.sol @@ -15,42 +15,42 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - string private baseURI; // Missing _ due to _baseURI() function in ERC721A - string private _name; - string private _symbol; + string private baseURI; + string private tokenName; + string private tokenSymbol; - address private immutable _initializer; - bool private _initialized; + address private immutable initializer; + bool private initialized; /** * Deploy contract. */ constructor() ERC721A("", "") { - _initializer = msg.sender; + initializer = msg.sender; } /** * Initialize contract. - * @param owner_ The owner of the contract - * @param name_ Name of the token - * @param symbol_ Symbol of the token + * @param owner The owner of the contract + * @param tokenName_ Name of the token + * @param tokenSymbol_ Symbol of the token * @param baseURI_ Base URI of the token * @dev This should be called immediately after deployment. */ - function initialize(address owner_, string memory name_, string memory symbol_, string memory baseURI_) external { - if (msg.sender != _initializer || _initialized) { + function initialize(address owner, string memory tokenName_, string memory tokenSymbol_, string memory baseURI_) external { + if (msg.sender != initializer || initialized) { revert InvalidInitialization(); } - _initialized = true; + initialized = true; - _name = name_; - _symbol = symbol_; + tokenName = tokenName_; + tokenSymbol = tokenSymbol_; baseURI = baseURI_; - _setupRole(DEFAULT_ADMIN_ROLE, owner_); - _setupRole(METADATA_ADMIN_ROLE, owner_); - _setupRole(MINTER_ROLE, owner_); - _setupRole(ROYALTY_ADMIN_ROLE, owner_); + _setupRole(DEFAULT_ADMIN_ROLE, owner); + _setupRole(METADATA_ADMIN_ROLE, owner); + _setupRole(MINTER_ROLE, owner); + _setupRole(ROYALTY_ADMIN_ROLE, owner); } // @@ -59,11 +59,11 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { /** * Mint tokens. - * @param _to Address to mint tokens to. - * @param _amount Amount of tokens to mint. + * @param to Address to mint tokens to. + * @param amount Amount of tokens to mint. */ - function mint(address _to, uint256 _amount) external onlyRole(MINTER_ROLE) { - _mint(_to, _amount); + function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { + _mint(to, amount); } // @@ -72,10 +72,10 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { /** * Update the base URL of token's URI. - * @param _baseMetadataURI New base URL of token's URI + * @param baseURI_ New base URL of token's URI */ - function setBaseMetadataURI(string memory _baseMetadataURI) external onlyRole(METADATA_ADMIN_ROLE) { - baseURI = _baseMetadataURI; + function setBaseMetadataURI(string memory baseURI_) external onlyRole(METADATA_ADMIN_ROLE) { + baseURI = baseURI_; } // @@ -84,18 +84,18 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { /** * Check interface support. - * @param _interfaceId Interface id + * @param interfaceId Interface id * @return True if supported */ - function supportsInterface(bytes4 _interfaceId) + function supportsInterface(bytes4 interfaceId) public view override (ERC721A, IERC721A, ERC2981Controlled) returns (bool) { - return _interfaceId == type(IERC721A).interfaceId || _interfaceId == type(IERC721AQueryable).interfaceId - || ERC721A.supportsInterface(_interfaceId) || ERC2981Controlled.supportsInterface(_interfaceId) - || super.supportsInterface(_interfaceId); + return interfaceId == type(IERC721A).interfaceId || interfaceId == type(IERC721AQueryable).interfaceId + || ERC721A.supportsInterface(interfaceId) || ERC2981Controlled.supportsInterface(interfaceId) + || super.supportsInterface(interfaceId); } // @@ -113,13 +113,13 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { * Override the ERC721A name function. */ function name() public view override (ERC721A, IERC721A) returns (string memory) { - return _name; + return tokenName; } /** * Override the ERC721A symbol function. */ function symbol() public view override (ERC721A, IERC721A) returns (string memory) { - return _symbol; + return tokenSymbol; } } diff --git a/src/tokens/ERC721/ERC721TokenFactory.sol b/src/tokens/ERC721/ERC721TokenFactory.sol index f11972f..f2cdd7b 100644 --- a/src/tokens/ERC721/ERC721TokenFactory.sol +++ b/src/tokens/ERC721/ERC721TokenFactory.sol @@ -6,32 +6,32 @@ import {IERC721TokenFactory} from "./IERC721TokenFactory.sol"; import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; contract ERC721TokenFactory is IERC721TokenFactory, ProxyDeployer { - address private immutable _implAddr; + address private immutable implAddr; /** * Creates an ERC-721 Token Factory. */ constructor() { ERC721Token proxyImpl = new ERC721Token(); - _implAddr = address(proxyImpl); + implAddr = address(proxyImpl); } /** * Creates an ERC-721 Token proxy. - * @param _owner The owner of the ERC-721 Token proxy - * @param _name The name of the ERC-721 Token proxy - * @param _symbol The symbol of the ERC-721 Token proxy - * @param _baseURI The base URI of the ERC-721 Token proxy - * @param _salt The deployment salt + * @param owner The owner of the ERC-721 Token proxy + * @param name The name of the ERC-721 Token proxy + * @param symbol The symbol of the ERC-721 Token proxy + * @param baseURI The base URI of the ERC-721 Token proxy + * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Token Proxy - * @dev The provided `_salt` is hashed with the caller address for security. + * @dev The provided `salt` is hashed with the caller address for security. */ - function deploy(address _owner, string memory _name, string memory _symbol, string memory _baseURI, bytes32 _salt) + function deploy(address owner, string memory name, string memory symbol, string memory baseURI, bytes32 salt) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, _salt))); - ERC721Token(proxyAddr).initialize(_owner, _name, _symbol, _baseURI); + proxyAddr = _deployProxy(implAddr, keccak256(abi.encode(msg.sender, salt))); + ERC721Token(proxyAddr).initialize(owner, name, symbol, baseURI); emit ERC721TokenDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC721/IERC721TokenFactory.sol b/src/tokens/ERC721/IERC721TokenFactory.sol index ed78642..3c44107 100644 --- a/src/tokens/ERC721/IERC721TokenFactory.sol +++ b/src/tokens/ERC721/IERC721TokenFactory.sol @@ -10,14 +10,14 @@ interface IERC721TokenFactory { /** * Creates an ERC-721 Token proxy. - * @param _owner The owner of the ERC-721 Token proxy - * @param _name The name of the ERC-721 Token proxy - * @param _symbol The symbol of the ERC-721 Token proxy - * @param _baseURI The base URI of the ERC-721 Token proxy - * @param _salt The deployment salt + * @param owner The owner of the ERC-721 Token proxy + * @param name The name of the ERC-721 Token proxy + * @param symbol The symbol of the ERC-721 Token proxy + * @param baseURI The base URI of the ERC-721 Token proxy + * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Token Proxy */ - function deploy(address _owner, string memory _name, string memory _symbol, string memory _baseURI, bytes32 _salt) + function deploy(address owner, string memory name, string memory symbol, string memory baseURI, bytes32 salt) external returns (address proxyAddr); } diff --git a/src/tokens/common/ERC2981Controlled.sol b/src/tokens/common/ERC2981Controlled.sol index 17f7854..9204258 100644 --- a/src/tokens/common/ERC2981Controlled.sol +++ b/src/tokens/common/ERC2981Controlled.sol @@ -19,25 +19,25 @@ abstract contract ERC2981Controlled is /** * Sets the royalty information that all ids in this contract will default to. - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @param receiver Address of who should be sent the royalty payment + * @param feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) */ - function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) external onlyRole(ROYALTY_ADMIN_ROLE) { - _setDefaultRoyalty(_receiver, _feeNumerator); + function setDefaultRoyalty(address receiver, uint96 feeNumerator) external onlyRole(ROYALTY_ADMIN_ROLE) { + _setDefaultRoyalty(receiver, feeNumerator); } /** * Sets the royalty information that a given token id in this contract will use. - * @param _tokenId The token id to set the royalty information for - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @param tokenId The token id to set the royalty information for + * @param receiver Address of who should be sent the royalty payment + * @param feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @notice This overrides the default royalty information for this token id */ - function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) + function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) external onlyRole(ROYALTY_ADMIN_ROLE) { - _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + _setTokenRoyalty(tokenId, receiver, feeNumerator); } // @@ -46,17 +46,17 @@ abstract contract ERC2981Controlled is /** * Check interface support. - * @param _interfaceId Interface id + * @param interfaceId Interface id * @return True if supported */ - function supportsInterface(bytes4 _interfaceId) + function supportsInterface(bytes4 interfaceId) public view virtual override (ERC2981, AccessControl) returns (bool) { - return ERC2981.supportsInterface(_interfaceId) || AccessControl.supportsInterface(_interfaceId) - || super.supportsInterface(_interfaceId); + return ERC2981.supportsInterface(interfaceId) || AccessControl.supportsInterface(interfaceId) + || super.supportsInterface(interfaceId); } } From 194ec581128adfd40cef6494bdb58581d54ba7c3 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Fri, 9 Jun 2023 09:50:16 +1200 Subject: [PATCH 13/49] Add READMEs --- README.md | 39 ++++++++++++++++++++++++++- src/proxies/ERC1967/README.md | 27 +++++++++++++++++++ src/tokens/ERC1155/Packed/README.md | 41 +++++++++++++++++++++++++++++ src/tokens/ERC1155/README.md | 41 +++++++++++++++++++++++++++++ src/tokens/ERC20/README.md | 36 +++++++++++++++++++++++++ src/tokens/ERC721/README.md | 39 +++++++++++++++++++++++++++ src/tokens/common/README.md | 32 ++++++++++++++++++++++ 7 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 src/proxies/ERC1967/README.md create mode 100644 src/tokens/ERC1155/Packed/README.md create mode 100644 src/tokens/ERC1155/README.md create mode 100644 src/tokens/ERC20/README.md create mode 100644 src/tokens/ERC721/README.md create mode 100644 src/tokens/common/README.md diff --git a/README.md b/README.md index dfce5ec..3f10a92 100644 --- a/README.md +++ b/README.md @@ -1 +1,38 @@ -# contracts-library \ No newline at end of file +# Sequence Contracts Library + +This repository provides a set of smart contracts to facilitate the creation and management of contracts deployable on EVM compatible chains, including ERC20, ERC721, and ERC1155 token standards. These contracts are designed for gas efficiency and reuse via proxy deployments. + +## Features + +* **ERC20TokenFactory**: Allows for the easy creation of new ERC20 tokens through a factory contract and also provides functionality for minting new tokens. + +* **ERC721TokenFactory**: Similar to the ERC20TokenFactory, but for ERC721 (non-fungible) tokens. It allows for the creation and minting of ERC721 tokens, and also supports ERC2981 royalty information. + +* **ERC1155TokenFactory**: A factory for creating ERC1155 tokens, which can represent semi-fungible items. This contract also supports minting and updating metadata, as well as ERC2981 royalty information. + +* **Common Token Functionality**: This contains contracts that can be used for additional functionalities, such as the `ERC2981Controlled` contract which provides a way to handle royalties in NFTs. + +* **Proxies**: This section contains contracts implementing ERC1967 compliant proxies for upgradeability. + +## Usage + +1. Clone the repository +2. Install dependencies with `yarn` +3. Compile the contracts with `yarn build` +4. Run tests with `yarn test` + +### Deployment + +```sh +yarn deploy +``` + +**Note:** The Factory contracts in this repository contain no state and are not ownable, as such they only need to be deployed once per network. The Factory contracts are then available to be used by anyone. + +## Dependencies + +The contracts in this repository are built with Solidity ^0.8.17 and use OpenZeppelin and Azuki contracts for standards implementation and additional functionalities such as access control. + +## License + +All contracts in this repository are released under the Apache-2.0 license. diff --git a/src/proxies/ERC1967/README.md b/src/proxies/ERC1967/README.md new file mode 100644 index 0000000..378ff8c --- /dev/null +++ b/src/proxies/ERC1967/README.md @@ -0,0 +1,27 @@ +# ERC1967 Proxies + +This subsection of the repository contains the implementation of [ERC1967 proxy contracts](https://eips.ethereum.org/EIPS/eip-1967). ERC1967 defines a standard for storage slots of upgradeable smart contract proxies. These proxies delegate calls to an implementation contract, which allows the logic of the contract to be upgraded without changing the address of the contract. + +## Features + +* **IERC1967**: This interface defines the standard events emitted by ERC1967 proxies - `Upgraded`, `AdminChanged`, and `BeaconUpgraded`. + +* **Proxy**: This is the core contract that acts as a ERC1967 proxy. It contains logic to forward calls to an implementation contract, allowing the contract to change its behavior over time without changing its address. + +* **ProxyDeployer**: This contract provides a helper function for deploying new proxies. It contains the logic to compute the address of a proxy before it is deployed, as well as a function to check if an address is a contract. + +**Note:** The current implementations do not support upgradeable proxies. + +## Usage + +To use the contracts in this section, import the desired contracts from the "proxies/ERC1967" directory and use the provided functions to deploy and interact with proxy contracts. For example: + +```solidity +import {ProxyDeployer} from "./proxies/ERC1967/ProxyDeployer.sol"; + +contract MyContractFactory is ProxyDeployer { + function deployNewContract(address implementation) public returns (address) { + return _deployProxy(implementation, keccak256(abi.encode(msg.sender))); + } +} +``` diff --git a/src/tokens/ERC1155/Packed/README.md b/src/tokens/ERC1155/Packed/README.md new file mode 100644 index 0000000..670f535 --- /dev/null +++ b/src/tokens/ERC1155/Packed/README.md @@ -0,0 +1,41 @@ +# ERC1155 Packed Contracts + +This subsection contains contracts related to the [ERC1155 token standard](https://eips.ethereum.org/EIPS/eip-1155). The implementation utilises the [0xSequence Packed Balance implementation](https://github.com/0xsequence/erc-1155/blob/master/SPECIFICATIONS.md#packed-balance) for gas efficiency. + +## ERC1155PackedToken + +This contract is a complete, ready-to-use implementation of the ERC-1155 token standard. It includes additional features from the ERC1155MintBurn, ERC1155Meta, and ERC1155Metadata contracts. These contracts provide minting capabilities, support for meta transactions, and metadata functionality. + +Meta transactions are provided by the [0xSequence ERC1155 library](https://github.com/0xsequence/erc-1155/blob/master/SPECIFICATIONS.md#meta-transactions). Please refer to library documentation for more information on meta transactions. + +The ERC1155PackedToken contract has a two-step deployment process. First, it's deployed with an empty constructor. After deployment, the `initialize` function must be called to set the owner, name, and base URI. This process is in place to support proxy deployments with the ERC1155PackedTokenFactory. + +### Functions + +* `initialize(address owner, string memory name_, string memory baseURI_)`: Initializes the token contract, setting the owner, name, and base URI. +* `mint(address to, uint256 tokenId, uint256 amount, bytes memory data)`: Mints the specified amount of tokens of a given ID to the specified address. This function is restricted to addresses with the Minter role. +* `batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data)`: Mints specified amounts of tokens of given IDs to the specified address. This function is restricted to addresses with the Minter role. +* `setBaseMetadataURI(string memory baseURI_)`: Updates the base URI for the token metadata. This function is restricted to addresses with the Metadata Admin role. +* `setContractName(string memory name_)`: Updates the contract's name. This function is restricted to addresses with the Metadata Admin role. + +## ERC1155PackedTokenFactory + +This contract deploys ERC1155PackedToken contracts. It uses a proxy pattern to create new token instances to reduce gas costs. + +The deployment uses a `salt` which is combined with the caller's address for cross chain consistency and security. + +### Functions + +* `deploy(address owner, string memory name, string memory baseURI, bytes32 salt)`: Deploys a new ERC1155PackedToken proxy contract, initializes it, and emits an ERC1155PackedTokenDeployed event. + +## Usage + +To create a new ERC1155 token: + +1. Deploy the ERC1155PackedTokenFactory contract (or use an existing deployment). +2. Call the `deploy` function on the factory, providing the desired parameters. +3. A new ERC1155PackedToken contract will be created and initialized, ready for use. + +## Dependencies + +This repository relies on the ERC1155, ERC1155MintBurn, ERC1155Meta, ERC1155Metadata contracts from 0xSequence for core ERC-1155 functionality, AccessControl from OpenZeppelin for role base permissions and the ERC2981Controlled contract for handling of royalties. diff --git a/src/tokens/ERC1155/README.md b/src/tokens/ERC1155/README.md new file mode 100644 index 0000000..e1ba95c --- /dev/null +++ b/src/tokens/ERC1155/README.md @@ -0,0 +1,41 @@ +# ERC1155 Contracts + +This subsection contains contracts related to the [ERC1155 token standard](https://eips.ethereum.org/EIPS/eip-1155). + +## ERC1155Token + +This contract is a complete, ready-to-use implementation of the ERC-1155 token standard. It includes additional features from the ERC1155MintBurn, ERC1155Meta, and ERC1155Metadata contracts. These contracts provide minting capabilities, support for meta transactions, and metadata functionality. + +Meta transactions are provided by the [0xSequence ERC1155 library](https://github.com/0xsequence/erc-1155/blob/master/SPECIFICATIONS.md#meta-transactions). Please refer to library documentation for more information on meta transactions. + +The ERC1155Token contract has a two-step deployment process. First, it's deployed with an empty constructor. After deployment, the `initialize` function must be called to set the owner, name, and base URI. This process is in place to support proxy deployments with the ERC1155TokenFactory. + +### Functions + +* `initialize(address owner, string memory name_, string memory baseURI_)`: Initializes the token contract, setting the owner, name, and base URI. +* `mint(address to, uint256 tokenId, uint256 amount, bytes memory data)`: Mints the specified amount of tokens of a given ID to the specified address. This function is restricted to addresses with the Minter role. +* `batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data)`: Mints specified amounts of tokens of given IDs to the specified address. This function is restricted to addresses with the Minter role. +* `setBaseMetadataURI(string memory baseURI_)`: Updates the base URI for the token metadata. This function is restricted to addresses with the Metadata Admin role. +* `setContractName(string memory name_)`: Updates the contract's name. This function is restricted to addresses with the Metadata Admin role. + +## ERC1155TokenFactory + +This contract deploys ERC1155Token contracts. It uses a proxy pattern to create new token instances to reduce gas costs. + +The deployment uses a `salt` which is combined with the caller's address for cross chain consistency and security. + +### Functions + +* `deploy(address owner, string memory name, string memory baseURI, bytes32 salt)`: Deploys a new ERC1155Token proxy contract, initializes it, and emits an ERC1155TokenDeployed event. + +## Usage + +To create a new ERC1155 token: + +1. Deploy the ERC1155TokenFactory contract (or use an existing deployment). +2. Call the `deploy` function on the factory, providing the desired parameters. +3. A new ERC1155Token contract will be created and initialized, ready for use. + +## Dependencies + +This repository relies on the ERC1155, ERC1155MintBurn, ERC1155Meta, ERC1155Metadata contracts from 0xSequence for core ERC-1155 functionality, AccessControl from OpenZeppelin for role base permissions and the ERC2981Controlled contract for handling of royalties. diff --git a/src/tokens/ERC20/README.md b/src/tokens/ERC20/README.md new file mode 100644 index 0000000..879d44b --- /dev/null +++ b/src/tokens/ERC20/README.md @@ -0,0 +1,36 @@ +# ERC20 Contracts + +This subsection contains contracts related to the [ERC20 token standard](https://eips.ethereum.org/EIPS/eip-20). + +## ERC20Token + +This contract is a complete, ready-to-use implementation of the ERC-20 token standard. It includes role based access control features from the [OpenZeppelin AccessControl](https://docs.openzeppelin.com/contracts/4.x/access-control) contract, providing control over minting operations. Please refer to OpenZeppelin documentation for more information on AccessControl. + +The ERC20Token contract has a two-step deployment process. First, it's deployed with an empty constructor. After deployment, the `initialize` function must be called to set the owner, token name, symbol, and decimals. This process is in place to support proxy deployments with the ERC20TokenFactory. + +### Functions + +* `initialize(address owner, string memory tokenName_, string memory tokenSymbol_, uint8 tokenDecimals_)`: Initializes the token contract, setting the owner, name, symbol, and number of decimals. +* `mint(address to, uint256 amount)`: Mints the given amount of tokens toP the specified address. This function is restricted to addresses with the Minter role. + +## ERC20TokenFactory + +This contract deploys ERC20Token contracts. It uses a proxy pattern to create new token instances to reduce gas costs. + +The deployment uses a `salt` which is combined with the caller's address for cross chain consistency and security. + +### Functions + +* `deploy(address owner, string memory name, string memory symbol, uint8 decimals, bytes32 salt)`: Deploys a new ERC20Token proxy contract, initializes it, and emits an ERC20TokenDeployed event. + +## Usage + +To create a new ERC20 token: + +1. Deploy the ERC20TokenFactory contract (or use an existing deployment). +2. Call the `deploy` function on the factory, providing the desired parameters. +3. A new ERC20Token contract will be created and initialized, ready for use. + +## Dependencies + +This repo relies on the OpenZeppelin Contracts library, particularly the ERC20, IERC20, IERC20Metadata, and AccessControl contracts, which provide core ERC-20 functionality and secure access control mechanisms. diff --git a/src/tokens/ERC721/README.md b/src/tokens/ERC721/README.md new file mode 100644 index 0000000..89fd7d7 --- /dev/null +++ b/src/tokens/ERC721/README.md @@ -0,0 +1,39 @@ +# ERC721 Contracts + +This subsection contains contracts related to the [ERC721 token standard](https://eips.ethereum.org/EIPS/eip-721). + +## ERC721Token + +This contract is a complete, ready-to-use implementation of the ERC-721 token standard. It leverages the [Azuki ERC-721A implementation](https://www.erc721a.org/) for gas efficiency. It includes role based access control features from the [OpenZeppelin AccessControl](https://docs.openzeppelin.com/contracts/4.x/access-control) contract, providing control over minting operations and metadata administration. Please refer to OpenZeppelin documentation for more information on AccessControl. + +The ERC721Token contract has a two-step deployment process. First, it's deployed with an empty constructor. After deployment, the `initialize` function must be called to set the owner, token name, symbol, and base URI. This process is in place to support proxy deployments with the ERC721TokenFactory. + +The contract supports the [ERC2981 token royalty standard](https://eips.ethereum.org/EIPS/eip-2981) via the ERC2981Controlled contract. Please refer to the ERC2981Controlled documentation for more information on token royalty. + +### Functions + +* `initialize(address owner, string memory tokenName_, string memory tokenSymbol_, string memory baseURI_)`: Initializes the token contract, setting the owner, name, symbol, and base URI. +* `mint(address to, uint256 amount)`: Mints the given amount of tokens to the specified address. This function is restricted to addresses with the Minter role. +* `setBaseMetadataURI(string memory baseURI_)`: Updates the base URI for the token metadata. This function is restricted to addresses with the Metadata Admin role. + +## ERC721TokenFactory + +This contract deploys ERC721Token contracts. It uses a proxy pattern to create new token instances to reduce gas costs. + +The deployment uses a `salt` which is combined with the caller's address for cross chain consistency and security. + +### Functions + +* `deploy(address owner, string memory name, string memory symbol, string memory baseURI, bytes32 salt)`: Deploys a new ERC721Token proxy contract, initializes it, and emits an ERC721TokenDeployed event. + +## Usage + +To create a new ERC721 token: + +1. Deploy the ERC721TokenFactory contract (or use an existing deployment). +2. Call the `deploy` function on the factory, providing the desired parameters. +3. A new ERC721Token contract will be created and initialized, ready for use. + +## Dependencies + +This repo relies on the ERC721A, IERC721A, ERC721AQueryable, and IERC721AQueryable contracts from Azuki for core ERC-721 functionality, AccessControl from OpenZeppelin for role base permissions and the ERC2981Controlled contract for handling of royalties. diff --git a/src/tokens/common/README.md b/src/tokens/common/README.md new file mode 100644 index 0000000..e3277d6 --- /dev/null +++ b/src/tokens/common/README.md @@ -0,0 +1,32 @@ +# Common Token Functionality + +This section contains common contracts that can be used for additional functionality beyond the base token standards. + +## ERC2981Controlled + +The `ERC2981Controlled` contract is an implementation of the [ERC2981 token royalty standard](https://eips.ethereum.org/EIPS/eip-2981), which provides a standardized way to handle royalties in NFTs and SFTs. + +This contract allows the royalty information for the contract as a whole, or individual token IDs, to be updated by users with the `ROYALTY_ADMIN_ROLE`. + +### Functions + +* `setDefaultRoyalty(address receiver, uint96 feeNumerator)`: Sets the default royalty information for all token IDs in the contract. The `receiver` is the address that will receive royalty payments, and the `feeNumerator` is the royalty fee expressed in basis points (e.g., 15% would be 1500). This function is restricted to users with the `ROYALTY_ADMIN_ROLE`. +* `setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator)`: Sets the royalty information for a specific token ID, overriding the default royalty information for that token ID. The parameters are the same as in `setDefaultRoyalty`. This function is restricted to users with the `ROYALTY_ADMIN_ROLE`. + +### Usage + +To use this contract, it should be inherited by the main token contract. For example: + +```solidity +contract MyNFT is ERC721, ERC2981Controlled { + // ... +} +``` + +After that, the royalty information can be set and updated by users with the `ROYALTY_ADMIN_ROLE`. + +Alternatively, use the `ERC721Token` or `ERC1155Token` implementations which already extend this contract. + +### Dependencies + +The `ERC2981Controlled` contract depends on OpenZeppelin's `ERC2981` and `AccessControl` contracts. `ERC2981` provides the basic royalty-related functionality according to the standard, while `AccessControl` provides a flexible system of access control based on roles. From a35815a95b1790ec30bf2386bd0fa5b1417da412 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Fri, 9 Jun 2023 10:58:36 +1200 Subject: [PATCH 14/49] Add deploy script --- .env.example | 3 ++ README.md | 8 +++ package.json | 2 + scripts/deploy.ts | 133 ++++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 5 ++ 5 files changed, 151 insertions(+) create mode 100644 .env.example create mode 100644 scripts/deploy.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..81486b9 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +PRIVATE_KEY= +RPC_URL= + diff --git a/README.md b/README.md index 3f10a92..1e263d1 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,14 @@ This repository provides a set of smart contracts to facilitate the creation and ### Deployment +Copy `.env.example` to `.env` and set your wallet configuration. + +```sh +cp .env.example .env +``` + +Then run the deployment script. + ```sh yarn deploy ``` diff --git a/package.json b/package.json index cc3612d..307f9f7 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "license": "Apache-2.0", "scripts": { "build": "ts-node scripts/build.ts", + "deploy": "ts-node scripts/deploy.ts", "test": "forge test", "lint:init": "husky install", "lint:sol": "solhint \"./src/**/*.sol\" \"./tests/**/*.sol\"", @@ -35,6 +36,7 @@ }, "devDependencies": { "@types/node": "^20.1.0", + "dotenv": "^16.1.4", "husky": "^8.0.3", "lint-staged": "^13.2.2", "solhint": "^3.4.1", diff --git a/scripts/deploy.ts b/scripts/deploy.ts new file mode 100644 index 0000000..0bec1d9 --- /dev/null +++ b/scripts/deploy.ts @@ -0,0 +1,133 @@ +import { readFile } from 'fs/promises' +import { join } from 'path' +import { BUILD_DIR, DEPLOYABLE_CONTRACT_NAMES } from './constants' +import { config as dotenvConfig } from 'dotenv' +import { + ContractFactory, + ContractTransaction, + Signer, + Wallet, + ethers, +} from 'ethers' +import { JsonRpcProvider } from '@ethersproject/providers' + +dotenvConfig() + +const { PRIVATE_KEY, RPC_URL } = process.env + +const MAX_GAS_LIMIT = 6000000 + +const singletonFactoryFactory = { + address: '0xce0042B868300000d44A59004Da54A005ffdcf9f', + abi: [ + { + constant: false, + inputs: [ + { + internalType: 'bytes', + type: 'bytes', + }, + { + internalType: 'bytes32', + type: 'bytes32', + }, + ], + name: 'deploy', + outputs: [ + { + internalType: 'address payable', + type: 'address', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + ], +} + +const main = async () => { + if (!PRIVATE_KEY || !RPC_URL) { + throw new Error('Environment vars not set') + } + + // Prep deployer wallet + const provider = new JsonRpcProvider(RPC_URL) + const wallet = new Wallet(PRIVATE_KEY, provider) + + // Create deployer factory + const singletonFactory = new ethers.Contract( + singletonFactoryFactory.address, + singletonFactoryFactory.abi, + wallet, + ) + + // Get deployment files from build dir + for (const solFile of DEPLOYABLE_CONTRACT_NAMES) { + console.log(`Deploying ${solFile}`) + + // Create contract for deployment + const compilerOutput = JSON.parse( + await readFile(join(BUILD_DIR, `${solFile}.json`), 'utf8'), + ) + class MyContractFactory extends ContractFactory { + constructor(signer?: Signer) { + super(compilerOutput.abi, compilerOutput.bytecode.object, signer) + } + } + const contract = new MyContractFactory(wallet) + const contractCode = contract.getDeployTransaction().data + if (!contractCode) { + throw new Error(`${solFile} did not return contract code`) + } + + // Check if already deployed + const address = ethers.utils.getAddress( + ethers.utils.hexDataSlice( + ethers.utils.keccak256( + ethers.utils.solidityPack( + ['bytes1', 'address', 'bytes32', 'bytes32'], + [ + '0xff', + singletonFactory.address, + ethers.constants.HashZero, + ethers.utils.keccak256(contractCode), + ], + ), + ), + 12, + ), + ) + + if (ethers.utils.arrayify(await provider.getCode(address)).length > 0) { + console.log( + `Skipping ${solFile} because it has been deployed at ${address}`, + ) + continue + } + + const tx: ContractTransaction = await singletonFactory.deploy( + contractCode, + ethers.constants.HashZero, + { + gasLimit: MAX_GAS_LIMIT, + }, + ) + await tx.wait() + + if (ethers.utils.arrayify(await provider.getCode(address)).length === 0) { + throw new Error(`failed to deploy ${solFile}`) + } + + console.log(`Deployed ${solFile} at ${address}`) + } +} + +main() + .then(() => { + console.log('Done') + }) + .catch(err => { + console.error(err) + process.exit(1) + }) diff --git a/yarn.lock b/yarn.lock index aaa018c..56213ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -725,6 +725,11 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +dotenv@^16.1.4: + version "16.1.4" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.1.4.tgz#67ac1a10cd9c25f5ba604e4e08bc77c0ebe0ca8c" + integrity sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" From 8de75f4e7b6bdbe4c611dcdc2113a727271ff776 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 20 Jun 2023 09:18:30 +1200 Subject: [PATCH 15/49] Variable names --- src/proxies/ERC1967/Proxy.sol | 13 +++--- src/proxies/ERC1967/ProxyDeployer.sol | 31 +++++++------- src/tokens/ERC1155/ERC1155Token.sol | 32 +++++++------- src/tokens/ERC1155/ERC1155TokenFactory.sol | 6 +-- .../ERC1155/Packed/ERC1155PackedToken.sol | 32 +++++++------- .../Packed/ERC1155PackedTokenFactory.sol | 6 +-- src/tokens/ERC20/ERC20Token.sol | 36 ++++++++-------- src/tokens/ERC20/ERC20TokenFactory.sol | 6 +-- src/tokens/ERC721/ERC721Token.sol | 42 +++++++++---------- src/tokens/ERC721/ERC721TokenFactory.sol | 6 +-- src/utils/StorageSlot.sol | 16 +++---- 11 files changed, 114 insertions(+), 112 deletions(-) diff --git a/src/proxies/ERC1967/Proxy.sol b/src/proxies/ERC1967/Proxy.sol index 1cb44d6..0bd22b2 100644 --- a/src/proxies/ERC1967/Proxy.sol +++ b/src/proxies/ERC1967/Proxy.sol @@ -19,20 +19,20 @@ contract Proxy is IERC1967 { * Forward calls to the proxy implementation contract. */ receive() external payable { - proxy(); + _proxy(); } /** * Forward calls to the proxy implementation contract. */ fallback() external payable { - proxy(); + _proxy(); } /** * Forward calls to the proxy implementation contract. */ - function proxy() private { + function _proxy() private { address target = _getImplementation(); assembly { // solhint-disable-line no-inline-assembly let ptr := mload(0x40) @@ -48,15 +48,16 @@ contract Proxy is IERC1967 { /** * Set the implementation address. + * @param _implementation The address of the implementation contract. */ - function _setImplementation(address implementation) internal { - StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = implementation; + function _setImplementation(address _implementation) internal { + StorageSlot._getAddressSlot(IMPLEMENTATION_SLOT).value = _implementation; } /** * Returns the address of the current implementation. */ function _getImplementation() internal view returns (address) { - return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value; + return StorageSlot._getAddressSlot(IMPLEMENTATION_SLOT).value; } } diff --git a/src/proxies/ERC1967/ProxyDeployer.sol b/src/proxies/ERC1967/ProxyDeployer.sol index e590c4f..f2aa6e8 100644 --- a/src/proxies/ERC1967/ProxyDeployer.sol +++ b/src/proxies/ERC1967/ProxyDeployer.sol @@ -5,18 +5,19 @@ import {ProxyDeployerErrors} from "./ProxyDeployerErrors.sol"; import {Proxy} from "./Proxy.sol"; abstract contract ProxyDeployer is ProxyDeployerErrors { + /** * Creates a proxy contract for a given implementation - * @param implAddr The address of the proxy implementation - * @param salt The deployment salt + * @param _implAddr The address of the proxy implementation + * @param _salt The deployment salt * @return proxyAddr The address of the deployed proxy */ - function _deployProxy(address implAddr, bytes32 salt) internal returns (address proxyAddr) { - bytes memory code = _getProxyCode(implAddr); + function _deployProxy(address _implAddr, bytes32 _salt) internal returns (address proxyAddr) { + bytes memory code = _getProxyCode(_implAddr); // Deploy it assembly { // solhint-disable-line no-inline-assembly - proxyAddr := create2(0, add(code, 32), mload(code), salt) + proxyAddr := create2(0, add(code, 32), mload(code), _salt) } if (proxyAddr == address(0)) { revert ProxyCreationFailed(); @@ -37,35 +38,35 @@ abstract contract ProxyDeployer is ProxyDeployerErrors { /** * Predict the deployed wrapper proxy address for a given implementation. - * @param code The code of the wrapper implementation - * @param salt The deployment salt + * @param _code The code of the wrapper implementation + * @param _salt The deployment salt * @return proxyAddr The address of the deployed wrapper */ - function _predictProxyAddress(bytes memory code, bytes32 salt) private view returns (address proxyAddr) { + function _predictProxyAddress(bytes memory _code, bytes32 _salt) private view returns (address proxyAddr) { address deployer = address(this); - bytes32 data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(code))); + bytes32 data = keccak256(abi.encodePacked(bytes1(0xff), deployer, _salt, keccak256(_code))); return address(uint160(uint256(data))); } /** * Returns the code of the proxy contract for a given implementation - * @param implAddr The address of the proxy implementation + * @param _implAddr The address of the proxy implementation * @return code The code of the proxy contract */ - function _getProxyCode(address implAddr) private pure returns (bytes memory code) { - return abi.encodePacked(type(Proxy).creationCode, abi.encode(implAddr)); + function _getProxyCode(address _implAddr) private pure returns (bytes memory code) { + return abi.encodePacked(type(Proxy).creationCode, abi.encode(_implAddr)); } /** * Checks if an address is a contract - * @param addr The address to check + * @param _addr The address to check * @return result True if the address is a contract */ - function _isContract(address addr) internal view returns (bool result) { + function _isContract(address _addr) internal view returns (bool result) { uint256 csize; // solhint-disable-next-line no-inline-assembly assembly { - csize := extcodesize(addr) + csize := extcodesize(_addr) } return csize != 0; } diff --git a/src/tokens/ERC1155/ERC1155Token.sol b/src/tokens/ERC1155/ERC1155Token.sol index c27e085..943ae5f 100644 --- a/src/tokens/ERC1155/ERC1155Token.sol +++ b/src/tokens/ERC1155/ERC1155Token.sol @@ -15,31 +15,31 @@ contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981C bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - address private immutable initializer; - bool private initialized; + address private immutable _initializer; + bool private _initialized; /** * Initialize contract. */ constructor() ERC1155Metadata("", "") { - initializer = msg.sender; + _initializer = msg.sender; } /** * Initialize the contract. * @param owner Owner address. - * @param name_ Token name. - * @param baseURI_ Base URI for token metadata. + * @param tokenName Token name. + * @param tokenBaseURI Base URI for token metadata. * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory name_, string memory baseURI_) public { - if (msg.sender != initializer || initialized) { + function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public { + if (msg.sender != _initializer || _initialized) { revert InvalidInitialization(); } - initialized = true; + _initialized = true; - name = name_; - baseURI = baseURI_; + name = tokenName; + baseURI = tokenBaseURI; _setupRole(DEFAULT_ADMIN_ROLE, owner); _setupRole(MINTER_ROLE, owner); @@ -82,18 +82,18 @@ contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981C /** * Update the base URL of token's URI. - * @param baseURI_ New base URL of token's URI + * @param tokenBaseURI New base URL of token's URI */ - function setBaseMetadataURI(string memory baseURI_) external onlyRole(METADATA_ADMIN_ROLE) { - _setBaseMetadataURI(baseURI_); + function setBaseMetadataURI(string memory tokenBaseURI) external onlyRole(METADATA_ADMIN_ROLE) { + _setBaseMetadataURI(tokenBaseURI); } /** * Update the name of the contract. - * @param name_ New contract name + * @param tokenName New contract name */ - function setContractName(string memory name_) external onlyRole(METADATA_ADMIN_ROLE) { - _setContractName(name_); + function setContractName(string memory tokenName) external onlyRole(METADATA_ADMIN_ROLE) { + _setContractName(tokenName); } // diff --git a/src/tokens/ERC1155/ERC1155TokenFactory.sol b/src/tokens/ERC1155/ERC1155TokenFactory.sol index 3c54a80..564503d 100644 --- a/src/tokens/ERC1155/ERC1155TokenFactory.sol +++ b/src/tokens/ERC1155/ERC1155TokenFactory.sol @@ -6,14 +6,14 @@ import {IERC1155TokenFactory} from "./IERC1155TokenFactory.sol"; import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; contract ERC1155TokenFactory is IERC1155TokenFactory, ProxyDeployer { - address private immutable implAddr; + address private immutable _implAddr; /** * Creates an ERC-1155 Token Factory. */ constructor() { ERC1155Token proxyImpl = new ERC1155Token(); - implAddr = address(proxyImpl); + _implAddr = address(proxyImpl); } /** @@ -29,7 +29,7 @@ contract ERC1155TokenFactory is IERC1155TokenFactory, ProxyDeployer { external returns (address proxyAddr) { - proxyAddr = _deployProxy(implAddr, keccak256(abi.encode(msg.sender, salt))); + proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); ERC1155Token(proxyAddr).initialize(owner, name, baseURI); emit ERC1155TokenDeployed(proxyAddr); return proxyAddr; diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol index 71b91d3..f0ba098 100644 --- a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol +++ b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol @@ -23,31 +23,31 @@ contract ERC1155PackedToken is bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); - address private immutable initializer; - bool private initialized; + address private immutable _initializer; + bool private _initialized; /** * Initialize contract. */ constructor() ERC1155Metadata("", "") { - initializer = msg.sender; + _initializer = msg.sender; } /** * Initialize the contract. * @param owner Owner address. - * @param name_ Token name. - * @param baseURI_ Base URI for token metadata. + * @param tokenName Token name. + * @param tokenBaseURI Base URI for token metadata. * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory name_, string memory baseURI_) public { - if (msg.sender != initializer || initialized) { + function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public { + if (msg.sender != _initializer || _initialized) { revert InvalidInitialization(); } - initialized = true; + _initialized = true; - name = name_; - baseURI = baseURI_; + name = tokenName; + baseURI = tokenBaseURI; _setupRole(DEFAULT_ADMIN_ROLE, owner); _setupRole(MINTER_ROLE, owner); @@ -90,18 +90,18 @@ contract ERC1155PackedToken is /** * Update the base URL of token's URI. - * @param baseURI_ New base URL of token's URI + * @param tokenBaseURI New base URL of token's URI */ - function setBaseMetadataURI(string memory baseURI_) external onlyRole(METADATA_ADMIN_ROLE) { - _setBaseMetadataURI(baseURI_); + function setBaseMetadataURI(string memory tokenBaseURI) external onlyRole(METADATA_ADMIN_ROLE) { + _setBaseMetadataURI(tokenBaseURI); } /** * Update the name of the contract. - * @param name_ New contract name + * @param tokenName New contract name */ - function setContractName(string memory name_) external onlyRole(METADATA_ADMIN_ROLE) { - _setContractName(name_); + function setContractName(string memory tokenName) external onlyRole(METADATA_ADMIN_ROLE) { + _setContractName(tokenName); } // diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol b/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol index 9f483f7..9581a98 100644 --- a/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol +++ b/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol @@ -6,14 +6,14 @@ import {IERC1155PackedTokenFactory} from "./IERC1155PackedTokenFactory.sol"; import {ProxyDeployer} from "../../../proxies/ERC1967/ProxyDeployer.sol"; contract ERC1155PackedTokenFactory is IERC1155PackedTokenFactory, ProxyDeployer { - address private immutable implAddr; + address private immutable _implAddr; /** * Creates an ERC-1155 Token Factory. */ constructor() { ERC1155PackedToken proxyImpl = new ERC1155PackedToken(); - implAddr = address(proxyImpl); + _implAddr = address(proxyImpl); } /** @@ -29,7 +29,7 @@ contract ERC1155PackedTokenFactory is IERC1155PackedTokenFactory, ProxyDeployer external returns (address proxyAddr) { - proxyAddr = _deployProxy(implAddr, keccak256(abi.encode(msg.sender, salt))); + proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); ERC1155PackedToken(proxyAddr).initialize(owner, name, baseURI); emit ERC1155PackedTokenDeployed(proxyAddr); return proxyAddr; diff --git a/src/tokens/ERC20/ERC20Token.sol b/src/tokens/ERC20/ERC20Token.sol index 308579f..9d53711 100644 --- a/src/tokens/ERC20/ERC20Token.sol +++ b/src/tokens/ERC20/ERC20Token.sol @@ -14,37 +14,37 @@ error InvalidInitialization(); contract ERC20Token is ERC20, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - string private tokenName; - string private tokenSymbol; - uint8 private tokenDecimals; + string private _tokenName; + string private _tokenSymbol; + uint8 private _tokenDecimals; - address private immutable initializer; - bool private initialized; + address private immutable _initializer; + bool private _initialized; /** * Deploy contract. */ constructor() ERC20("", "") { - initializer = msg.sender; + _initializer = msg.sender; } /** * Initialize contract. * @param owner The owner of the contract - * @param tokenName_ Name of the token - * @param tokenSymbol_ Symbol of the token - * @param tokenDecimals_ Number of decimals + * @param tokenName Name of the token + * @param tokenSymbol Symbol of the token + * @param tokenDecimals Number of decimals * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory tokenName_, string memory tokenSymbol_, uint8 tokenDecimals_) external { - if (msg.sender != initializer || initialized) { + function initialize(address owner, string memory tokenName, string memory tokenSymbol, uint8 tokenDecimals) external { + if (msg.sender != _initializer || _initialized) { revert InvalidInitialization(); } - initialized = true; + _initialized = true; - tokenName = tokenName_; - tokenSymbol = tokenSymbol_; - tokenDecimals = tokenDecimals_; + _tokenName = tokenName; + _tokenSymbol = tokenSymbol; + _tokenDecimals = tokenDecimals; _setupRole(DEFAULT_ADMIN_ROLE, owner); _setupRole(MINTER_ROLE, owner); @@ -85,20 +85,20 @@ contract ERC20Token is ERC20, AccessControl { * Override the ERC20 name function. */ function name() public view override returns (string memory) { - return tokenName; + return _tokenName; } /** * Override the ERC20 symbol function. */ function symbol() public view override returns (string memory) { - return tokenSymbol; + return _tokenSymbol; } /** * Override the ERC20 decimals function. */ function decimals() public view override returns (uint8) { - return tokenDecimals; + return _tokenDecimals; } } diff --git a/src/tokens/ERC20/ERC20TokenFactory.sol b/src/tokens/ERC20/ERC20TokenFactory.sol index 716cff5..7329f16 100644 --- a/src/tokens/ERC20/ERC20TokenFactory.sol +++ b/src/tokens/ERC20/ERC20TokenFactory.sol @@ -6,14 +6,14 @@ import {IERC20TokenFactory} from "./IERC20TokenFactory.sol"; import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; contract ERC20TokenFactory is IERC20TokenFactory, ProxyDeployer { - address private immutable implAddr; + address private immutable _implAddr; /** * Creates an ERC-20 Token Factory. */ constructor() { ERC20Token proxyImpl = new ERC20Token(); - implAddr = address(proxyImpl); + _implAddr = address(proxyImpl); } /** @@ -30,7 +30,7 @@ contract ERC20TokenFactory is IERC20TokenFactory, ProxyDeployer { external returns (address proxyAddr) { - proxyAddr = _deployProxy(implAddr, keccak256(abi.encode(msg.sender, salt))); + proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); ERC20Token(proxyAddr).initialize(owner, name, symbol, decimals); emit ERC20TokenDeployed(proxyAddr); return proxyAddr; diff --git a/src/tokens/ERC721/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol index 91ab807..d1618f0 100644 --- a/src/tokens/ERC721/ERC721Token.sol +++ b/src/tokens/ERC721/ERC721Token.sol @@ -15,37 +15,37 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - string private baseURI; - string private tokenName; - string private tokenSymbol; + string private _tokenBaseURI; + string private _tokenName; + string private _tokenSymbol; - address private immutable initializer; - bool private initialized; + address private immutable _initializer; + bool private _initialized; /** * Deploy contract. */ constructor() ERC721A("", "") { - initializer = msg.sender; + _initializer = msg.sender; } /** * Initialize contract. * @param owner The owner of the contract - * @param tokenName_ Name of the token - * @param tokenSymbol_ Symbol of the token - * @param baseURI_ Base URI of the token + * @param tokenName Name of the token + * @param tokenSymbol Symbol of the token + * @param tokenBaseURI Base URI of the token * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory tokenName_, string memory tokenSymbol_, string memory baseURI_) external { - if (msg.sender != initializer || initialized) { + function initialize(address owner, string memory tokenName, string memory tokenSymbol, string memory tokenBaseURI) external { + if (msg.sender != _initializer || _initialized) { revert InvalidInitialization(); } - initialized = true; + _initialized = true; - tokenName = tokenName_; - tokenSymbol = tokenSymbol_; - baseURI = baseURI_; + _tokenName = tokenName; + _tokenSymbol = tokenSymbol; + _tokenBaseURI = tokenBaseURI; _setupRole(DEFAULT_ADMIN_ROLE, owner); _setupRole(METADATA_ADMIN_ROLE, owner); @@ -72,10 +72,10 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { /** * Update the base URL of token's URI. - * @param baseURI_ New base URL of token's URI + * @param tokenBaseURI New base URL of token's URI */ - function setBaseMetadataURI(string memory baseURI_) external onlyRole(METADATA_ADMIN_ROLE) { - baseURI = baseURI_; + function setBaseMetadataURI(string memory tokenBaseURI) external onlyRole(METADATA_ADMIN_ROLE) { + _tokenBaseURI = tokenBaseURI; } // @@ -106,20 +106,20 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { * Override the ERC721A baseURI function. */ function _baseURI() internal view override returns (string memory) { - return baseURI; + return _tokenBaseURI; } /** * Override the ERC721A name function. */ function name() public view override (ERC721A, IERC721A) returns (string memory) { - return tokenName; + return _tokenName; } /** * Override the ERC721A symbol function. */ function symbol() public view override (ERC721A, IERC721A) returns (string memory) { - return tokenSymbol; + return _tokenSymbol; } } diff --git a/src/tokens/ERC721/ERC721TokenFactory.sol b/src/tokens/ERC721/ERC721TokenFactory.sol index f2cdd7b..1f44491 100644 --- a/src/tokens/ERC721/ERC721TokenFactory.sol +++ b/src/tokens/ERC721/ERC721TokenFactory.sol @@ -6,14 +6,14 @@ import {IERC721TokenFactory} from "./IERC721TokenFactory.sol"; import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; contract ERC721TokenFactory is IERC721TokenFactory, ProxyDeployer { - address private immutable implAddr; + address private immutable _implAddr; /** * Creates an ERC-721 Token Factory. */ constructor() { ERC721Token proxyImpl = new ERC721Token(); - implAddr = address(proxyImpl); + _implAddr = address(proxyImpl); } /** @@ -30,7 +30,7 @@ contract ERC721TokenFactory is IERC721TokenFactory, ProxyDeployer { external returns (address proxyAddr) { - proxyAddr = _deployProxy(implAddr, keccak256(abi.encode(msg.sender, salt))); + proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); ERC721Token(proxyAddr).initialize(owner, name, symbol, baseURI); emit ERC721TokenDeployed(proxyAddr); return proxyAddr; diff --git a/src/utils/StorageSlot.sol b/src/utils/StorageSlot.sol index d0c0ef3..b45b9fb 100644 --- a/src/utils/StorageSlot.sol +++ b/src/utils/StorageSlot.sol @@ -29,36 +29,36 @@ library StorageSlot { /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ - function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { + function _getAddressSlot(bytes32 _slot) internal pure returns (AddressSlot storage r) { assembly { // solhint-disable-line no-inline-assembly - r.slot := slot + r.slot := _slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ - function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { + function _getBooleanSlot(bytes32 _slot) internal pure returns (BooleanSlot storage r) { assembly { // solhint-disable-line no-inline-assembly - r.slot := slot + r.slot := _slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ - function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { + function _getBytes32Slot(bytes32 _slot) internal pure returns (Bytes32Slot storage r) { assembly { // solhint-disable-line no-inline-assembly - r.slot := slot + r.slot := _slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ - function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { + function _getUint256Slot(bytes32 _slot) internal pure returns (Uint256Slot storage r) { assembly { // solhint-disable-line no-inline-assembly - r.slot := slot + r.slot := _slot } } } From 59f4483fb287f99ffcca44809db95829058882da Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 2 May 2023 10:32:18 +1200 Subject: [PATCH 16/49] Add ERC1155Sale base implementation --- package.json | 4 +- src/tokens/ERC1155/ERC1155Sale.sol | 208 ++++++++++++++ src/tokens/ERC1155/ERC1155SaleErrors.sol | 23 ++ test/tokens/ERC1155/ERC1155Sale.t.sol | 347 +++++++++++++++++++++++ 4 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 src/tokens/ERC1155/ERC1155Sale.sol create mode 100644 src/tokens/ERC1155/ERC1155SaleErrors.sol create mode 100644 test/tokens/ERC1155/ERC1155Sale.t.sol diff --git a/package.json b/package.json index 307f9f7..648eed1 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ "dependencies": { "@0xsequence/erc-1155": "^4.0.3", "@0xsequence/erc20-meta-token": "^4.0.1", - "@openzeppelin/contracts": "^4.8.3", "erc721a": "^4.2.3", - "erc721a-upgradeable": "^4.2.3" + "erc721a-upgradeable": "^4.2.3", + "@openzeppelin/contracts": "^4.8.3" }, "lint-staged": { "**/*.sol": "yarn lint:sol && yarn format:sol" diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol new file mode 100644 index 0000000..9e2a57f --- /dev/null +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC1155, ERC1155Meta} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Meta.sol"; +import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; +import {ERC1155MintBurn} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155MintBurn.sol"; +import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ERC1155SaleErrors} from "./ERC1155SaleErrors.sol"; + +contract ERC1155Sale is ERC1155SaleErrors, ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981, AccessControl { + bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); + bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); + + bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = + bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); + + event GlobalSaleDetailsUpdated(uint256 amount, uint64 startTime, uint64 endTime); + event TokenSaleDetailsUpdated(uint256 tokenId, uint256 amount, uint64 startTime, uint64 endTime); + + // ERC20 token address for payment. address(0) indicated payment in ETH. + address public paymentToken; + + struct SaleDetails { + uint256 amount; + uint64 startTime; + uint64 endTime; // 0 end time indicates sale inactive + } + + SaleDetails public globalSaleDetails; + mapping(uint256 => SaleDetails) public tokenSaleDetails; + + constructor(address owner, string memory _name, string memory _baseURI) ERC1155Metadata(_name, _baseURI) { + _setupRole(DEFAULT_ADMIN_ROLE, owner); + _setupRole(MINT_ADMIN_ROLE, owner); + _setupRole(ROYALTY_ADMIN_ROLE, owner); + } + + /** + * Checks if the current block.timestamp is out of the give timestamp range. + * @param startTime Earliest acceptable timestamp (inclusive). + * @param endTime Latest acceptable timestamp (exclusive). + * @dev A zero endTime value is always considered out of bounds. + */ + function blockTimeOutOfBounds(uint256 startTime, uint256 endTime) private view returns (bool) { + // 0 end time indicates inactive sale. + return endTime == 0 || block.timestamp < startTime || block.timestamp >= endTime; + } + + /** + * Checks the sale is active and takes payment. + * @param _tokenIds Token IDs to mint. + * @param _amounts Amounts of tokens to mint. + */ + function payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts) private { + uint256 total; + bool globalSaleInactive = blockTimeOutOfBounds(globalSaleDetails.startTime, globalSaleDetails.endTime); + for (uint256 i; i < _tokenIds.length; i++) { + // Active sale test + SaleDetails memory saleDetails = tokenSaleDetails[_tokenIds[i]]; + bool tokenSaleInactive = blockTimeOutOfBounds(saleDetails.startTime, saleDetails.endTime); + if (tokenSaleInactive) { + // Prefer token sale + if (globalSaleInactive) { + // Both sales inactive + revert SaleInactive(_tokenIds[i]); + } + // Use global sale price + total += globalSaleDetails.amount * _amounts[i]; + } else { + // Use token sale price + total += saleDetails.amount * _amounts[i]; + } + } + + if (paymentToken == address(0)) { + // Paid in ETH + if (msg.value != total) { + revert InsufficientPayment(total, msg.value); + } + } else { + // Paid in ERC20 + (bool success, bytes memory data) = + paymentToken.call(abi.encodeWithSelector(ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), total)); + if (!success || (data.length > 0 && !abi.decode(data, (bool)))) { + revert InsufficientPayment(total, 0); + } + } + } + + // + // Minting + // + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _tokenIds Token IDs to mint. + * @param _amounts Amounts of tokens to mint. + * @param _data Data to pass if receiver is contract. + * @notice Sale must be active for all tokens. + */ + function mint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + public + payable + { + payForActiveMint(_tokenIds, _amounts); + _batchMint(_to, _tokenIds, _amounts, _data); + } + + /** + * Set the global sale details. + * @param _paymentToken The ERC20 token address to accept payment in. address(0) indicates ETH. + * @param _amount The amount of payment tokens to accept for each token minted. + * @param _startTime The start time of the sale. Tokens cannot be minted before this time. + * @param _endTime The end time of the sale. Tokens cannot be minted after this time. + * @dev A zero end time indicates an inactive sale. + */ + function setGlobalSalesDetails(address _paymentToken, uint256 _amount, uint64 _startTime, uint64 _endTime) + public + onlyRole(MINT_ADMIN_ROLE) + { + paymentToken = _paymentToken; + globalSaleDetails = SaleDetails(_amount, _startTime, _endTime); + emit GlobalSaleDetailsUpdated(_amount, _startTime, _endTime); + } + + /** + * Set the sale details for an individual token. + * @param _tokenId The token ID to set the sale details for. + * @param _amount The amount of payment tokens to accept for each token minted. + * @param _startTime The start time of the sale. Tokens cannot be minted before this time. + * @param _endTime The end time of the sale. Tokens cannot be minted after this time. + * @dev A zero end time indicates an inactive sale. + */ + function setTokenSalesDetails(uint256 _tokenId, uint256 _amount, uint64 _startTime, uint64 _endTime) + public + onlyRole(MINT_ADMIN_ROLE) + { + tokenSaleDetails[_tokenId] = SaleDetails(_amount, _startTime, _endTime); + emit TokenSaleDetailsUpdated(_tokenId, _amount, _startTime, _endTime); + } + + // + // Royalty + // + + /** + * Sets the royalty information that all ids in this contract will default to. + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + */ + function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) public onlyRole(ROYALTY_ADMIN_ROLE) { + _setDefaultRoyalty(_receiver, _feeNumerator); + } + + /** + * Sets the royalty information that a given token id in this contract will use. + * @param _tokenId The token id to set the royalty information for + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @notice This overrides the default royalty information for this token id + */ + function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) + public + onlyRole(ROYALTY_ADMIN_ROLE) + { + _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + } + + // + // Withdraw + // + + /** + * Withdraws ETH or ERC20 tokens owned by this sale contract. + * @dev Withdraws ERC20 when paymentToken is set, else ETH. + * @notice Only callable by the contract admin. + */ + function withdraw(address _to, uint256 _amount) public onlyRole(DEFAULT_ADMIN_ROLE) { + if (paymentToken == address(0)) { + (bool success,) = _to.call{value: _amount}(""); + if (!success) { + revert WithdrawFailed(); + } + } else { + (bool success) = IERC20(paymentToken).transfer(_to, _amount); + if (!success) { + revert WithdrawFailed(); + } + } + } + + // + // Views + // + function supportsInterface(bytes4 _interfaceId) + public + view + override (ERC1155, ERC1155Metadata, ERC2981, AccessControl) + returns (bool) + { + // FIXME Fix inheritance issues + return ERC1155.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId) + || super.supportsInterface(_interfaceId); + } +} diff --git a/src/tokens/ERC1155/ERC1155SaleErrors.sol b/src/tokens/ERC1155/ERC1155SaleErrors.sol new file mode 100644 index 0000000..b2b36c8 --- /dev/null +++ b/src/tokens/ERC1155/ERC1155SaleErrors.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +// A contract for errors for extensibility. +contract ERC1155SaleErrors { + /** + * Sale is not active. + * @param tokenId Invalid Token ID. + */ + error SaleInactive(uint256 tokenId); + + /** + * Insufficient tokens for payment. + * @param expected Expected amount of tokens. + * @param actual Actual amount of tokens. + */ + error InsufficientPayment(uint256 expected, uint256 actual); + + /** + * Withdraw failed. + */ + error WithdrawFailed(); +} diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol new file mode 100644 index 0000000..d98a836 --- /dev/null +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import {ERC1155Sale} from "src/tokens/ERC1155/ERC1155Sale.sol"; +import {ERC1155SaleErrors} from "src/tokens/ERC1155/ERC1155SaleErrors.sol"; + +import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +// Interfaces +import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; +import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; +import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; + +contract ERC1155SaleTest is Test, ERC1155SaleErrors { + // Redeclare events + event TransferBatch( + address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts + ); + + ERC1155Sale private token; + ERC20Mock private erc20; + uint256 private perTokenCost = 0.02 ether; + + function setUp() public { + token = new ERC1155Sale(address(this), "test", "ipfs://"); + + vm.deal(address(this), 100 ether); + } + + function testSupportsInterface() public { + assertTrue(token.supportsInterface(type(IERC165).interfaceId)); + assertTrue(token.supportsInterface(type(IERC1155).interfaceId)); + assertTrue(token.supportsInterface(type(IERC1155Metadata).interfaceId)); + assertTrue(token.supportsInterface(type(IAccessControl).interfaceId)); + } + + // + // Minting + // + + // Minting denied when no sale active. + function testMintInactiveFail(address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + { + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when sale is active but not for the token. + function testMintInactiveSingleFail(address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withTokenSaleActive(tokenId + 1) + { + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when token sale is expired. + function testMintExpiredSingleFail( + address mintTo, + uint256 tokenId, + uint256 amount, + uint256 startTime, + uint256 endTime + ) + public + assumeSafe(mintTo, tokenId, amount) + { + vm.assume(startTime > endTime); + vm.assume(block.timestamp < startTime || block.timestamp >= endTime); + token.setTokenSalesDetails(tokenId, perTokenCost, uint64(startTime), uint64(endTime)); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when global sale is expired. + function testMintExpiredGlobalFail( + address mintTo, + uint256 tokenId, + uint256 amount, + uint256 startTime, + uint256 endTime + ) + public + assumeSafe(mintTo, tokenId, amount) + { + vm.assume(startTime > endTime); + vm.assume(block.timestamp < startTime || block.timestamp >= endTime); + token.setGlobalSalesDetails(address(0), perTokenCost, uint64(startTime), uint64(endTime)); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when sale is active but not for all tokens in the group. + function testMintInactiveInGroupFail(address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withTokenSaleActive(tokenId) + { + uint256[] memory tokenIds = new uint256[](2); + tokenIds[0] = tokenId; + tokenIds[1] = tokenId + 1; + uint256[] memory amounts = new uint256[](2); + amounts[0] = amount; + amounts[1] = amount; + + vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId + 1)); + token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, ""); + } + + // Minting allowed when sale is active globally. + function testMintGlobalSuccess(address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withGlobalSaleActive + { + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + uint256 count = token.balanceOf(mintTo, tokenId); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + } + + // Minting allowed when sale is active for the token. + function testMintSingleSuccess(address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withTokenSaleActive(tokenId) + { + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + uint256 count = token.balanceOf(mintTo, tokenId); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + } + + // Minting allowed when sale is active for both tokens individually. + function testMintGroupSuccess(address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + { + setTokenSaleActive(tokenId); + setTokenSaleActive(tokenId + 1); + uint256[] memory tokenIds = new uint256[](2); + tokenIds[0] = tokenId; + tokenIds[1] = tokenId + 1; + uint256[] memory amounts = new uint256[](2); + amounts[0] = amount; + amounts[1] = amount; + + uint256 count = token.balanceOf(mintTo, tokenId); + uint256 count2 = token.balanceOf(mintTo, tokenId + 1); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + assertEq(count2 + amount, token.balanceOf(mintTo, tokenId + 1)); + } + + // Minting allowed when global sale is free. + function testFreeGlobalMint(address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + { + token.setGlobalSalesDetails(address(0), 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + uint256 count = token.balanceOf(mintTo, tokenId); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + } + + // Minting allowed when token sale is free and global is not. + function testFreeTokenMint(address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withGlobalSaleActive + { + token.setTokenSalesDetails(tokenId, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + uint256 count = token.balanceOf(mintTo, tokenId); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + } + + // Minting allowed when mint charged with ERC20. + function testERC20Mint(address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withERC20 + { + token.setGlobalSalesDetails( + address(erc20), perTokenCost, uint64(block.timestamp - 1), uint64(block.timestamp + 1) + ); + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + uint256 cost = amount * perTokenCost; + + uint256 balanace = erc20.balanceOf(address(this)); + uint256 count = token.balanceOf(mintTo, tokenId); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + assertEq(balanace - cost, erc20.balanceOf(address(this))); + assertEq(cost, erc20.balanceOf(address(token))); + } + + // + // Royalty + // + + // Token royalty fails if the caller doesn't have the ROYALTY_ADMIN_ROLE + function testSetTokenRoyaltyFail(uint256 _tokenId, address _receiver, uint96 _feeNumerator) public { + token.revokeRole(token.ROYALTY_ADMIN_ROLE(), address(this)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + token.setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + } + + // Default royalty fails if the caller doesn't have the ROYALTY_ADMIN_ROLE + function testSetDefaultRoyaltyFail(address _receiver, uint96 _feeNumerator) public { + token.revokeRole(token.ROYALTY_ADMIN_ROLE(), address(this)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + token.setDefaultRoyalty(_receiver, _feeNumerator); + } + + // Royalty calculation for token with custom royalty information + function testRoyaltyInfoCustom(uint256 _tokenId, uint256 _salePrice, address _receiver, uint96 _feeNumerator) + public + { + vm.assume(_receiver != address(0)); + vm.assume(_feeNumerator < 10000); + vm.assume(_salePrice < 10000 ether); + + token.setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + uint256 expectedRoyaltyAmount = (_salePrice * _feeNumerator) / 10000; + + (address receiver, uint256 royaltyAmount) = token.royaltyInfo(_tokenId, _salePrice); + + assertEq(_receiver, receiver); + assertEq(expectedRoyaltyAmount, royaltyAmount); + } + + // Test royalty calculation for token with default royalty information + function testRoyaltyInfoDefault(uint256 _tokenId, uint256 _salePrice, address _receiver, uint96 _feeNumerator) + public + { + vm.assume(_receiver != address(0)); + vm.assume(_feeNumerator < 10000); + vm.assume(_salePrice < 10000 ether); + + token.setDefaultRoyalty(_receiver, _feeNumerator); + uint256 expectedRoyaltyAmount = (_salePrice * _feeNumerator) / 10000; + + (address receiver, uint256 royaltyAmount) = token.royaltyInfo(_tokenId, _salePrice); + + assertEq(_receiver, receiver); + assertEq(expectedRoyaltyAmount, royaltyAmount); + } + + // + // Helpers + // + modifier assumeSafe(address mintTo, uint256 tokenId, uint256 amount) { + vm.assume(mintTo != address(0)); + vm.assume(mintTo.code.length == 0); + vm.assume(tokenId < 100); + vm.assume(amount > 0 && amount < 20); + _; + } + + // Create ERC20. Give this contract 100 ERC20 tokens. Approve token to spend 100 ERC20 tokens. + modifier withERC20() { + erc20 = new ERC20Mock(); + erc20.mockMint(address(this), 100 ether); + erc20.approve(address(token), 100 ether); + _; + } + + modifier withGlobalSaleActive() { + token.setGlobalSalesDetails(address(0), perTokenCost, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + _; + } + + modifier withTokenSaleActive(uint256 tokenId) { + setTokenSaleActive(tokenId); + _; + } + + function setTokenSaleActive(uint256 tokenId) private { + token.setTokenSalesDetails(tokenId, perTokenCost, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + } + + function singleToArray(uint256 value) private pure returns (uint256[] memory) { + uint256[] memory values = new uint256[](1); + values[0] = value; + return values; + } +} From 691ff2bcec8dc1e7c17a8db1c0f9cec76e492bfd Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 2 May 2023 12:54:02 +1200 Subject: [PATCH 17/49] ERC1155 Admin minting --- src/tokens/ERC1155/ERC1155Sale.sol | 15 ++++++++++ test/tokens/ERC1155/ERC1155Sale.t.sol | 42 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol index 9e2a57f..8267777 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -109,6 +109,21 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155MintBurn, ERC1155Meta, ERC1155 _batchMint(_to, _tokenIds, _amounts, _data); } + /** + * Mint tokens as admin. + * @param _to Address to mint tokens to. + * @param _tokenIds Token IDs to mint. + * @param _amounts Amounts of tokens to mint. + * @param _data Data to pass if receiver is contract. + * @notice Only callable by mint admin. + */ + function mintAdmin(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + public + onlyRole(MINT_ADMIN_ROLE) + { + _batchMint(_to, _tokenIds, _amounts, _data); + } + /** * Set the global sale details. * @param _paymentToken The ERC20 token address to accept payment in. address(0) indicates ETH. diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index d98a836..d085ddb 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -238,6 +238,48 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors { assertEq(cost, erc20.balanceOf(address(token))); } + // + // Admin minting + // + + // Admin minting denied when not admin. + function testMintAdminFail(address minter, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + { + vm.assume(minter != address(this)); + vm.assume(minter != address(0)); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(minter), + " is missing role ", + Strings.toHexString(uint256(token.MINT_ADMIN_ROLE()), 32) + ) + ); + vm.prank(minter); + token.mintAdmin(mintTo, tokenIds, amounts, ""); + } + + // Minting as admin success. + function testMintAdminSuccess(address minter, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + { + token.grantRole(token.MINT_ADMIN_ROLE(), minter); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + uint256 count = token.balanceOf(mintTo, tokenId); + token.mintAdmin(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + } + // // Royalty // From 65e36b8816f85ead37b28a03396f4503ca564a1b Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 2 May 2023 13:50:37 +1200 Subject: [PATCH 18/49] Withdraw tests --- src/tokens/ERC1155/ERC1155Sale.sol | 2 + test/tokens/ERC1155/ERC1155Sale.t.sol | 53 ++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol index 8267777..b9932cb 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -190,6 +190,8 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155MintBurn, ERC1155Meta, ERC1155 /** * Withdraws ETH or ERC20 tokens owned by this sale contract. + * @param _to Address to withdraw to. + * @param _amount Amount to withdraw. * @dev Withdraws ERC20 when paymentToken is set, else ETH. * @notice Only callable by the contract admin. */ diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index d085ddb..99e740b 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -348,22 +348,63 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors { assertEq(expectedRoyaltyAmount, royaltyAmount); } + // + // Withdraw + // + + // Withdraw fails if the caller doesn't have the WITHDRAW_ROLE + function testWithdrawFail(address withdrawTo, uint256 amount) public { + token.revokeRole(token.DEFAULT_ADMIN_ROLE(), address(this)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(token.DEFAULT_ADMIN_ROLE()), 32) + ) + ); + token.withdraw(withdrawTo, amount); + } + + // Withdraw success ETH + function testWithdrawETH(address withdrawTo, uint256 tokenId, uint256 amount) public { + // Address 9 doesnt receive ETH + vm.assume(withdrawTo != address(9)); + testMintSingleSuccess(withdrawTo, tokenId, amount); + + uint256 tokenBalance = address(token).balance; + uint256 balance = withdrawTo.balance; + token.withdraw(withdrawTo, tokenBalance); + assertEq(tokenBalance + balance, withdrawTo.balance); + } + + // Withdraw success ERC20 + function testWithdrawERC20(address withdrawTo, uint256 tokenId, uint256 amount) public { + testERC20Mint(withdrawTo, tokenId, amount); + + uint256 tokenBalance = erc20.balanceOf(address(token)); + uint256 balance = erc20.balanceOf(withdrawTo); + token.withdraw(withdrawTo, tokenBalance); + assertEq(tokenBalance + balance, erc20.balanceOf(withdrawTo)); + } + // // Helpers // - modifier assumeSafe(address mintTo, uint256 tokenId, uint256 amount) { - vm.assume(mintTo != address(0)); - vm.assume(mintTo.code.length == 0); + modifier assumeSafe(address nonContract, uint256 tokenId, uint256 amount) { + vm.assume(nonContract != address(0)); + vm.assume(nonContract.code.length == 0); vm.assume(tokenId < 100); vm.assume(amount > 0 && amount < 20); _; } - // Create ERC20. Give this contract 100 ERC20 tokens. Approve token to spend 100 ERC20 tokens. + // Create ERC20. Give this contract 1000 ERC20 tokens. Approve token to spend 100 ERC20 tokens. modifier withERC20() { erc20 = new ERC20Mock(); - erc20.mockMint(address(this), 100 ether); - erc20.approve(address(token), 100 ether); + erc20.mockMint(address(this), 1000 ether); + erc20.approve(address(token), 1000 ether); _; } From ab5749b1e61dc59624cd4ae06a13562aa468b420 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 3 May 2023 09:30:52 +1200 Subject: [PATCH 19/49] Add ERC721 Sale --- src/tokens/ERC1155/ERC1155Sale.sol | 4 +- src/tokens/ERC721/ERC721Sale.sol | 179 ++++++++++++++ .../SaleErrors.sol} | 7 +- test/tokens/ERC1155/ERC1155Sale.t.sol | 4 +- test/tokens/ERC721/ERC721Sale.t.sol | 231 ++++++++++++++++++ 5 files changed, 420 insertions(+), 5 deletions(-) create mode 100644 src/tokens/ERC721/ERC721Sale.sol rename src/{tokens/ERC1155/ERC1155SaleErrors.sol => utils/SaleErrors.sol} (82%) create mode 100644 test/tokens/ERC721/ERC721Sale.t.sol diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol index b9932cb..bf8bf18 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -7,9 +7,9 @@ import {ERC1155MintBurn} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {ERC1155SaleErrors} from "./ERC1155SaleErrors.sol"; +import {SaleErrors} from "../../utils/SaleErrors.sol"; -contract ERC1155Sale is ERC1155SaleErrors, ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981, AccessControl { +contract ERC1155Sale is SaleErrors, ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981, AccessControl { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); diff --git a/src/tokens/ERC721/ERC721Sale.sol b/src/tokens/ERC721/ERC721Sale.sol new file mode 100644 index 0000000..483903b --- /dev/null +++ b/src/tokens/ERC721/ERC721Sale.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import { + ERC721AQueryable, IERC721AQueryable, ERC721A, IERC721A +} from "erc721a/contracts/extensions/ERC721AQueryable.sol"; +import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; + +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SaleErrors} from "../../utils/SaleErrors.sol"; + +contract ERC721Sale is SaleErrors, ERC721AQueryable, ERC2981, AccessControl { + bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); + bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); + + bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = + bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); + + string private baseURI; + + event SaleDetailsUpdated(uint256 amount, address paymentToken, uint64 startTime, uint64 endTime); + + struct SaleDetails { + uint256 amount; + address paymentToken; // ERC20 token address for payment. address(0) indicated payment in ETH. + uint64 startTime; + uint64 endTime; // 0 end time indicates sale inactive + } + + SaleDetails public saleDetails; + + constructor(address owner, string memory _name, string memory _symbol, string memory baseURI_) + ERC721A(_name, _symbol) + { + baseURI = baseURI_; + _setupRole(DEFAULT_ADMIN_ROLE, owner); + _setupRole(MINT_ADMIN_ROLE, owner); + _setupRole(ROYALTY_ADMIN_ROLE, owner); + } + + /** + * Checks if the current block.timestamp is out of the give timestamp range. + * @param startTime Earliest acceptable timestamp (inclusive). + * @param endTime Latest acceptable timestamp (exclusive). + * @dev A zero endTime value is always considered out of bounds. + */ + function blockTimeOutOfBounds(uint256 startTime, uint256 endTime) private view returns (bool) { + // 0 end time indicates inactive sale. + return endTime == 0 || block.timestamp < startTime || block.timestamp >= endTime; + } + + /** + * Checks the sale is active and takes payment. + * @param _amount Amount of tokens to mint. + */ + function payForActiveMint(uint256 _amount) private { + // Active sale test + if (blockTimeOutOfBounds(saleDetails.startTime, saleDetails.endTime)) { + revert GlobalSaleInactive(); + } + + uint256 total = saleDetails.amount * _amount; + address paymentToken = saleDetails.paymentToken; + if (paymentToken == address(0)) { + // Paid in ETH + if (msg.value != total) { + revert InsufficientPayment(total, msg.value); + } + } else { + // Paid in ERC20 + (bool success, bytes memory data) = + paymentToken.call(abi.encodeWithSelector(ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), total)); + if (!success || (data.length > 0 && !abi.decode(data, (bool)))) { + revert InsufficientPayment(total, 0); + } + } + } + + // + // Minting + // + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _amount Amount of tokens to mint. + * @notice Sale must be active for all tokens. + */ + function mint(address _to, uint256 _amount) public payable { + payForActiveMint(_amount); + _mint(_to, _amount); + } + + /** + * Mint tokens as admin. + * @param _to Address to mint tokens to. + * @param _amount Amount of tokens to mint. + * @notice Only callable by mint admin. + */ + function mintAdmin(address _to, uint256 _amount) public onlyRole(MINT_ADMIN_ROLE) { + _mint(_to, _amount); + } + + /** + * Set the sale details. + * @param _paymentToken The ERC20 token address to accept payment in. address(0) indicates ETH. + * @param _amount The amount of payment tokens to accept for each token minted. + * @param _startTime The start time of the sale. Tokens cannot be minted before this time. + * @param _endTime The end time of the sale. Tokens cannot be minted after this time. + * @dev A zero end time indicates an inactive sale. + */ + function setSalesDetails(address _paymentToken, uint256 _amount, uint64 _startTime, uint64 _endTime) + public + onlyRole(MINT_ADMIN_ROLE) + { + saleDetails = SaleDetails(_amount, _paymentToken, _startTime, _endTime); + emit SaleDetailsUpdated(_amount, _paymentToken, _startTime, _endTime); + } + + // + // Royalty + // + + /** + * Sets the royalty information that all ids in this contract will default to. + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + */ + function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) public onlyRole(ROYALTY_ADMIN_ROLE) { + _setDefaultRoyalty(_receiver, _feeNumerator); + } + + // + // Withdraw + // + + /** + * Withdraws ETH or ERC20 tokens owned by this sale contract. + * @param _to Address to withdraw to. + * @param _amount Amount to withdraw. + * @dev Withdraws ERC20 when paymentToken is set, else ETH. + * @notice Only callable by the contract admin. + */ + function withdraw(address _to, uint256 _amount) public onlyRole(DEFAULT_ADMIN_ROLE) { + address paymentToken = saleDetails.paymentToken; + if (paymentToken == address(0)) { + (bool success,) = _to.call{value: _amount}(""); + if (!success) { + revert WithdrawFailed(); + } + } else { + (bool success) = IERC20(paymentToken).transfer(_to, _amount); + if (!success) { + revert WithdrawFailed(); + } + } + } + + // + // Views + // + function supportsInterface(bytes4 _interfaceId) + public + view + override (ERC721A, IERC721A, ERC2981, AccessControl) + returns (bool) + { + return _interfaceId == type(IERC721A).interfaceId || _interfaceId == type(IERC721AQueryable).interfaceId + || ERC721A.supportsInterface(_interfaceId) || super.supportsInterface(_interfaceId); + } + + /** + * Override the ERC721A baseURI function. + */ + function _baseURI() internal view override returns (string memory) { + return baseURI; + } +} diff --git a/src/tokens/ERC1155/ERC1155SaleErrors.sol b/src/utils/SaleErrors.sol similarity index 82% rename from src/tokens/ERC1155/ERC1155SaleErrors.sol rename to src/utils/SaleErrors.sol index b2b36c8..ce0fa1c 100644 --- a/src/tokens/ERC1155/ERC1155SaleErrors.sol +++ b/src/utils/SaleErrors.sol @@ -2,7 +2,12 @@ pragma solidity ^0.8.17; // A contract for errors for extensibility. -contract ERC1155SaleErrors { +contract SaleErrors { + /** + * Sale is not active globally. + */ + error GlobalSaleInactive(); + /** * Sale is not active. * @param tokenId Invalid Token ID. diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index 99e740b..086220d 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; import {ERC1155Sale} from "src/tokens/ERC1155/ERC1155Sale.sol"; -import {ERC1155SaleErrors} from "src/tokens/ERC1155/ERC1155SaleErrors.sol"; +import {SaleErrors} from "src/utils/SaleErrors.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -14,7 +14,7 @@ import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; -contract ERC1155SaleTest is Test, ERC1155SaleErrors { +contract ERC1155SaleTest is Test, SaleErrors { // Redeclare events event TransferBatch( address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts diff --git a/test/tokens/ERC721/ERC721Sale.t.sol b/test/tokens/ERC721/ERC721Sale.t.sol new file mode 100644 index 0000000..c20a5a6 --- /dev/null +++ b/test/tokens/ERC721/ERC721Sale.t.sol @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import {ERC721Sale} from "src/tokens/ERC721/ERC721Sale.sol"; +import {SaleErrors} from "src/utils/SaleErrors.sol"; + +import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +// Interfaces +import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {IERC721A} from "erc721a/contracts/interfaces/IERC721A.sol"; +import {IERC721AQueryable} from "erc721a/contracts/interfaces/IERC721AQueryable.sol"; +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; + +contract ERC721SaleTest is Test, SaleErrors { + // Redeclare events + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + ERC721Sale private token; + ERC20Mock private erc20; + uint256 private perTokenCost = 0.02 ether; + + function setUp() public { + token = new ERC721Sale(address(this), "test", "test", "ipfs://"); + + vm.deal(address(this), 100 ether); + } + + function testSupportsInterface() public { + assertTrue(token.supportsInterface(type(IERC165).interfaceId)); + assertTrue(token.supportsInterface(type(IERC721).interfaceId)); + assertTrue(token.supportsInterface(type(IERC721A).interfaceId)); + assertTrue(token.supportsInterface(type(IERC721AQueryable).interfaceId)); + assertTrue(token.supportsInterface(type(IAccessControl).interfaceId)); + } + + // + // Minting + // + + // Minting denied when no sale active. + function testMintInactiveFail(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) { + vm.expectRevert(abi.encodeWithSelector(GlobalSaleInactive.selector)); + token.mint{value: amount * perTokenCost}(mintTo, amount); + } + + // Minting denied when sale is expired. + function testMintExpiredFail(address mintTo, uint256 amount, uint256 startTime, uint256 endTime) + public + assumeSafe(mintTo, amount) + { + vm.assume(startTime > endTime); + vm.assume(block.timestamp < startTime || block.timestamp >= endTime); + token.setSalesDetails(address(0), perTokenCost, uint64(startTime), uint64(endTime)); + + vm.expectRevert(abi.encodeWithSelector(GlobalSaleInactive.selector)); + token.mint{value: amount * perTokenCost}(mintTo, amount); + } + + // Minting allowed when sale is active. + function testMintSuccess(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) withSaleActive { + uint256 count = token.balanceOf(mintTo); + vm.expectEmit(true, true, true, true, address(token)); + emit Transfer(address(0), mintTo, 0); + token.mint{value: amount * perTokenCost}(mintTo, amount); + assertEq(count + amount, token.balanceOf(mintTo)); + } + + // Minting allowed when sale is free. + function testFreeMint(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) { + token.setSalesDetails(address(0), 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + + uint256 count = token.balanceOf(mintTo); + vm.expectEmit(true, true, true, true, address(token)); + emit Transfer(address(0), mintTo, 0); + token.mint(mintTo, amount); + assertEq(count + amount, token.balanceOf(mintTo)); + } + + // Minting allowed when mint charged with ERC20. + function testERC20Mint(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) withERC20 { + token.setSalesDetails(address(erc20), perTokenCost, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + uint256 cost = amount * perTokenCost; + + uint256 balanace = erc20.balanceOf(address(this)); + uint256 count = token.balanceOf(mintTo); + vm.expectEmit(true, true, true, true, address(token)); + emit Transfer(address(0), mintTo, 0); + token.mint(mintTo, amount); + assertEq(count + amount, token.balanceOf(mintTo)); + assertEq(balanace - cost, erc20.balanceOf(address(this))); + assertEq(cost, erc20.balanceOf(address(token))); + } + + // + // Admin minting + // + + // Admin minting denied when not admin. + function testMintAdminFail(address minter, address mintTo, uint256 amount) public assumeSafe(mintTo, amount) { + vm.assume(minter != address(this)); + vm.assume(minter != address(0)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(minter), + " is missing role ", + Strings.toHexString(uint256(token.MINT_ADMIN_ROLE()), 32) + ) + ); + vm.prank(minter); + token.mintAdmin(mintTo, amount); + } + + // Minting as admin success. + function testMintAdminSuccess(address minter, address mintTo, uint256 amount) public assumeSafe(mintTo, amount) { + token.grantRole(token.MINT_ADMIN_ROLE(), minter); + + uint256 count = token.balanceOf(mintTo); + token.mintAdmin(mintTo, amount); + assertEq(count + amount, token.balanceOf(mintTo)); + } + + // + // Royalty + // + + // Set royalty fails if the caller doesn't have the ROYALTY_ADMIN_ROLE + function testSetRoyaltyFail(address _receiver, uint96 _feeNumerator) public { + token.revokeRole(token.ROYALTY_ADMIN_ROLE(), address(this)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + token.setDefaultRoyalty(_receiver, _feeNumerator); + } + + // Test royalty calculation for token with royalty information + function testRoyaltyInfo(uint256 _salePrice, address _receiver, uint96 _feeNumerator) public { + vm.assume(_receiver != address(0)); + vm.assume(_feeNumerator < 10000); + vm.assume(_salePrice < 10000 ether); + + token.setDefaultRoyalty(_receiver, _feeNumerator); + uint256 expectedRoyaltyAmount = (_salePrice * _feeNumerator) / 10000; + + (address receiver, uint256 royaltyAmount) = token.royaltyInfo(0, _salePrice); + + assertEq(_receiver, receiver); + assertEq(expectedRoyaltyAmount, royaltyAmount); + } + + // + // Withdraw + // + + // Withdraw fails if the caller doesn't have the WITHDRAW_ROLE + function testWithdrawFail(address withdrawTo, uint256 amount) public { + token.revokeRole(token.DEFAULT_ADMIN_ROLE(), address(this)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(token.DEFAULT_ADMIN_ROLE()), 32) + ) + ); + token.withdraw(withdrawTo, amount); + } + + // Withdraw success ETH + function testWithdrawETH(address withdrawTo, uint256 amount) public { + // Address 9 doesnt receive ETH + vm.assume(withdrawTo != address(9)); + testMintSuccess(withdrawTo, amount); + + uint256 tokenBalance = address(token).balance; + uint256 balance = withdrawTo.balance; + token.withdraw(withdrawTo, tokenBalance); + assertEq(tokenBalance + balance, withdrawTo.balance); + } + + // Withdraw success ERC20 + function testWithdrawERC20(address withdrawTo, uint256 amount) public { + testERC20Mint(withdrawTo, amount); + + uint256 tokenBalance = erc20.balanceOf(address(token)); + uint256 balance = erc20.balanceOf(withdrawTo); + token.withdraw(withdrawTo, tokenBalance); + assertEq(tokenBalance + balance, erc20.balanceOf(withdrawTo)); + } + + // + // Helpers + // + modifier assumeSafe(address nonContract, uint256 amount) { + vm.assume(nonContract != address(0)); + vm.assume(nonContract.code.length == 0); + vm.assume(amount > 0 && amount < 20); + _; + } + + // Create ERC20. Give this contract 1000 ERC20 tokens. Approve token to spend 100 ERC20 tokens. + modifier withERC20() { + erc20 = new ERC20Mock(); + erc20.mockMint(address(this), 1000 ether); + erc20.approve(address(token), 1000 ether); + _; + } + + modifier withSaleActive() { + token.setSalesDetails(address(0), perTokenCost, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + _; + } + + function singleToArray(uint256 value) private pure returns (uint256[] memory) { + uint256[] memory values = new uint256[](1); + values[0] = value; + return values; + } +} From 0b74bac32d414d2f3fd0eea457efa818a2c56f7a Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 3 May 2023 13:13:41 +1200 Subject: [PATCH 20/49] Add supply cap to sale contracts --- src/tokens/ERC1155/ERC1155Sale.sol | 62 +++++---- .../ERC1155/ERC1155SaleErrors.sol} | 2 +- src/tokens/ERC1155/ERC1155Supply.sol | 122 ++++++++++++++++++ src/tokens/ERC1155/ERC1155SupplyErrors.sol | 15 +++ src/tokens/ERC721/ERC721Sale.sol | 33 +++-- src/tokens/ERC721/ERC721SaleErrors.sol | 30 +++++ test/tokens/ERC1155/ERC1155Sale.t.sol | 59 +++++++-- test/tokens/ERC721/ERC721Sale.t.sol | 29 +++-- 8 files changed, 301 insertions(+), 51 deletions(-) rename src/{utils/SaleErrors.sol => tokens/ERC1155/ERC1155SaleErrors.sol} (94%) create mode 100644 src/tokens/ERC1155/ERC1155Supply.sol create mode 100644 src/tokens/ERC1155/ERC1155SupplyErrors.sol create mode 100644 src/tokens/ERC721/ERC721SaleErrors.sol diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol index bf8bf18..13ca595 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -3,27 +3,27 @@ pragma solidity ^0.8.17; import {ERC1155, ERC1155Meta} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Meta.sol"; import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -import {ERC1155MintBurn} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155MintBurn.sol"; +import {ERC1155Supply} from "./ERC1155Supply.sol"; +import {ERC1155SaleErrors} from "./ERC1155SaleErrors.sol"; import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SaleErrors} from "../../utils/SaleErrors.sol"; -contract ERC1155Sale is SaleErrors, ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981, AccessControl { +contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Metadata, ERC2981, AccessControl { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); - event GlobalSaleDetailsUpdated(uint256 amount, uint64 startTime, uint64 endTime); - event TokenSaleDetailsUpdated(uint256 tokenId, uint256 amount, uint64 startTime, uint64 endTime); + event GlobalSaleDetailsUpdated(uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime); + event TokenSaleDetailsUpdated(uint256 tokenId, uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime); // ERC20 token address for payment. address(0) indicated payment in ETH. address public paymentToken; struct SaleDetails { - uint256 amount; + uint256 cost; uint64 startTime; uint64 endTime; // 0 end time indicates sale inactive } @@ -54,7 +54,8 @@ contract ERC1155Sale is SaleErrors, ERC1155MintBurn, ERC1155Meta, ERC1155Metadat * @param _amounts Amounts of tokens to mint. */ function payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts) private { - uint256 total; + uint256 totalAmount; + uint256 totalCost; bool globalSaleInactive = blockTimeOutOfBounds(globalSaleDetails.startTime, globalSaleDetails.endTime); for (uint256 i; i < _tokenIds.length; i++) { // Active sale test @@ -66,25 +67,26 @@ contract ERC1155Sale is SaleErrors, ERC1155MintBurn, ERC1155Meta, ERC1155Metadat // Both sales inactive revert SaleInactive(_tokenIds[i]); } - // Use global sale price - total += globalSaleDetails.amount * _amounts[i]; + // Use global sale details + totalCost += globalSaleDetails.cost * _amounts[i]; } else { // Use token sale price - total += saleDetails.amount * _amounts[i]; + totalCost += saleDetails.cost * _amounts[i]; } + totalAmount += _amounts[i]; } if (paymentToken == address(0)) { // Paid in ETH - if (msg.value != total) { - revert InsufficientPayment(total, msg.value); + if (msg.value != totalCost) { + revert InsufficientPayment(totalCost, msg.value); } } else { // Paid in ERC20 (bool success, bytes memory data) = - paymentToken.call(abi.encodeWithSelector(ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), total)); + paymentToken.call(abi.encodeWithSelector(ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), totalCost)); if (!success || (data.length > 0 && !abi.decode(data, (bool)))) { - revert InsufficientPayment(total, 0); + revert InsufficientPayment(totalCost, 0); } } } @@ -127,34 +129,50 @@ contract ERC1155Sale is SaleErrors, ERC1155MintBurn, ERC1155Meta, ERC1155Metadat /** * Set the global sale details. * @param _paymentToken The ERC20 token address to accept payment in. address(0) indicates ETH. - * @param _amount The amount of payment tokens to accept for each token minted. + * @param _cost The amount of payment tokens to accept for each token minted. + * @param _supplyCap The maximum number of tokens that can be minted. * @param _startTime The start time of the sale. Tokens cannot be minted before this time. * @param _endTime The end time of the sale. Tokens cannot be minted after this time. * @dev A zero end time indicates an inactive sale. */ - function setGlobalSalesDetails(address _paymentToken, uint256 _amount, uint64 _startTime, uint64 _endTime) + function setGlobalSaleDetails( + address _paymentToken, + uint256 _cost, + uint256 _supplyCap, + uint64 _startTime, + uint64 _endTime + ) public onlyRole(MINT_ADMIN_ROLE) { paymentToken = _paymentToken; - globalSaleDetails = SaleDetails(_amount, _startTime, _endTime); - emit GlobalSaleDetailsUpdated(_amount, _startTime, _endTime); + totalSupplyCap = _supplyCap; + globalSaleDetails = SaleDetails(_cost, _startTime, _endTime); + emit GlobalSaleDetailsUpdated(_cost, _supplyCap, _startTime, _endTime); } /** * Set the sale details for an individual token. * @param _tokenId The token ID to set the sale details for. - * @param _amount The amount of payment tokens to accept for each token minted. + * @param _cost The amount of payment tokens to accept for each token minted. + * @param _supplyCap The maximum number of tokens that can be minted. * @param _startTime The start time of the sale. Tokens cannot be minted before this time. * @param _endTime The end time of the sale. Tokens cannot be minted after this time. * @dev A zero end time indicates an inactive sale. */ - function setTokenSalesDetails(uint256 _tokenId, uint256 _amount, uint64 _startTime, uint64 _endTime) + function setTokenSaleDetails( + uint256 _tokenId, + uint256 _cost, + uint256 _supplyCap, + uint64 _startTime, + uint64 _endTime + ) public onlyRole(MINT_ADMIN_ROLE) { - tokenSaleDetails[_tokenId] = SaleDetails(_amount, _startTime, _endTime); - emit TokenSaleDetailsUpdated(_tokenId, _amount, _startTime, _endTime); + tokenSupplyCap[_tokenId] = _supplyCap; + tokenSaleDetails[_tokenId] = SaleDetails(_cost, _startTime, _endTime); + emit TokenSaleDetailsUpdated(_tokenId, _cost, _supplyCap, _startTime, _endTime); } // diff --git a/src/utils/SaleErrors.sol b/src/tokens/ERC1155/ERC1155SaleErrors.sol similarity index 94% rename from src/utils/SaleErrors.sol rename to src/tokens/ERC1155/ERC1155SaleErrors.sol index ce0fa1c..b8f1211 100644 --- a/src/utils/SaleErrors.sol +++ b/src/tokens/ERC1155/ERC1155SaleErrors.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; // A contract for errors for extensibility. -contract SaleErrors { +abstract contract ERC1155SaleErrors { /** * Sale is not active globally. */ diff --git a/src/tokens/ERC1155/ERC1155Supply.sol b/src/tokens/ERC1155/ERC1155Supply.sol new file mode 100644 index 0000000..bbd092c --- /dev/null +++ b/src/tokens/ERC1155/ERC1155Supply.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC1155} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155.sol"; +import {ERC1155SupplyErrors} from "./ERC1155SupplyErrors.sol"; + +/** + * An ERC1155 extension that tracks token supply. + */ +contract ERC1155Supply is ERC1155, ERC1155SupplyErrors { + // Maximum supply globally and per token. 0 indicates unlimited supply + uint256 public totalSupplyCap; + mapping(uint256 => uint256) public tokenSupplyCap; + + uint256 public totalSupply; + mapping(uint256 => uint256) public tokenSupply; + + /** + * Mint _amount of tokens of a given id + * @param _to The address to mint tokens to + * @param _id Token id to mint + * @param _amount The amount to be minted + * @param _data Data to pass if receiver is contract + */ + function _mint(address _to, uint256 _id, uint256 _amount, bytes memory _data) internal { + // Check supply cap + totalSupply += _amount; + if (totalSupplyCap > 0 && totalSupply > totalSupplyCap) { + revert InsufficientSupply(); + } + tokenSupply[_id] += _amount; + if (tokenSupplyCap[_id] > 0 && tokenSupply[_id] > tokenSupplyCap[_id]) { + revert InsufficientSupply(); + } + + // Add _amount + balances[_to][_id] += _amount; + + // Emit event + emit TransferSingle(msg.sender, address(0x0), _to, _id, _amount); + + // Calling onReceive method if recipient is contract + _callonERC1155Received(address(0x0), _to, _id, _amount, gasleft(), _data); + } + + /** + * Mint tokens for each ids in _ids + * @param _to The address to mint tokens to + * @param _ids Array of ids to mint + * @param _amounts Array of amount of tokens to mint per id + * @param _data Data to pass if receiver is contract + */ + function _batchMint(address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data) internal { + uint256 nMint = _ids.length; + if (nMint != _amounts.length) { + revert InvalidArrayLength(); + } + + // Executing all minting + for (uint256 i = 0; i < nMint; i++) { + // Update storage balance + balances[_to][_ids[i]] += _amounts[i]; + totalSupply += _amounts[i]; + tokenSupply[_ids[i]] += _amounts[i]; + if (tokenSupplyCap[_ids[i]] > 0 && tokenSupply[_ids[i]] > tokenSupplyCap[_ids[i]]) { + revert InsufficientSupply(); + } + } + if (totalSupplyCap > 0 && totalSupply > totalSupplyCap) { + revert InsufficientSupply(); + } + + // Emit batch mint event + emit TransferBatch(msg.sender, address(0x0), _to, _ids, _amounts); + + // Calling onReceive method if recipient is contract + _callonERC1155BatchReceived(address(0x0), _to, _ids, _amounts, gasleft(), _data); + } + + /** + * Burn _amount of tokens of a given token id + * @param _from The address to burn tokens from + * @param _id Token id to burn + * @param _amount The amount to be burned + */ + function _burn(address _from, uint256 _id, uint256 _amount) internal { + // Supply + totalSupply -= _amount; + tokenSupply[_id] -= _amount; + + // Balances + balances[_from][_id] -= _amount; + + // Emit event + emit TransferSingle(msg.sender, _from, address(0x0), _id, _amount); + } + + /** + * Burn tokens of given token id for each (_ids[i], _amounts[i]) pair + * @param _from The address to burn tokens from + * @param _ids Array of token ids to burn + * @param _amounts Array of the amount to be burned + */ + function _batchBurn(address _from, uint256[] memory _ids, uint256[] memory _amounts) internal { + // Number of mints to execute + uint256 nBurn = _ids.length; + if (nBurn != _amounts.length) { + revert InvalidArrayLength(); + } + + // Executing all minting + for (uint256 i = 0; i < nBurn; i++) { + // Update balances + balances[_from][_ids[i]] -= _amounts[i]; + totalSupply -= _amounts[i]; + tokenSupply[_ids[i]] -= _amounts[i]; + } + + // Emit batch mint event + emit TransferBatch(msg.sender, _from, address(0x0), _ids, _amounts); + } +} diff --git a/src/tokens/ERC1155/ERC1155SupplyErrors.sol b/src/tokens/ERC1155/ERC1155SupplyErrors.sol new file mode 100644 index 0000000..c439891 --- /dev/null +++ b/src/tokens/ERC1155/ERC1155SupplyErrors.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +// A contract for errors for extensibility. +abstract contract ERC1155SupplyErrors { + /** + * Insufficient supply. + */ + error InsufficientSupply(); + + /** + * Invalid array input length. + */ + error InvalidArrayLength(); +} diff --git a/src/tokens/ERC721/ERC721Sale.sol b/src/tokens/ERC721/ERC721Sale.sol index 483903b..05ee6fb 100644 --- a/src/tokens/ERC721/ERC721Sale.sol +++ b/src/tokens/ERC721/ERC721Sale.sol @@ -4,13 +4,13 @@ pragma solidity ^0.8.17; import { ERC721AQueryable, IERC721AQueryable, ERC721A, IERC721A } from "erc721a/contracts/extensions/ERC721AQueryable.sol"; +import {ERC721SaleErrors} from "./ERC721SaleErrors.sol"; import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SaleErrors} from "../../utils/SaleErrors.sol"; -contract ERC721Sale is SaleErrors, ERC721AQueryable, ERC2981, AccessControl { +contract ERC721Sale is ERC721AQueryable, ERC2981, AccessControl, ERC721SaleErrors { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); @@ -19,10 +19,11 @@ contract ERC721Sale is SaleErrors, ERC721AQueryable, ERC2981, AccessControl { string private baseURI; - event SaleDetailsUpdated(uint256 amount, address paymentToken, uint64 startTime, uint64 endTime); + event SaleDetailsUpdated(uint256 supplyCap, uint256 cost, address paymentToken, uint64 startTime, uint64 endTime); struct SaleDetails { - uint256 amount; + uint256 supplyCap; // 0 supply cap indicates unlimited supply + uint256 cost; address paymentToken; // ERC20 token address for payment. address(0) indicated payment in ETH. uint64 startTime; uint64 endTime; // 0 end time indicates sale inactive @@ -57,10 +58,10 @@ contract ERC721Sale is SaleErrors, ERC721AQueryable, ERC2981, AccessControl { function payForActiveMint(uint256 _amount) private { // Active sale test if (blockTimeOutOfBounds(saleDetails.startTime, saleDetails.endTime)) { - revert GlobalSaleInactive(); + revert SaleInactive(); } - uint256 total = saleDetails.amount * _amount; + uint256 total = saleDetails.cost * _amount; address paymentToken = saleDetails.paymentToken; if (paymentToken == address(0)) { // Paid in ETH @@ -88,6 +89,11 @@ contract ERC721Sale is SaleErrors, ERC721AQueryable, ERC2981, AccessControl { * @notice Sale must be active for all tokens. */ function mint(address _to, uint256 _amount) public payable { + uint256 currentSupply = ERC721A.totalSupply(); + uint256 supplyCap = saleDetails.supplyCap; + if (supplyCap > 0 && currentSupply + _amount > supplyCap) { + revert InsufficientSupply(currentSupply, _amount, supplyCap); + } payForActiveMint(_amount); _mint(_to, _amount); } @@ -104,18 +110,25 @@ contract ERC721Sale is SaleErrors, ERC721AQueryable, ERC2981, AccessControl { /** * Set the sale details. + * @param _supplyCap The maximum number of tokens that can be minted. 0 indicates unlimited supply. + * @param _cost The amount of payment tokens to accept for each token minted. * @param _paymentToken The ERC20 token address to accept payment in. address(0) indicates ETH. - * @param _amount The amount of payment tokens to accept for each token minted. * @param _startTime The start time of the sale. Tokens cannot be minted before this time. * @param _endTime The end time of the sale. Tokens cannot be minted after this time. * @dev A zero end time indicates an inactive sale. */ - function setSalesDetails(address _paymentToken, uint256 _amount, uint64 _startTime, uint64 _endTime) + function setSaleDetails( + uint256 _supplyCap, + uint256 _cost, + address _paymentToken, + uint64 _startTime, + uint64 _endTime + ) public onlyRole(MINT_ADMIN_ROLE) { - saleDetails = SaleDetails(_amount, _paymentToken, _startTime, _endTime); - emit SaleDetailsUpdated(_amount, _paymentToken, _startTime, _endTime); + saleDetails = SaleDetails(_supplyCap, _cost, _paymentToken, _startTime, _endTime); + emit SaleDetailsUpdated(_supplyCap, _cost, _paymentToken, _startTime, _endTime); } // diff --git a/src/tokens/ERC721/ERC721SaleErrors.sol b/src/tokens/ERC721/ERC721SaleErrors.sol new file mode 100644 index 0000000..e993fe5 --- /dev/null +++ b/src/tokens/ERC721/ERC721SaleErrors.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +// A contract for errors for extensibility. +abstract contract ERC721SaleErrors { + /** + * Sale is not active. + */ + error SaleInactive(); + + /** + * Insufficient supply. + * @param currentSupply Current supply. + * @param amount Amount to mint. + * @param maxSupply Maximum supply. + */ + error InsufficientSupply(uint256 currentSupply, uint256 amount, uint256 maxSupply); + + /** + * Insufficient tokens for payment. + * @param expected Expected amount of tokens. + * @param actual Actual amount of tokens. + */ + error InsufficientPayment(uint256 expected, uint256 actual); + + /** + * Withdraw failed. + */ + error WithdrawFailed(); +} diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index 086220d..19f94cb 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; import {ERC1155Sale} from "src/tokens/ERC1155/ERC1155Sale.sol"; -import {SaleErrors} from "src/utils/SaleErrors.sol"; +import {ERC1155SaleErrors} from "src/tokens/ERC1155/ERC1155SaleErrors.sol"; +import {ERC1155SupplyErrors} from "src/tokens/ERC1155/ERC1155SupplyErrors.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -14,7 +15,7 @@ import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; -contract ERC1155SaleTest is Test, SaleErrors { +contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { // Redeclare events event TransferBatch( address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts @@ -79,7 +80,7 @@ contract ERC1155SaleTest is Test, SaleErrors { { vm.assume(startTime > endTime); vm.assume(block.timestamp < startTime || block.timestamp >= endTime); - token.setTokenSalesDetails(tokenId, perTokenCost, uint64(startTime), uint64(endTime)); + token.setTokenSaleDetails(tokenId, perTokenCost, 0, uint64(startTime), uint64(endTime)); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -101,7 +102,7 @@ contract ERC1155SaleTest is Test, SaleErrors { { vm.assume(startTime > endTime); vm.assume(block.timestamp < startTime || block.timestamp >= endTime); - token.setGlobalSalesDetails(address(0), perTokenCost, uint64(startTime), uint64(endTime)); + token.setGlobalSaleDetails(address(0), perTokenCost, 0, uint64(startTime), uint64(endTime)); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -127,6 +128,42 @@ contract ERC1155SaleTest is Test, SaleErrors { token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, ""); } + // Minting denied when global supply exceeded. + function testMintGlobalSupplyExceeded(address mintTo, uint256 tokenId, uint256 amount, uint256 supplyCap) + public + assumeSafe(mintTo, tokenId, amount) + { + vm.assume(supplyCap > 0); + vm.assume(amount > supplyCap); + token.setGlobalSaleDetails( + address(0), perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) + ); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(InsufficientSupply.selector); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when token supply exceeded. + function testMintTokenSupplyExceeded(address mintTo, uint256 tokenId, uint256 amount, uint256 supplyCap) + public + assumeSafe(mintTo, tokenId, amount) + { + vm.assume(supplyCap > 0); + vm.assume(amount > supplyCap); + token.setTokenSaleDetails( + tokenId, perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) + ); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(InsufficientSupply.selector); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + // Minting allowed when sale is active globally. function testMintGlobalSuccess(address mintTo, uint256 tokenId, uint256 amount) public @@ -187,7 +224,7 @@ contract ERC1155SaleTest is Test, SaleErrors { public assumeSafe(mintTo, tokenId, amount) { - token.setGlobalSalesDetails(address(0), 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setGlobalSaleDetails(address(0), 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -204,7 +241,7 @@ contract ERC1155SaleTest is Test, SaleErrors { assumeSafe(mintTo, tokenId, amount) withGlobalSaleActive { - token.setTokenSalesDetails(tokenId, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setTokenSaleDetails(tokenId, 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -221,8 +258,8 @@ contract ERC1155SaleTest is Test, SaleErrors { assumeSafe(mintTo, tokenId, amount) withERC20 { - token.setGlobalSalesDetails( - address(erc20), perTokenCost, uint64(block.timestamp - 1), uint64(block.timestamp + 1) + token.setGlobalSaleDetails( + address(erc20), perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1) ); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -409,7 +446,9 @@ contract ERC1155SaleTest is Test, SaleErrors { } modifier withGlobalSaleActive() { - token.setGlobalSalesDetails(address(0), perTokenCost, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setGlobalSaleDetails( + address(0), perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1) + ); _; } @@ -419,7 +458,7 @@ contract ERC1155SaleTest is Test, SaleErrors { } function setTokenSaleActive(uint256 tokenId) private { - token.setTokenSalesDetails(tokenId, perTokenCost, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setTokenSaleDetails(tokenId, perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); } function singleToArray(uint256 value) private pure returns (uint256[] memory) { diff --git a/test/tokens/ERC721/ERC721Sale.t.sol b/test/tokens/ERC721/ERC721Sale.t.sol index c20a5a6..a4d110a 100644 --- a/test/tokens/ERC721/ERC721Sale.t.sol +++ b/test/tokens/ERC721/ERC721Sale.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; import {ERC721Sale} from "src/tokens/ERC721/ERC721Sale.sol"; -import {SaleErrors} from "src/utils/SaleErrors.sol"; +import {ERC721SaleErrors} from "src/tokens/ERC721/ERC721SaleErrors.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -15,7 +15,7 @@ import {IERC721A} from "erc721a/contracts/interfaces/IERC721A.sol"; import {IERC721AQueryable} from "erc721a/contracts/interfaces/IERC721AQueryable.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; -contract ERC721SaleTest is Test, SaleErrors { +contract ERC721SaleTest is Test, ERC721SaleErrors { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); @@ -43,7 +43,7 @@ contract ERC721SaleTest is Test, SaleErrors { // Minting denied when no sale active. function testMintInactiveFail(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) { - vm.expectRevert(abi.encodeWithSelector(GlobalSaleInactive.selector)); + vm.expectRevert(SaleInactive.selector); token.mint{value: amount * perTokenCost}(mintTo, amount); } @@ -54,9 +54,22 @@ contract ERC721SaleTest is Test, SaleErrors { { vm.assume(startTime > endTime); vm.assume(block.timestamp < startTime || block.timestamp >= endTime); - token.setSalesDetails(address(0), perTokenCost, uint64(startTime), uint64(endTime)); + token.setSaleDetails(0, perTokenCost, address(0), uint64(startTime), uint64(endTime)); - vm.expectRevert(abi.encodeWithSelector(GlobalSaleInactive.selector)); + vm.expectRevert(SaleInactive.selector); + token.mint{value: amount * perTokenCost}(mintTo, amount); + } + + // Minting denied when supply exceeded. + function testMintSupplyExceeded(address mintTo, uint256 amount, uint256 supplyCap) + public + assumeSafe(mintTo, amount) + { + vm.assume(supplyCap > 0); + vm.assume(amount > supplyCap); + token.setSaleDetails(supplyCap, perTokenCost, address(0), uint64(block.timestamp), uint64(block.timestamp + 1)); + + vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap)); token.mint{value: amount * perTokenCost}(mintTo, amount); } @@ -71,7 +84,7 @@ contract ERC721SaleTest is Test, SaleErrors { // Minting allowed when sale is free. function testFreeMint(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) { - token.setSalesDetails(address(0), 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1)); uint256 count = token.balanceOf(mintTo); vm.expectEmit(true, true, true, true, address(token)); @@ -82,7 +95,7 @@ contract ERC721SaleTest is Test, SaleErrors { // Minting allowed when mint charged with ERC20. function testERC20Mint(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) withERC20 { - token.setSalesDetails(address(erc20), perTokenCost, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setSaleDetails(0, perTokenCost, address(erc20), uint64(block.timestamp - 1), uint64(block.timestamp + 1)); uint256 cost = amount * perTokenCost; uint256 balanace = erc20.balanceOf(address(this)); @@ -219,7 +232,7 @@ contract ERC721SaleTest is Test, SaleErrors { } modifier withSaleActive() { - token.setSalesDetails(address(0), perTokenCost, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setSaleDetails(0, perTokenCost, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1)); _; } From c72086910a8409c6ab9318e204a7dc3bc1186e31 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Fri, 5 May 2023 14:45:02 +1200 Subject: [PATCH 21/49] Add ERCXXXSale interfaces --- src/tokens/ERC1155/ERC1155Sale.sol | 84 ++++++++++++++++++++--------- src/tokens/ERC1155/IERC1155Sale.sol | 48 +++++++++++++++++ src/tokens/ERC721/ERC721Sale.sol | 35 ++++++------ src/tokens/ERC721/IERC721Sale.sol | 28 ++++++++++ 4 files changed, 151 insertions(+), 44 deletions(-) create mode 100644 src/tokens/ERC1155/IERC1155Sale.sol create mode 100644 src/tokens/ERC721/IERC721Sale.sol diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol index 13ca595..aa4ba0b 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; +import {IERC1155Sale} from "./IERC1155Sale.sol"; import {ERC1155, ERC1155Meta} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Meta.sol"; import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; import {ERC1155Supply} from "./ERC1155Supply.sol"; @@ -9,27 +10,26 @@ import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Metadata, ERC2981, AccessControl { +contract ERC1155Sale is + IERC1155Sale, + ERC1155SaleErrors, + ERC1155Supply, + ERC1155Meta, + ERC1155Metadata, + ERC2981, + AccessControl +{ bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); - event GlobalSaleDetailsUpdated(uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime); - event TokenSaleDetailsUpdated(uint256 tokenId, uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime); - // ERC20 token address for payment. address(0) indicated payment in ETH. - address public paymentToken; - - struct SaleDetails { - uint256 cost; - uint64 startTime; - uint64 endTime; // 0 end time indicates sale inactive - } + address private _paymentToken; - SaleDetails public globalSaleDetails; - mapping(uint256 => SaleDetails) public tokenSaleDetails; + SaleDetails private _globalSaleDetails; + mapping(uint256 => SaleDetails) private _tokenSaleDetails; constructor(address owner, string memory _name, string memory _baseURI) ERC1155Metadata(_name, _baseURI) { _setupRole(DEFAULT_ADMIN_ROLE, owner); @@ -56,10 +56,11 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Me function payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts) private { uint256 totalAmount; uint256 totalCost; - bool globalSaleInactive = blockTimeOutOfBounds(globalSaleDetails.startTime, globalSaleDetails.endTime); + SaleDetails memory gSaleDetails = _globalSaleDetails; + bool globalSaleInactive = blockTimeOutOfBounds(gSaleDetails.startTime, gSaleDetails.endTime); for (uint256 i; i < _tokenIds.length; i++) { // Active sale test - SaleDetails memory saleDetails = tokenSaleDetails[_tokenIds[i]]; + SaleDetails memory saleDetails = _tokenSaleDetails[_tokenIds[i]]; bool tokenSaleInactive = blockTimeOutOfBounds(saleDetails.startTime, saleDetails.endTime); if (tokenSaleInactive) { // Prefer token sale @@ -68,7 +69,7 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Me revert SaleInactive(_tokenIds[i]); } // Use global sale details - totalCost += globalSaleDetails.cost * _amounts[i]; + totalCost += gSaleDetails.cost * _amounts[i]; } else { // Use token sale price totalCost += saleDetails.cost * _amounts[i]; @@ -76,15 +77,16 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Me totalAmount += _amounts[i]; } - if (paymentToken == address(0)) { + if (_paymentToken == address(0)) { // Paid in ETH if (msg.value != totalCost) { revert InsufficientPayment(totalCost, msg.value); } } else { // Paid in ERC20 - (bool success, bytes memory data) = - paymentToken.call(abi.encodeWithSelector(ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), totalCost)); + (bool success, bytes memory data) = _paymentToken.call( + abi.encodeWithSelector(ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), totalCost) + ); if (!success || (data.length > 0 && !abi.decode(data, (bool)))) { revert InsufficientPayment(totalCost, 0); } @@ -128,7 +130,7 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Me /** * Set the global sale details. - * @param _paymentToken The ERC20 token address to accept payment in. address(0) indicates ETH. + * @param _paymentTokenAddr The ERC20 token address to accept payment in. address(0) indicates ETH. * @param _cost The amount of payment tokens to accept for each token minted. * @param _supplyCap The maximum number of tokens that can be minted. * @param _startTime The start time of the sale. Tokens cannot be minted before this time. @@ -136,7 +138,7 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Me * @dev A zero end time indicates an inactive sale. */ function setGlobalSaleDetails( - address _paymentToken, + address _paymentTokenAddr, uint256 _cost, uint256 _supplyCap, uint64 _startTime, @@ -145,9 +147,9 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Me public onlyRole(MINT_ADMIN_ROLE) { - paymentToken = _paymentToken; + _paymentToken = _paymentTokenAddr; totalSupplyCap = _supplyCap; - globalSaleDetails = SaleDetails(_cost, _startTime, _endTime); + _globalSaleDetails = SaleDetails(_cost, _startTime, _endTime); emit GlobalSaleDetailsUpdated(_cost, _supplyCap, _startTime, _endTime); } @@ -171,7 +173,7 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Me onlyRole(MINT_ADMIN_ROLE) { tokenSupplyCap[_tokenId] = _supplyCap; - tokenSaleDetails[_tokenId] = SaleDetails(_cost, _startTime, _endTime); + _tokenSaleDetails[_tokenId] = SaleDetails(_cost, _startTime, _endTime); emit TokenSaleDetailsUpdated(_tokenId, _cost, _supplyCap, _startTime, _endTime); } @@ -214,13 +216,13 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Me * @notice Only callable by the contract admin. */ function withdraw(address _to, uint256 _amount) public onlyRole(DEFAULT_ADMIN_ROLE) { - if (paymentToken == address(0)) { + if (_paymentToken == address(0)) { (bool success,) = _to.call{value: _amount}(""); if (!success) { revert WithdrawFailed(); } } else { - (bool success) = IERC20(paymentToken).transfer(_to, _amount); + (bool success) = IERC20(_paymentToken).transfer(_to, _amount); if (!success) { revert WithdrawFailed(); } @@ -230,6 +232,36 @@ contract ERC1155Sale is ERC1155SaleErrors, ERC1155Supply, ERC1155Meta, ERC1155Me // // Views // + + /** + * Get global sales details. + * @return Sale details. + * @notice Global sales details apply to all tokens. + * @notice Global sales details are overriden when token sale is active. + */ + function globalSaleDetails() external view returns (SaleDetails memory) { + return _globalSaleDetails; + } + + /** + * Get token sale details. + * @param _tokenId Token ID to get sale details for. + * @return Sale details. + * @notice Token sale details override global sale details. + */ + function tokenSaleDetails(uint256 _tokenId) external view returns (SaleDetails memory) { + return _tokenSaleDetails[_tokenId]; + } + + /** + * Get payment token. + * @return Payment token address. + * @notice address(0) indicates payment in ETH. + */ + function paymentToken() external view returns (address) { + return _paymentToken; + } + function supportsInterface(bytes4 _interfaceId) public view diff --git a/src/tokens/ERC1155/IERC1155Sale.sol b/src/tokens/ERC1155/IERC1155Sale.sol new file mode 100644 index 0000000..f07e905 --- /dev/null +++ b/src/tokens/ERC1155/IERC1155Sale.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC1155Sale { + event GlobalSaleDetailsUpdated(uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime); + event TokenSaleDetailsUpdated(uint256 tokenId, uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime); + + struct SaleDetails { + uint256 cost; + uint64 startTime; + uint64 endTime; // 0 end time indicates sale inactive + } + + /** + * Get global sales details. + * @return Sale details. + * @notice Global sales details apply to all tokens. + * @notice Global sales details are overriden when token sale is active. + */ + function globalSaleDetails() external returns (SaleDetails memory); + + /** + * Get token sale details. + * @param _tokenId Token ID to get sale details for. + * @return Sale details. + * @notice Token sale details override global sale details. + */ + function tokenSaleDetails(uint256 _tokenId) external returns (SaleDetails memory); + + /** + * Get payment token. + * @return Payment token address. + * @notice address(0) indicates payment in ETH. + */ + function paymentToken() external returns (address); + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _tokenIds Token IDs to mint. + * @param _amounts Amounts of tokens to mint. + * @param _data Data to pass if receiver is contract. + * @notice Sale must be active for all tokens. + */ + function mint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + external + payable; +} diff --git a/src/tokens/ERC721/ERC721Sale.sol b/src/tokens/ERC721/ERC721Sale.sol index 05ee6fb..cae4b0e 100644 --- a/src/tokens/ERC721/ERC721Sale.sol +++ b/src/tokens/ERC721/ERC721Sale.sol @@ -4,13 +4,14 @@ pragma solidity ^0.8.17; import { ERC721AQueryable, IERC721AQueryable, ERC721A, IERC721A } from "erc721a/contracts/extensions/ERC721AQueryable.sol"; +import {IERC721Sale} from "./IERC721Sale.sol"; import {ERC721SaleErrors} from "./ERC721SaleErrors.sol"; import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -contract ERC721Sale is ERC721AQueryable, ERC2981, AccessControl, ERC721SaleErrors { +contract ERC721Sale is IERC721Sale, ERC721AQueryable, ERC2981, AccessControl, ERC721SaleErrors { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); @@ -19,17 +20,7 @@ contract ERC721Sale is ERC721AQueryable, ERC2981, AccessControl, ERC721SaleError string private baseURI; - event SaleDetailsUpdated(uint256 supplyCap, uint256 cost, address paymentToken, uint64 startTime, uint64 endTime); - - struct SaleDetails { - uint256 supplyCap; // 0 supply cap indicates unlimited supply - uint256 cost; - address paymentToken; // ERC20 token address for payment. address(0) indicated payment in ETH. - uint64 startTime; - uint64 endTime; // 0 end time indicates sale inactive - } - - SaleDetails public saleDetails; + SaleDetails private _saleDetails; constructor(address owner, string memory _name, string memory _symbol, string memory baseURI_) ERC721A(_name, _symbol) @@ -57,12 +48,12 @@ contract ERC721Sale is ERC721AQueryable, ERC2981, AccessControl, ERC721SaleError */ function payForActiveMint(uint256 _amount) private { // Active sale test - if (blockTimeOutOfBounds(saleDetails.startTime, saleDetails.endTime)) { + if (blockTimeOutOfBounds(_saleDetails.startTime, _saleDetails.endTime)) { revert SaleInactive(); } - uint256 total = saleDetails.cost * _amount; - address paymentToken = saleDetails.paymentToken; + uint256 total = _saleDetails.cost * _amount; + address paymentToken = _saleDetails.paymentToken; if (paymentToken == address(0)) { // Paid in ETH if (msg.value != total) { @@ -90,7 +81,7 @@ contract ERC721Sale is ERC721AQueryable, ERC2981, AccessControl, ERC721SaleError */ function mint(address _to, uint256 _amount) public payable { uint256 currentSupply = ERC721A.totalSupply(); - uint256 supplyCap = saleDetails.supplyCap; + uint256 supplyCap = _saleDetails.supplyCap; if (supplyCap > 0 && currentSupply + _amount > supplyCap) { revert InsufficientSupply(currentSupply, _amount, supplyCap); } @@ -127,7 +118,7 @@ contract ERC721Sale is ERC721AQueryable, ERC2981, AccessControl, ERC721SaleError public onlyRole(MINT_ADMIN_ROLE) { - saleDetails = SaleDetails(_supplyCap, _cost, _paymentToken, _startTime, _endTime); + _saleDetails = SaleDetails(_supplyCap, _cost, _paymentToken, _startTime, _endTime); emit SaleDetailsUpdated(_supplyCap, _cost, _paymentToken, _startTime, _endTime); } @@ -156,7 +147,7 @@ contract ERC721Sale is ERC721AQueryable, ERC2981, AccessControl, ERC721SaleError * @notice Only callable by the contract admin. */ function withdraw(address _to, uint256 _amount) public onlyRole(DEFAULT_ADMIN_ROLE) { - address paymentToken = saleDetails.paymentToken; + address paymentToken = _saleDetails.paymentToken; if (paymentToken == address(0)) { (bool success,) = _to.call{value: _amount}(""); if (!success) { @@ -173,6 +164,14 @@ contract ERC721Sale is ERC721AQueryable, ERC2981, AccessControl, ERC721SaleError // // Views // + /** + * Get sale details. + * @return Sale details. + */ + function saleDetails() external view returns (SaleDetails memory) { + return _saleDetails; + } + function supportsInterface(bytes4 _interfaceId) public view diff --git a/src/tokens/ERC721/IERC721Sale.sol b/src/tokens/ERC721/IERC721Sale.sol new file mode 100644 index 0000000..1da6651 --- /dev/null +++ b/src/tokens/ERC721/IERC721Sale.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC721Sale { + event SaleDetailsUpdated(uint256 supplyCap, uint256 cost, address paymentToken, uint64 startTime, uint64 endTime); + + struct SaleDetails { + uint256 supplyCap; // 0 supply cap indicates unlimited supply + uint256 cost; + address paymentToken; // ERC20 token address for payment. address(0) indicated payment in ETH. + uint64 startTime; + uint64 endTime; // 0 end time indicates sale inactive + } + + /** + * Get sale details. + * @return Sale details. + */ + function saleDetails() external view returns (SaleDetails memory); + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _amount Amount of tokens to mint. + * @notice Sale must be active for all tokens. + */ + function mint(address _to, uint256 _amount) external payable; +} From 59521352b0d852f1eeb2f0c676bf191fc62c9804 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Mon, 8 May 2023 10:53:44 +1200 Subject: [PATCH 22/49] Use a proxy for ERC721Sale deployments --- src/tokens/ERC721/ERC721Sale.sol | 51 +++++++++++++++++--- src/tokens/ERC721/ERC721SaleErrors.sol | 5 ++ src/tokens/ERC721/ERC721SaleFactory.sol | 43 +++++++++++++++++ src/tokens/ERC721/IERC721SaleFactory.sol | 10 ++++ test/tokens/ERC1155/ERC1155Sale.t.sol | 2 +- test/tokens/ERC721/ERC721Sale.t.sol | 59 +++++++++++++++++++----- 6 files changed, 151 insertions(+), 19 deletions(-) create mode 100644 src/tokens/ERC721/ERC721SaleFactory.sol create mode 100644 src/tokens/ERC721/IERC721SaleFactory.sol diff --git a/src/tokens/ERC721/ERC721Sale.sol b/src/tokens/ERC721/ERC721Sale.sol index cae4b0e..f900266 100644 --- a/src/tokens/ERC721/ERC721Sale.sol +++ b/src/tokens/ERC721/ERC721Sale.sol @@ -18,17 +18,38 @@ contract ERC721Sale is IERC721Sale, ERC721AQueryable, ERC2981, AccessControl, ER bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); + bool private _initialized; + string private _name; + string private _symbol; string private baseURI; SaleDetails private _saleDetails; - constructor(address owner, string memory _name, string memory _symbol, string memory baseURI_) - ERC721A(_name, _symbol) - { + /** + * Initialize with empty values. + * @dev These are overridden by initialize(). + */ + constructor() ERC721A("", "") {} + + /** + * Initialize the contract. + * @param _owner Owner address. + * @param name_ Token name. + * @param symbol_ Token symbol. + * @param baseURI_ Base URI for token metadata. + * @dev This should be called immediately after deployment. + */ + function initialize(address _owner, string memory name_, string memory symbol_, string memory baseURI_) public { + if (_initialized) { + revert InvalidInitialization(); + } + _initialized = true; + _name = name_; + _symbol = symbol_; baseURI = baseURI_; - _setupRole(DEFAULT_ADMIN_ROLE, owner); - _setupRole(MINT_ADMIN_ROLE, owner); - _setupRole(ROYALTY_ADMIN_ROLE, owner); + _setupRole(DEFAULT_ADMIN_ROLE, _owner); + _setupRole(MINT_ADMIN_ROLE, _owner); + _setupRole(ROYALTY_ADMIN_ROLE, _owner); } /** @@ -182,10 +203,28 @@ contract ERC721Sale is IERC721Sale, ERC721AQueryable, ERC2981, AccessControl, ER || ERC721A.supportsInterface(_interfaceId) || super.supportsInterface(_interfaceId); } + // + // ERC721A Overrides + // + /** * Override the ERC721A baseURI function. */ function _baseURI() internal view override returns (string memory) { return baseURI; } + + /** + * @dev Returns the token collection name. + */ + function name() public view virtual override (ERC721A, IERC721A) returns (string memory) { + return _name; + } + + /** + * @dev Returns the token collection symbol. + */ + function symbol() public view virtual override (ERC721A, IERC721A) returns (string memory) { + return _symbol; + } } diff --git a/src/tokens/ERC721/ERC721SaleErrors.sol b/src/tokens/ERC721/ERC721SaleErrors.sol index e993fe5..5f837d9 100644 --- a/src/tokens/ERC721/ERC721SaleErrors.sol +++ b/src/tokens/ERC721/ERC721SaleErrors.sol @@ -3,6 +3,11 @@ pragma solidity ^0.8.17; // A contract for errors for extensibility. abstract contract ERC721SaleErrors { + /** + * Contract already initialized. + */ + error InvalidInitialization(); + /** * Sale is not active. */ diff --git a/src/tokens/ERC721/ERC721SaleFactory.sol b/src/tokens/ERC721/ERC721SaleFactory.sol new file mode 100644 index 0000000..9eb49af --- /dev/null +++ b/src/tokens/ERC721/ERC721SaleFactory.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +import {ERC721Sale} from "./ERC721Sale.sol"; +import {IERC721SaleFactory} from "./IERC721SaleFactory.sol"; +import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; + +contract ERC721SaleFactory is IERC721SaleFactory, ProxyDeployer { + address private immutable _implAddr; + + /** + * Creates an ERC-721 Sale Factory. + */ + constructor() { + ERC721Sale proxyImpl = new ERC721Sale(); + _implAddr = address(proxyImpl); + } + + /** + * Creates an ERC-721 Floor Wrapper for given token contract + * @param _owner The owner of the ERC-721 Sale + * @param _name The name of the ERC-721 Sale token + * @param _symbol The symbol of the ERC-721 Sale token + * @param _baseURI The base URI of the ERC-721 Sale token + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-721 Sale Proxy + */ + function deployERC721Sale( + address _owner, + string memory _name, + string memory _symbol, + string memory _baseURI, + bytes32 _salt + ) + external + returns (address proxyAddr) + { + proxyAddr = _deployProxy(_implAddr, _salt); + ERC721Sale(proxyAddr).initialize(_owner, _name, _symbol, _baseURI); + emit ERC721SaleDeployed(proxyAddr); + return proxyAddr; + } +} diff --git a/src/tokens/ERC721/IERC721SaleFactory.sol b/src/tokens/ERC721/IERC721SaleFactory.sol new file mode 100644 index 0000000..c31cf0a --- /dev/null +++ b/src/tokens/ERC721/IERC721SaleFactory.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC721SaleFactory { + /** + * Event emitted when a new ERC-721 Sale proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC721SaleDeployed(address proxyAddr); +} diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index 19f94cb..ec198e5 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.13; +pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {ERC1155Sale} from "src/tokens/ERC1155/ERC1155Sale.sol"; diff --git a/test/tokens/ERC721/ERC721Sale.t.sol b/test/tokens/ERC721/ERC721Sale.t.sol index a4d110a..f5d916b 100644 --- a/test/tokens/ERC721/ERC721Sale.t.sol +++ b/test/tokens/ERC721/ERC721Sale.t.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.13; +pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {ERC721Sale} from "src/tokens/ERC721/ERC721Sale.sol"; import {ERC721SaleErrors} from "src/tokens/ERC721/ERC721SaleErrors.sol"; +import {ERC721SaleFactory} from "src/tokens/ERC721/ERC721SaleFactory.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -24,11 +25,17 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { uint256 private perTokenCost = 0.02 ether; function setUp() public { - token = new ERC721Sale(address(this), "test", "test", "ipfs://"); + token = new ERC721Sale(); + token.initialize(address(this), "test", "test", "ipfs://"); vm.deal(address(this), 100 ether); } + function setUpFromFactory() public { + ERC721SaleFactory factory = new ERC721SaleFactory(); + token = ERC721Sale(factory.deployERC721Sale(address(this), "test", "test", "ipfs://", "")); + } + function testSupportsInterface() public { assertTrue(token.supportsInterface(type(IERC165).interfaceId)); assertTrue(token.supportsInterface(type(IERC721).interfaceId)); @@ -42,15 +49,20 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { // // Minting denied when no sale active. - function testMintInactiveFail(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) { + function testMintInactiveFail(bool useFactory, address mintTo, uint256 amount) + public + assumeSafe(mintTo, amount) + withFactory(useFactory) + { vm.expectRevert(SaleInactive.selector); token.mint{value: amount * perTokenCost}(mintTo, amount); } // Minting denied when sale is expired. - function testMintExpiredFail(address mintTo, uint256 amount, uint256 startTime, uint256 endTime) + function testMintExpiredFail(bool useFactory, address mintTo, uint256 amount, uint256 startTime, uint256 endTime) public assumeSafe(mintTo, amount) + withFactory(useFactory) { vm.assume(startTime > endTime); vm.assume(block.timestamp < startTime || block.timestamp >= endTime); @@ -61,9 +73,10 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { } // Minting denied when supply exceeded. - function testMintSupplyExceeded(address mintTo, uint256 amount, uint256 supplyCap) + function testMintSupplyExceeded(bool useFactory, address mintTo, uint256 amount, uint256 supplyCap) public assumeSafe(mintTo, amount) + withFactory(useFactory) { vm.assume(supplyCap > 0); vm.assume(amount > supplyCap); @@ -74,7 +87,12 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { } // Minting allowed when sale is active. - function testMintSuccess(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) withSaleActive { + function testMintSuccess(bool useFactory, address mintTo, uint256 amount) + public + assumeSafe(mintTo, amount) + withFactory(useFactory) + withSaleActive + { uint256 count = token.balanceOf(mintTo); vm.expectEmit(true, true, true, true, address(token)); emit Transfer(address(0), mintTo, 0); @@ -83,7 +101,11 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { } // Minting allowed when sale is free. - function testFreeMint(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) { + function testFreeMint(bool useFactory, address mintTo, uint256 amount) + public + assumeSafe(mintTo, amount) + withFactory(useFactory) + { token.setSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1)); uint256 count = token.balanceOf(mintTo); @@ -94,7 +116,12 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { } // Minting allowed when mint charged with ERC20. - function testERC20Mint(address mintTo, uint256 amount) public assumeSafe(mintTo, amount) withERC20 { + function testERC20Mint(bool useFactory, address mintTo, uint256 amount) + public + assumeSafe(mintTo, amount) + withFactory(useFactory) + withERC20 + { token.setSaleDetails(0, perTokenCost, address(erc20), uint64(block.timestamp - 1), uint64(block.timestamp + 1)); uint256 cost = amount * perTokenCost; @@ -192,10 +219,10 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { } // Withdraw success ETH - function testWithdrawETH(address withdrawTo, uint256 amount) public { + function testWithdrawETH(bool useFactory, address withdrawTo, uint256 amount) public withFactory(useFactory) { // Address 9 doesnt receive ETH vm.assume(withdrawTo != address(9)); - testMintSuccess(withdrawTo, amount); + testMintSuccess(false, withdrawTo, amount); uint256 tokenBalance = address(token).balance; uint256 balance = withdrawTo.balance; @@ -204,8 +231,8 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { } // Withdraw success ERC20 - function testWithdrawERC20(address withdrawTo, uint256 amount) public { - testERC20Mint(withdrawTo, amount); + function testWithdrawERC20(bool useFactory, address withdrawTo, uint256 amount) public withFactory(useFactory) { + testERC20Mint(false, withdrawTo, amount); uint256 tokenBalance = erc20.balanceOf(address(token)); uint256 balance = erc20.balanceOf(withdrawTo); @@ -216,8 +243,16 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { // // Helpers // + modifier withFactory(bool useFactory) { + if (useFactory) { + setUpFromFactory(); + } + _; + } + modifier assumeSafe(address nonContract, uint256 amount) { vm.assume(nonContract != address(0)); + vm.assume(uint160(nonContract) > 16); vm.assume(nonContract.code.length == 0); vm.assume(amount > 0 && amount < 20); _; From 86979ec1d3c7e782715951bd4d39dfa564ca1e42 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Mon, 8 May 2023 11:10:16 +1200 Subject: [PATCH 23/49] Use factory for ERC1155Sale --- src/tokens/ERC1155/ERC1155Sale.sol | 29 +++++++- src/tokens/ERC1155/ERC1155SaleErrors.sol | 5 ++ src/tokens/ERC1155/ERC1155SaleFactory.sol | 36 +++++++++ src/tokens/ERC1155/IERC1155SaleFactory.sol | 10 +++ test/tokens/ERC1155/ERC1155Sale.t.sol | 87 +++++++++++++++++----- 5 files changed, 144 insertions(+), 23 deletions(-) create mode 100644 src/tokens/ERC1155/ERC1155SaleFactory.sol create mode 100644 src/tokens/ERC1155/IERC1155SaleFactory.sol diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol index aa4ba0b..5806703 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -25,16 +25,37 @@ contract ERC1155Sale is bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); + bool private _initialized; + // ERC20 token address for payment. address(0) indicated payment in ETH. address private _paymentToken; SaleDetails private _globalSaleDetails; mapping(uint256 => SaleDetails) private _tokenSaleDetails; - constructor(address owner, string memory _name, string memory _baseURI) ERC1155Metadata(_name, _baseURI) { - _setupRole(DEFAULT_ADMIN_ROLE, owner); - _setupRole(MINT_ADMIN_ROLE, owner); - _setupRole(ROYALTY_ADMIN_ROLE, owner); + /** + * Initialize with empty values. + * @dev These are overridden by initialize(). + */ + constructor() ERC1155Metadata("", "") {} + + /** + * Initialize the contract. + * @param _owner Owner address. + * @param _name Token name. + * @param _baseURI Base URI for token metadata. + * @dev This should be called immediately after deployment. + */ + function initialize(address _owner, string memory _name, string memory _baseURI) public { + if (_initialized) { + revert InvalidInitialization(); + } + _initialized = true; + name = _name; + baseURI = _baseURI; + _setupRole(DEFAULT_ADMIN_ROLE, _owner); + _setupRole(MINT_ADMIN_ROLE, _owner); + _setupRole(ROYALTY_ADMIN_ROLE, _owner); } /** diff --git a/src/tokens/ERC1155/ERC1155SaleErrors.sol b/src/tokens/ERC1155/ERC1155SaleErrors.sol index b8f1211..36bc1b1 100644 --- a/src/tokens/ERC1155/ERC1155SaleErrors.sol +++ b/src/tokens/ERC1155/ERC1155SaleErrors.sol @@ -3,6 +3,11 @@ pragma solidity ^0.8.17; // A contract for errors for extensibility. abstract contract ERC1155SaleErrors { + /** + * Contract already initialized. + */ + error InvalidInitialization(); + /** * Sale is not active globally. */ diff --git a/src/tokens/ERC1155/ERC1155SaleFactory.sol b/src/tokens/ERC1155/ERC1155SaleFactory.sol new file mode 100644 index 0000000..7af4571 --- /dev/null +++ b/src/tokens/ERC1155/ERC1155SaleFactory.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +import {ERC1155Sale} from "./ERC1155Sale.sol"; +import {IERC1155SaleFactory} from "./IERC1155SaleFactory.sol"; +import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; + +contract ERC1155SaleFactory is IERC1155SaleFactory, ProxyDeployer { + address private immutable _implAddr; + + /** + * Creates an ERC-1155 Sale Factory. + */ + constructor() { + ERC1155Sale proxyImpl = new ERC1155Sale(); + _implAddr = address(proxyImpl); + } + + /** + * Creates an ERC-1155 Floor Wrapper for given token contract + * @param _owner The owner of the ERC-1155 Sale + * @param _name The name of the ERC-1155 Sale token + * @param _baseURI The base URI of the ERC-1155 Sale token + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-1155 Sale Proxy + */ + function deployERC1155Sale(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + external + returns (address proxyAddr) + { + proxyAddr = deployProxy(_implAddr, _salt); + ERC1155Sale(proxyAddr).initialize(_owner, _name, _baseURI); + emit ERC1155SaleDeployed(proxyAddr); + return proxyAddr; + } +} diff --git a/src/tokens/ERC1155/IERC1155SaleFactory.sol b/src/tokens/ERC1155/IERC1155SaleFactory.sol new file mode 100644 index 0000000..fcdf7b5 --- /dev/null +++ b/src/tokens/ERC1155/IERC1155SaleFactory.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC1155SaleFactory { + /** + * Event emitted when a new ERC-1155 Sale proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC1155SaleDeployed(address proxyAddr); +} diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index ec198e5..fe88859 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {ERC1155Sale} from "src/tokens/ERC1155/ERC1155Sale.sol"; +import {ERC1155SaleFactory} from "src/tokens/ERC1155/ERC1155SaleFactory.sol"; import {ERC1155SaleErrors} from "src/tokens/ERC1155/ERC1155SaleErrors.sol"; import {ERC1155SupplyErrors} from "src/tokens/ERC1155/ERC1155SupplyErrors.sol"; @@ -26,11 +27,17 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { uint256 private perTokenCost = 0.02 ether; function setUp() public { - token = new ERC1155Sale(address(this), "test", "ipfs://"); + token = new ERC1155Sale(); + token.initialize(address(this), "test", "ipfs://"); vm.deal(address(this), 100 ether); } + function setUpFromFactory() public { + ERC1155SaleFactory factory = new ERC1155SaleFactory(); + token = ERC1155Sale(factory.deployERC1155Sale(address(this), "test", "ipfs://", "")); + } + function testSupportsInterface() public { assertTrue(token.supportsInterface(type(IERC165).interfaceId)); assertTrue(token.supportsInterface(type(IERC1155).interfaceId)); @@ -43,9 +50,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { // // Minting denied when no sale active. - function testMintInactiveFail(address mintTo, uint256 tokenId, uint256 amount) + function testMintInactiveFail(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) { uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -55,9 +63,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting denied when sale is active but not for the token. - function testMintInactiveSingleFail(address mintTo, uint256 tokenId, uint256 amount) + function testMintInactiveSingleFail(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) withTokenSaleActive(tokenId + 1) { uint256[] memory tokenIds = singleToArray(tokenId); @@ -69,6 +78,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { // Minting denied when token sale is expired. function testMintExpiredSingleFail( + bool useFactory, address mintTo, uint256 tokenId, uint256 amount, @@ -77,6 +87,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { ) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) { vm.assume(startTime > endTime); vm.assume(block.timestamp < startTime || block.timestamp >= endTime); @@ -91,6 +102,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { // Minting denied when global sale is expired. function testMintExpiredGlobalFail( + bool useFactory, address mintTo, uint256 tokenId, uint256 amount, @@ -99,6 +111,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { ) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) { vm.assume(startTime > endTime); vm.assume(block.timestamp < startTime || block.timestamp >= endTime); @@ -112,9 +125,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting denied when sale is active but not for all tokens in the group. - function testMintInactiveInGroupFail(address mintTo, uint256 tokenId, uint256 amount) + function testMintInactiveInGroupFail(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) withTokenSaleActive(tokenId) { uint256[] memory tokenIds = new uint256[](2); @@ -129,9 +143,16 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting denied when global supply exceeded. - function testMintGlobalSupplyExceeded(address mintTo, uint256 tokenId, uint256 amount, uint256 supplyCap) + function testMintGlobalSupplyExceeded( + bool useFactory, + address mintTo, + uint256 tokenId, + uint256 amount, + uint256 supplyCap + ) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) { vm.assume(supplyCap > 0); vm.assume(amount > supplyCap); @@ -147,9 +168,16 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting denied when token supply exceeded. - function testMintTokenSupplyExceeded(address mintTo, uint256 tokenId, uint256 amount, uint256 supplyCap) + function testMintTokenSupplyExceeded( + bool useFactory, + address mintTo, + uint256 tokenId, + uint256 amount, + uint256 supplyCap + ) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) { vm.assume(supplyCap > 0); vm.assume(amount > supplyCap); @@ -165,9 +193,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting allowed when sale is active globally. - function testMintGlobalSuccess(address mintTo, uint256 tokenId, uint256 amount) + function testMintGlobalSuccess(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) withGlobalSaleActive { uint256[] memory tokenIds = singleToArray(tokenId); @@ -181,9 +210,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting allowed when sale is active for the token. - function testMintSingleSuccess(address mintTo, uint256 tokenId, uint256 amount) + function testMintSingleSuccess(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) withTokenSaleActive(tokenId) { uint256[] memory tokenIds = singleToArray(tokenId); @@ -197,9 +227,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting allowed when sale is active for both tokens individually. - function testMintGroupSuccess(address mintTo, uint256 tokenId, uint256 amount) + function testMintGroupSuccess(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) { setTokenSaleActive(tokenId); setTokenSaleActive(tokenId + 1); @@ -220,9 +251,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting allowed when global sale is free. - function testFreeGlobalMint(address mintTo, uint256 tokenId, uint256 amount) + function testFreeGlobalMint(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) { token.setGlobalSaleDetails(address(0), 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); uint256[] memory tokenIds = singleToArray(tokenId); @@ -236,9 +268,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting allowed when token sale is free and global is not. - function testFreeTokenMint(address mintTo, uint256 tokenId, uint256 amount) + function testFreeTokenMint(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) withGlobalSaleActive { token.setTokenSaleDetails(tokenId, 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); @@ -253,9 +286,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting allowed when mint charged with ERC20. - function testERC20Mint(address mintTo, uint256 tokenId, uint256 amount) + function testERC20Mint(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) withERC20 { token.setGlobalSaleDetails( @@ -280,9 +314,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { // // Admin minting denied when not admin. - function testMintAdminFail(address minter, address mintTo, uint256 tokenId, uint256 amount) + function testMintAdminFail(bool useFactory, address minter, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) { vm.assume(minter != address(this)); vm.assume(minter != address(0)); @@ -303,9 +338,10 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Minting as admin success. - function testMintAdminSuccess(address minter, address mintTo, uint256 tokenId, uint256 amount) + function testMintAdminSuccess(bool useFactory, address minter, address mintTo, uint256 tokenId, uint256 amount) public assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) { token.grantRole(token.MINT_ADMIN_ROLE(), minter); @@ -390,7 +426,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { // // Withdraw fails if the caller doesn't have the WITHDRAW_ROLE - function testWithdrawFail(address withdrawTo, uint256 amount) public { + function testWithdrawFail(bool useFactory, address withdrawTo, uint256 amount) public withFactory(useFactory) { token.revokeRole(token.DEFAULT_ADMIN_ROLE(), address(this)); vm.expectRevert( @@ -405,10 +441,13 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Withdraw success ETH - function testWithdrawETH(address withdrawTo, uint256 tokenId, uint256 amount) public { + function testWithdrawETH(bool useFactory, address withdrawTo, uint256 tokenId, uint256 amount) + public + withFactory(useFactory) + { // Address 9 doesnt receive ETH vm.assume(withdrawTo != address(9)); - testMintSingleSuccess(withdrawTo, tokenId, amount); + testMintSingleSuccess(false, withdrawTo, tokenId, amount); uint256 tokenBalance = address(token).balance; uint256 balance = withdrawTo.balance; @@ -417,8 +456,11 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } // Withdraw success ERC20 - function testWithdrawERC20(address withdrawTo, uint256 tokenId, uint256 amount) public { - testERC20Mint(withdrawTo, tokenId, amount); + function testWithdrawERC20(bool useFactory, address withdrawTo, uint256 tokenId, uint256 amount) + public + withFactory(useFactory) + { + testERC20Mint(false, withdrawTo, tokenId, amount); uint256 tokenBalance = erc20.balanceOf(address(token)); uint256 balance = erc20.balanceOf(withdrawTo); @@ -429,6 +471,13 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { // // Helpers // + modifier withFactory(bool useFactory) { + if (useFactory) { + setUpFromFactory(); + } + _; + } + modifier assumeSafe(address nonContract, uint256 tokenId, uint256 amount) { vm.assume(nonContract != address(0)); vm.assume(nonContract.code.length == 0); From 41a2fca509458a66a61d4beb09951c3c7df34ffa Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 16 May 2023 09:54:07 +1200 Subject: [PATCH 24/49] Fix docstring --- src/tokens/ERC1155/ERC1155SaleFactory.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tokens/ERC1155/ERC1155SaleFactory.sol b/src/tokens/ERC1155/ERC1155SaleFactory.sol index 7af4571..971d6b6 100644 --- a/src/tokens/ERC1155/ERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/ERC1155SaleFactory.sol @@ -17,7 +17,7 @@ contract ERC1155SaleFactory is IERC1155SaleFactory, ProxyDeployer { } /** - * Creates an ERC-1155 Floor Wrapper for given token contract + * Creates an ERC-1155 Sale proxy contract * @param _owner The owner of the ERC-1155 Sale * @param _name The name of the ERC-1155 Sale token * @param _baseURI The base URI of the ERC-1155 Sale token From 601de952da1f5f1e80eb560193528078c15967bf Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 17 May 2023 10:44:20 +1200 Subject: [PATCH 25/49] Fix forge non-contracts --- test/tokens/ERC1155/ERC1155Sale.t.sol | 2 +- test/tokens/ERC721/ERC721Sale.t.sol | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index fe88859..84f3287 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -479,7 +479,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } modifier assumeSafe(address nonContract, uint256 tokenId, uint256 amount) { - vm.assume(nonContract != address(0)); + vm.assume(uint160(nonContract) > 16); vm.assume(nonContract.code.length == 0); vm.assume(tokenId < 100); vm.assume(amount > 0 && amount < 20); diff --git a/test/tokens/ERC721/ERC721Sale.t.sol b/test/tokens/ERC721/ERC721Sale.t.sol index f5d916b..bb424ba 100644 --- a/test/tokens/ERC721/ERC721Sale.t.sol +++ b/test/tokens/ERC721/ERC721Sale.t.sol @@ -251,7 +251,6 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { } modifier assumeSafe(address nonContract, uint256 amount) { - vm.assume(nonContract != address(0)); vm.assume(uint160(nonContract) > 16); vm.assume(nonContract.code.length == 0); vm.assume(amount > 0 && amount < 20); From dd033c2c68e95a59c84c9d505fb7c4b88bb5accd Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 17 May 2023 10:46:31 +1200 Subject: [PATCH 26/49] Add ERC1155Packed --- .../ERC1155/Packed/ERC1155PackedSale.sol | 295 ++++++++++ .../Packed/ERC1155PackedSaleFactory.sol | 36 ++ .../ERC1155/Packed/ERC1155PackedSupply.sol | 160 ++++++ .../ERC1155/Packed/ERC1155PackedSale.t.sol | 518 ++++++++++++++++++ 4 files changed, 1009 insertions(+) create mode 100644 src/tokens/ERC1155/Packed/ERC1155PackedSale.sol create mode 100644 src/tokens/ERC1155/Packed/ERC1155PackedSaleFactory.sol create mode 100644 src/tokens/ERC1155/Packed/ERC1155PackedSupply.sol create mode 100644 test/tokens/ERC1155/Packed/ERC1155PackedSale.t.sol diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedSale.sol b/src/tokens/ERC1155/Packed/ERC1155PackedSale.sol new file mode 100644 index 0000000..e822cfa --- /dev/null +++ b/src/tokens/ERC1155/Packed/ERC1155PackedSale.sol @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {IERC1155Sale} from "../IERC1155Sale.sol"; +import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; +import {ERC1155PackedSupply} from "./ERC1155PackedSupply.sol"; +import {ERC1155PackedBalance} from "@0xsequence/erc-1155/contracts/tokens/ERC1155PackedBalance/ERC1155PackedBalance.sol"; +import {ERC1155SaleErrors} from "../ERC1155SaleErrors.sol"; +import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract ERC1155PackedSale is + IERC1155Sale, + ERC1155SaleErrors, + ERC1155PackedSupply, + ERC1155Metadata, + ERC2981, + AccessControl +{ + bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); + bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); + + bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = + bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); + + bool private _initialized; + + // ERC20 token address for payment. address(0) indicated payment in ETH. + address private _paymentToken; + + SaleDetails private _globalSaleDetails; + mapping(uint256 => SaleDetails) private _tokenSaleDetails; + + /** + * Initialize with empty values. + * @dev These are overridden by initialize(). + */ + constructor() ERC1155Metadata("", "") {} + + /** + * Initialize the contract. + * @param _owner Owner address. + * @param _name Token name. + * @param _baseURI Base URI for token metadata. + * @dev This should be called immediately after deployment. + */ + function initialize(address _owner, string memory _name, string memory _baseURI) public { + if (_initialized) { + revert InvalidInitialization(); + } + _initialized = true; + name = _name; + baseURI = _baseURI; + _setupRole(DEFAULT_ADMIN_ROLE, _owner); + _setupRole(MINT_ADMIN_ROLE, _owner); + _setupRole(ROYALTY_ADMIN_ROLE, _owner); + } + + /** + * Checks if the current block.timestamp is out of the give timestamp range. + * @param startTime Earliest acceptable timestamp (inclusive). + * @param endTime Latest acceptable timestamp (exclusive). + * @dev A zero endTime value is always considered out of bounds. + */ + function blockTimeOutOfBounds(uint256 startTime, uint256 endTime) private view returns (bool) { + // 0 end time indicates inactive sale. + return endTime == 0 || block.timestamp < startTime || block.timestamp >= endTime; + } + + /** + * Checks the sale is active and takes payment. + * @param _tokenIds Token IDs to mint. + * @param _amounts Amounts of tokens to mint. + */ + function payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts) private { + uint256 totalAmount; + uint256 totalCost; + SaleDetails memory gSaleDetails = _globalSaleDetails; + bool globalSaleInactive = blockTimeOutOfBounds(gSaleDetails.startTime, gSaleDetails.endTime); + for (uint256 i; i < _tokenIds.length; i++) { + // Active sale test + SaleDetails memory saleDetails = _tokenSaleDetails[_tokenIds[i]]; + bool tokenSaleInactive = blockTimeOutOfBounds(saleDetails.startTime, saleDetails.endTime); + if (tokenSaleInactive) { + // Prefer token sale + if (globalSaleInactive) { + // Both sales inactive + revert SaleInactive(_tokenIds[i]); + } + // Use global sale details + totalCost += gSaleDetails.cost * _amounts[i]; + } else { + // Use token sale price + totalCost += saleDetails.cost * _amounts[i]; + } + totalAmount += _amounts[i]; + } + + if (_paymentToken == address(0)) { + // Paid in ETH + if (msg.value != totalCost) { + revert InsufficientPayment(totalCost, msg.value); + } + } else { + // Paid in ERC20 + (bool success, bytes memory data) = _paymentToken.call( + abi.encodeWithSelector(ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), totalCost) + ); + if (!success || (data.length > 0 && !abi.decode(data, (bool)))) { + revert InsufficientPayment(totalCost, 0); + } + } + } + + // + // Minting + // + + /** + * Mint tokens. + * @param _to Address to mint tokens to. + * @param _tokenIds Token IDs to mint. + * @param _amounts Amounts of tokens to mint. + * @param _data Data to pass if receiver is contract. + * @notice Sale must be active for all tokens. + */ + function mint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + public + payable + { + payForActiveMint(_tokenIds, _amounts); + _batchMint(_to, _tokenIds, _amounts, _data); + } + + /** + * Mint tokens as admin. + * @param _to Address to mint tokens to. + * @param _tokenIds Token IDs to mint. + * @param _amounts Amounts of tokens to mint. + * @param _data Data to pass if receiver is contract. + * @notice Only callable by mint admin. + */ + function mintAdmin(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + public + onlyRole(MINT_ADMIN_ROLE) + { + _batchMint(_to, _tokenIds, _amounts, _data); + } + + /** + * Set the global sale details. + * @param _paymentTokenAddr The ERC20 token address to accept payment in. address(0) indicates ETH. + * @param _cost The amount of payment tokens to accept for each token minted. + * @param _supplyCap The maximum number of tokens that can be minted. + * @param _startTime The start time of the sale. Tokens cannot be minted before this time. + * @param _endTime The end time of the sale. Tokens cannot be minted after this time. + * @dev A zero end time indicates an inactive sale. + */ + function setGlobalSaleDetails( + address _paymentTokenAddr, + uint256 _cost, + uint256 _supplyCap, + uint64 _startTime, + uint64 _endTime + ) + public + onlyRole(MINT_ADMIN_ROLE) + { + _paymentToken = _paymentTokenAddr; + totalSupplyCap = _supplyCap; + _globalSaleDetails = SaleDetails(_cost, _startTime, _endTime); + emit GlobalSaleDetailsUpdated(_cost, _supplyCap, _startTime, _endTime); + } + + /** + * Set the sale details for an individual token. + * @param _tokenId The token ID to set the sale details for. + * @param _cost The amount of payment tokens to accept for each token minted. + * @param _supplyCap The maximum number of tokens that can be minted. + * @param _startTime The start time of the sale. Tokens cannot be minted before this time. + * @param _endTime The end time of the sale. Tokens cannot be minted after this time. + * @dev A zero end time indicates an inactive sale. + */ + function setTokenSaleDetails( + uint256 _tokenId, + uint256 _cost, + uint256 _supplyCap, + uint64 _startTime, + uint64 _endTime + ) + public + onlyRole(MINT_ADMIN_ROLE) + { + tokenSupplyCap[_tokenId] = _supplyCap; + _tokenSaleDetails[_tokenId] = SaleDetails(_cost, _startTime, _endTime); + emit TokenSaleDetailsUpdated(_tokenId, _cost, _supplyCap, _startTime, _endTime); + } + + // + // Royalty + // + + /** + * Sets the royalty information that all ids in this contract will default to. + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + */ + function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) public onlyRole(ROYALTY_ADMIN_ROLE) { + _setDefaultRoyalty(_receiver, _feeNumerator); + } + + /** + * Sets the royalty information that a given token id in this contract will use. + * @param _tokenId The token id to set the royalty information for + * @param _receiver Address of who should be sent the royalty payment + * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @notice This overrides the default royalty information for this token id + */ + function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) + public + onlyRole(ROYALTY_ADMIN_ROLE) + { + _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + } + + // + // Withdraw + // + + /** + * Withdraws ETH or ERC20 tokens owned by this sale contract. + * @param _to Address to withdraw to. + * @param _amount Amount to withdraw. + * @dev Withdraws ERC20 when paymentToken is set, else ETH. + * @notice Only callable by the contract admin. + */ + function withdraw(address _to, uint256 _amount) public onlyRole(DEFAULT_ADMIN_ROLE) { + if (_paymentToken == address(0)) { + (bool success,) = _to.call{value: _amount}(""); + if (!success) { + revert WithdrawFailed(); + } + } else { + (bool success) = IERC20(_paymentToken).transfer(_to, _amount); + if (!success) { + revert WithdrawFailed(); + } + } + } + + // + // Views + // + + /** + * Get global sales details. + * @return Sale details. + * @notice Global sales details apply to all tokens. + * @notice Global sales details are overriden when token sale is active. + */ + function globalSaleDetails() external view returns (SaleDetails memory) { + return _globalSaleDetails; + } + + /** + * Get token sale details. + * @param _tokenId Token ID to get sale details for. + * @return Sale details. + * @notice Token sale details override global sale details. + */ + function tokenSaleDetails(uint256 _tokenId) external view returns (SaleDetails memory) { + return _tokenSaleDetails[_tokenId]; + } + + /** + * Get payment token. + * @return Payment token address. + * @notice address(0) indicates payment in ETH. + */ + function paymentToken() external view returns (address) { + return _paymentToken; + } + + function supportsInterface(bytes4 _interfaceId) + public + view + override (ERC1155PackedBalance, ERC1155Metadata, ERC2981, AccessControl) + returns (bool) + { + // FIXME Fix inheritance issues + return ERC1155PackedBalance.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId) + || super.supportsInterface(_interfaceId); + } +} diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedSaleFactory.sol b/src/tokens/ERC1155/Packed/ERC1155PackedSaleFactory.sol new file mode 100644 index 0000000..9999fd3 --- /dev/null +++ b/src/tokens/ERC1155/Packed/ERC1155PackedSaleFactory.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +import {ERC1155PackedSale} from "./ERC1155PackedSale.sol"; +import {IERC1155SaleFactory} from "../IERC1155SaleFactory.sol"; +import {ProxyDeployer} from "../../../proxies/ERC1967/ProxyDeployer.sol"; + +contract ERC1155PackedSaleFactory is IERC1155SaleFactory, ProxyDeployer { + address private immutable _implAddr; + + /** + * Creates an ERC-1155 Sale Factory. + */ + constructor() { + ERC1155PackedSale proxyImpl = new ERC1155PackedSale(); + _implAddr = address(proxyImpl); + } + + /** + * Creates an ERC-1155 Packed Sale proxy contract + * @param _owner The owner of the ERC-1155 Sale + * @param _name The name of the ERC-1155 Sale token + * @param _baseURI The base URI of the ERC-1155 Sale token + * @param _salt The deployment salt + * @return proxyAddr The address of the ERC-1155 Sale Proxy + */ + function deployERC1155Sale(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + external + returns (address proxyAddr) + { + proxyAddr = deployProxy(_implAddr, _salt); + ERC1155PackedSale(proxyAddr).initialize(_owner, _name, _baseURI); + emit ERC1155SaleDeployed(proxyAddr); + return proxyAddr; + } +} diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedSupply.sol b/src/tokens/ERC1155/Packed/ERC1155PackedSupply.sol new file mode 100644 index 0000000..a4b8b54 --- /dev/null +++ b/src/tokens/ERC1155/Packed/ERC1155PackedSupply.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC1155MetaPackedBalance} from + "@0xsequence/erc-1155/contracts/tokens/ERC1155PackedBalance/ERC1155MetaPackedBalance.sol"; +import {ERC1155SupplyErrors} from "../ERC1155SupplyErrors.sol"; + +/** + * An ERC1155 extension that tracks token supply. + */ +contract ERC1155PackedSupply is ERC1155MetaPackedBalance, ERC1155SupplyErrors { + // Maximum supply globally and per token. 0 indicates unlimited supply + uint256 public totalSupplyCap; + mapping(uint256 => uint256) public tokenSupplyCap; + + uint256 public totalSupply; + mapping(uint256 => uint256) public tokenSupply; + + /** + * Mint _amount of tokens of a given id + * @param _to The address to mint tokens to + * @param _id Token id to mint + * @param _amount The amount to be minted + * @param _data Data to pass if receiver is contract + */ + function _mint(address _to, uint256 _id, uint256 _amount, bytes memory _data) internal { + // Check supply cap + totalSupply += _amount; + if (totalSupplyCap > 0 && totalSupply > totalSupplyCap) { + revert InsufficientSupply(); + } + tokenSupply[_id] += _amount; + if (tokenSupplyCap[_id] > 0 && tokenSupply[_id] > tokenSupplyCap[_id]) { + revert InsufficientSupply(); + } + + //Add _amount + _updateIDBalance(_to, _id, _amount, Operations.Add); // Add amount to recipient + + // Emit event + emit TransferSingle(msg.sender, address(0x0), _to, _id, _amount); + + // Calling onReceive method if recipient is contract + _callonERC1155Received(address(0x0), _to, _id, _amount, gasleft(), _data); + } + + /** + * Mint tokens for each ids in _ids + * @param _to The address to mint tokens to + * @param _ids Array of ids to mint + * @param _amounts Array of amount of tokens to mint per id + * @param _data Data to pass if receiver is contract + */ + function _batchMint(address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data) internal { + uint256 nMint = _ids.length; + if (nMint == 0 || nMint != _amounts.length) { + revert InvalidArrayLength(); + } + + // Update supply balance + uint256 id0 = _ids[0]; + uint256 amount0 = _amounts[0]; + totalSupply += amount0; + tokenSupply[id0] += amount0; + if (tokenSupplyCap[id0] > 0 && tokenSupply[id0] > tokenSupplyCap[id0]) { + revert InsufficientSupply(); + } + // Load first bin and index where the token ID balance exists + (uint256 bin, uint256 index) = getIDBinIndex(id0); + + // Balance for current bin in memory (initialized with first transfer) + uint256 balTo = _viewUpdateBinValue(balances[_to][bin], index, amount0, Operations.Add); + + // Last bin updated + uint256 lastBin = bin; + + for (uint256 i = 1; i < nMint; i++) { + // Update supply balance + totalSupply += _amounts[i]; + tokenSupply[_ids[i]] += _amounts[i]; + if (tokenSupplyCap[_ids[i]] > 0 && tokenSupply[_ids[i]] > tokenSupplyCap[_ids[i]]) { + revert InsufficientSupply(); + } + + (bin, index) = getIDBinIndex(_ids[i]); + + // If new bin + if (bin != lastBin) { + // Update storage balance of previous bin + balances[_to][lastBin] = balTo; + balTo = balances[_to][bin]; + + // Bin will be the most recent bin + lastBin = bin; + } + + // Update memory balance + balTo = _viewUpdateBinValue(balTo, index, _amounts[i], Operations.Add); + } + + // Validate total supply cap + if (totalSupplyCap > 0 && totalSupply > totalSupplyCap) { + revert InsufficientSupply(); + } + + // Update storage of the last bin visited + balances[_to][bin] = balTo; + + // //Emit event + emit TransferBatch(msg.sender, address(0x0), _to, _ids, _amounts); + + // Calling onReceive method if recipient is contract + _callonERC1155BatchReceived(address(0x0), _to, _ids, _amounts, gasleft(), _data); + } + + /** + * Burn _amount of tokens of a given token id + * @param _from The address to burn tokens from + * @param _id Token id to burn + * @param _amount The amount to be burned + */ + function _burn(address _from, uint256 _id, uint256 _amount) internal { + // Supply + totalSupply -= _amount; + tokenSupply[_id] -= _amount; + + // Substract _amount + _updateIDBalance(_from, _id, _amount, Operations.Sub); + + // Emit event + emit TransferSingle(msg.sender, _from, address(0x0), _id, _amount); + } + + /** + * Burn tokens of given token id for each (_ids[i], _amounts[i]) pair + * @param _from The address to burn tokens from + * @param _ids Array of token ids to burn + * @param _amounts Array of the amount to be burned + */ + function _batchBurn(address _from, uint256[] memory _ids, uint256[] memory _amounts) internal { + // Number of mints to execute + uint256 nBurn = _ids.length; + if (nBurn == 0 || nBurn != _amounts.length) { + revert InvalidArrayLength(); + } + + // Executing all minting + for (uint256 i = 0; i < nBurn; i++) { + // Update supplies + totalSupply -= _amounts[i]; + tokenSupply[_ids[i]] -= _amounts[i]; + + // Update storage balance + _updateIDBalance(_from, _ids[i], _amounts[i], Operations.Sub); // Add amount to recipient + } + + // Emit batch burn event + emit TransferBatch(msg.sender, _from, address(0x0), _ids, _amounts); + } +} diff --git a/test/tokens/ERC1155/Packed/ERC1155PackedSale.t.sol b/test/tokens/ERC1155/Packed/ERC1155PackedSale.t.sol new file mode 100644 index 0000000..8914395 --- /dev/null +++ b/test/tokens/ERC1155/Packed/ERC1155PackedSale.t.sol @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {ERC1155PackedSale} from "src/tokens/ERC1155/Packed/ERC1155PackedSale.sol"; +import {ERC1155PackedSaleFactory} from "src/tokens/ERC1155/Packed/ERC1155PackedSaleFactory.sol"; +import {ERC1155SaleErrors} from "src/tokens/ERC1155/ERC1155SaleErrors.sol"; +import {ERC1155SupplyErrors} from "src/tokens/ERC1155/ERC1155SupplyErrors.sol"; + +import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +// Interfaces +import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; +import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; +import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; + +contract ERC1155PackedSaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { + // Redeclare events + event TransferBatch( + address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts + ); + + ERC1155PackedSale private token; + ERC20Mock private erc20; + uint256 private perTokenCost = 0.02 ether; + + function setUp() public { + token = new ERC1155PackedSale(); + token.initialize(address(this), "test", "ipfs://"); + + vm.deal(address(this), 100 ether); + } + + function setUpFromFactory() public { + ERC1155PackedSaleFactory factory = new ERC1155PackedSaleFactory(); + token = ERC1155PackedSale(factory.deployERC1155Sale(address(this), "test", "ipfs://", "")); + } + + function testSupportsInterface() public { + assertTrue(token.supportsInterface(type(IERC165).interfaceId)); + assertTrue(token.supportsInterface(type(IERC1155).interfaceId)); + assertTrue(token.supportsInterface(type(IERC1155Metadata).interfaceId)); + assertTrue(token.supportsInterface(type(IAccessControl).interfaceId)); + } + + // + // Minting + // + + // Minting denied when no sale active. + function testMintInactiveFail(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + { + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when sale is active but not for the token. + function testMintInactiveSingleFail(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + withTokenSaleActive(tokenId + 1) + { + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when token sale is expired. + function testMintExpiredSingleFail( + bool useFactory, + address mintTo, + uint256 tokenId, + uint256 amount, + uint256 startTime, + uint256 endTime + ) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + { + vm.assume(startTime > endTime); + vm.assume(block.timestamp < startTime || block.timestamp >= endTime); + token.setTokenSaleDetails(tokenId, perTokenCost, 0, uint64(startTime), uint64(endTime)); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when global sale is expired. + function testMintExpiredGlobalFail( + bool useFactory, + address mintTo, + uint256 tokenId, + uint256 amount, + uint256 startTime, + uint256 endTime + ) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + { + vm.assume(startTime > endTime); + vm.assume(block.timestamp < startTime || block.timestamp >= endTime); + token.setGlobalSaleDetails(address(0), perTokenCost, 0, uint64(startTime), uint64(endTime)); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when sale is active but not for all tokens in the group. + function testMintInactiveInGroupFail(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + withTokenSaleActive(tokenId) + { + uint256[] memory tokenIds = new uint256[](2); + tokenIds[0] = tokenId; + tokenIds[1] = tokenId + 1; + uint256[] memory amounts = new uint256[](2); + amounts[0] = amount; + amounts[1] = amount; + + vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId + 1)); + token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when global supply exceeded. + function testMintGlobalSupplyExceeded( + bool useFactory, + address mintTo, + uint256 tokenId, + uint256 amount, + uint256 supplyCap + ) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + { + vm.assume(supplyCap > 0); + vm.assume(amount > supplyCap); + token.setGlobalSaleDetails( + address(0), perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) + ); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(InsufficientSupply.selector); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting denied when token supply exceeded. + function testMintTokenSupplyExceeded( + bool useFactory, + address mintTo, + uint256 tokenId, + uint256 amount, + uint256 supplyCap + ) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + { + vm.assume(supplyCap > 0); + vm.assume(amount > supplyCap); + token.setTokenSaleDetails( + tokenId, perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) + ); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert(InsufficientSupply.selector); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + } + + // Minting allowed when sale is active globally. + function testDebugMe(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + withGlobalSaleActive + { + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + uint256 count = token.balanceOf(mintTo, tokenId); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + } + + // Minting allowed when sale is active for the token. + function testMintSingleSuccess(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + withTokenSaleActive(tokenId) + { + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + uint256 count = token.balanceOf(mintTo, tokenId); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), address(69), tokenIds, amounts); + token.mint{value: amount * perTokenCost}(address(69), tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(address(69), tokenId)); + } + + // Minting allowed when sale is active for both tokens individually. + function testMintGroupSuccess(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + { + setTokenSaleActive(tokenId); + setTokenSaleActive(tokenId + 1); + uint256[] memory tokenIds = new uint256[](2); + tokenIds[0] = tokenId; + tokenIds[1] = tokenId + 1; + uint256[] memory amounts = new uint256[](2); + amounts[0] = amount; + amounts[1] = amount; + + uint256 count = token.balanceOf(mintTo, tokenId); + uint256 count2 = token.balanceOf(mintTo, tokenId + 1); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + assertEq(count2 + amount, token.balanceOf(mintTo, tokenId + 1)); + } + + // Minting allowed when global sale is free. + function testFreeGlobalMint(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + { + token.setGlobalSaleDetails(address(0), 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + uint256 count = token.balanceOf(mintTo, tokenId); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + } + + // Minting allowed when token sale is free and global is not. + function testFreeTokenMint(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + withGlobalSaleActive + { + token.setTokenSaleDetails(tokenId, 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + uint256 count = token.balanceOf(mintTo, tokenId); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + } + + // Minting allowed when mint charged with ERC20. + function testERC20Mint(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + withERC20 + { + token.setGlobalSaleDetails( + address(erc20), perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1) + ); + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + uint256 cost = amount * perTokenCost; + + uint256 balanace = erc20.balanceOf(address(this)); + uint256 count = token.balanceOf(mintTo, tokenId); + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); + token.mint(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + assertEq(balanace - cost, erc20.balanceOf(address(this))); + assertEq(cost, erc20.balanceOf(address(token))); + } + + // + // Admin minting + // + + // Admin minting denied when not admin. + function testMintAdminFail(bool useFactory, address minter, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + { + vm.assume(minter != address(this)); + vm.assume(minter != address(0)); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(minter), + " is missing role ", + Strings.toHexString(uint256(token.MINT_ADMIN_ROLE()), 32) + ) + ); + vm.prank(minter); + token.mintAdmin(mintTo, tokenIds, amounts, ""); + } + + // Minting as admin success. + function testMintAdminSuccess(bool useFactory, address minter, address mintTo, uint256 tokenId, uint256 amount) + public + assumeSafe(mintTo, tokenId, amount) + withFactory(useFactory) + { + token.grantRole(token.MINT_ADMIN_ROLE(), minter); + + uint256[] memory tokenIds = singleToArray(tokenId); + uint256[] memory amounts = singleToArray(amount); + + uint256 count = token.balanceOf(mintTo, tokenId); + token.mintAdmin(mintTo, tokenIds, amounts, ""); + assertEq(count + amount, token.balanceOf(mintTo, tokenId)); + } + + // + // Royalty + // + + // Token royalty fails if the caller doesn't have the ROYALTY_ADMIN_ROLE + function testSetTokenRoyaltyFail(uint256 _tokenId, address _receiver, uint96 _feeNumerator) public { + token.revokeRole(token.ROYALTY_ADMIN_ROLE(), address(this)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + token.setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + } + + // Default royalty fails if the caller doesn't have the ROYALTY_ADMIN_ROLE + function testSetDefaultRoyaltyFail(address _receiver, uint96 _feeNumerator) public { + token.revokeRole(token.ROYALTY_ADMIN_ROLE(), address(this)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) + ) + ); + token.setDefaultRoyalty(_receiver, _feeNumerator); + } + + // Royalty calculation for token with custom royalty information + function testRoyaltyInfoCustom(uint256 _tokenId, uint256 _salePrice, address _receiver, uint96 _feeNumerator) + public + { + vm.assume(_receiver != address(0)); + vm.assume(_feeNumerator < 10000); + vm.assume(_salePrice < 10000 ether); + + token.setTokenRoyalty(_tokenId, _receiver, _feeNumerator); + uint256 expectedRoyaltyAmount = (_salePrice * _feeNumerator) / 10000; + + (address receiver, uint256 royaltyAmount) = token.royaltyInfo(_tokenId, _salePrice); + + assertEq(_receiver, receiver); + assertEq(expectedRoyaltyAmount, royaltyAmount); + } + + // Test royalty calculation for token with default royalty information + function testRoyaltyInfoDefault(uint256 _tokenId, uint256 _salePrice, address _receiver, uint96 _feeNumerator) + public + { + vm.assume(_receiver != address(0)); + vm.assume(_feeNumerator < 10000); + vm.assume(_salePrice < 10000 ether); + + token.setDefaultRoyalty(_receiver, _feeNumerator); + uint256 expectedRoyaltyAmount = (_salePrice * _feeNumerator) / 10000; + + (address receiver, uint256 royaltyAmount) = token.royaltyInfo(_tokenId, _salePrice); + + assertEq(_receiver, receiver); + assertEq(expectedRoyaltyAmount, royaltyAmount); + } + + // + // Withdraw + // + + // Withdraw fails if the caller doesn't have the WITHDRAW_ROLE + function testWithdrawFail(bool useFactory, address withdrawTo, uint256 amount) public withFactory(useFactory) { + token.revokeRole(token.DEFAULT_ADMIN_ROLE(), address(this)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(token.DEFAULT_ADMIN_ROLE()), 32) + ) + ); + token.withdraw(withdrawTo, amount); + } + + // Withdraw success ETH + function testWithdrawETH(bool useFactory, address withdrawTo, uint256 tokenId, uint256 amount) + public + withFactory(useFactory) + { + // Address 9 doesnt receive ETH + vm.assume(withdrawTo != address(9)); + testMintSingleSuccess(false, withdrawTo, tokenId, amount); + + uint256 tokenBalance = address(token).balance; + uint256 balance = withdrawTo.balance; + token.withdraw(withdrawTo, tokenBalance); + assertEq(tokenBalance + balance, withdrawTo.balance); + } + + // Withdraw success ERC20 + function testWithdrawERC20(bool useFactory, address withdrawTo, uint256 tokenId, uint256 amount) + public + withFactory(useFactory) + { + testERC20Mint(false, withdrawTo, tokenId, amount); + + uint256 tokenBalance = erc20.balanceOf(address(token)); + uint256 balance = erc20.balanceOf(withdrawTo); + token.withdraw(withdrawTo, tokenBalance); + assertEq(tokenBalance + balance, erc20.balanceOf(withdrawTo)); + } + + // + // Helpers + // + modifier withFactory(bool useFactory) { + if (useFactory) { + setUpFromFactory(); + } + _; + } + + modifier assumeSafe(address nonContract, uint256 tokenId, uint256 amount) { + vm.assume(uint160(nonContract) > 16); + vm.assume(nonContract.code.length == 0); + vm.assume(tokenId < 100); + vm.assume(amount > 0 && amount < 20); + _; + } + + // Create ERC20. Give this contract 1000 ERC20 tokens. Approve token to spend 100 ERC20 tokens. + modifier withERC20() { + erc20 = new ERC20Mock(); + erc20.mockMint(address(this), 1000 ether); + erc20.approve(address(token), 1000 ether); + _; + } + + modifier withGlobalSaleActive() { + token.setGlobalSaleDetails( + address(0), perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1) + ); + _; + } + + modifier withTokenSaleActive(uint256 tokenId) { + setTokenSaleActive(tokenId); + _; + } + + function setTokenSaleActive(uint256 tokenId) private { + token.setTokenSaleDetails(tokenId, perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + } + + function singleToArray(uint256 value) private pure returns (uint256[] memory) { + uint256[] memory values = new uint256[](1); + values[0] = value; + return values; + } +} From 404fcafdfc2032cf428c19c0b091fe2a8328aaef Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 20 Jun 2023 08:41:23 +1200 Subject: [PATCH 27/49] Remove Packed --- .../ERC1155/Packed/ERC1155PackedSale.sol | 295 ---------- .../Packed/ERC1155PackedSaleFactory.sol | 36 -- .../ERC1155/Packed/ERC1155PackedSupply.sol | 160 ------ .../ERC1155/Packed/ERC1155PackedToken.sol | 126 ----- .../Packed/ERC1155PackedTokenFactory.sol | 37 -- .../Packed/IERC1155PackedTokenFactory.sol | 22 - src/tokens/ERC1155/Packed/README.md | 41 -- .../ERC1155/Packed/ERC1155PackedSale.t.sol | 518 ------------------ .../ERC1155/Packed/ERC1155PackedToken.t.sol | 312 ----------- 9 files changed, 1547 deletions(-) delete mode 100644 src/tokens/ERC1155/Packed/ERC1155PackedSale.sol delete mode 100644 src/tokens/ERC1155/Packed/ERC1155PackedSaleFactory.sol delete mode 100644 src/tokens/ERC1155/Packed/ERC1155PackedSupply.sol delete mode 100644 src/tokens/ERC1155/Packed/ERC1155PackedToken.sol delete mode 100644 src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol delete mode 100644 src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol delete mode 100644 src/tokens/ERC1155/Packed/README.md delete mode 100644 test/tokens/ERC1155/Packed/ERC1155PackedSale.t.sol delete mode 100644 test/tokens/ERC1155/Packed/ERC1155PackedToken.t.sol diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedSale.sol b/src/tokens/ERC1155/Packed/ERC1155PackedSale.sol deleted file mode 100644 index e822cfa..0000000 --- a/src/tokens/ERC1155/Packed/ERC1155PackedSale.sol +++ /dev/null @@ -1,295 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import {IERC1155Sale} from "../IERC1155Sale.sol"; -import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -import {ERC1155PackedSupply} from "./ERC1155PackedSupply.sol"; -import {ERC1155PackedBalance} from "@0xsequence/erc-1155/contracts/tokens/ERC1155PackedBalance/ERC1155PackedBalance.sol"; -import {ERC1155SaleErrors} from "../ERC1155SaleErrors.sol"; -import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; -import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -contract ERC1155PackedSale is - IERC1155Sale, - ERC1155SaleErrors, - ERC1155PackedSupply, - ERC1155Metadata, - ERC2981, - AccessControl -{ - bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); - bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); - - bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = - bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); - - bool private _initialized; - - // ERC20 token address for payment. address(0) indicated payment in ETH. - address private _paymentToken; - - SaleDetails private _globalSaleDetails; - mapping(uint256 => SaleDetails) private _tokenSaleDetails; - - /** - * Initialize with empty values. - * @dev These are overridden by initialize(). - */ - constructor() ERC1155Metadata("", "") {} - - /** - * Initialize the contract. - * @param _owner Owner address. - * @param _name Token name. - * @param _baseURI Base URI for token metadata. - * @dev This should be called immediately after deployment. - */ - function initialize(address _owner, string memory _name, string memory _baseURI) public { - if (_initialized) { - revert InvalidInitialization(); - } - _initialized = true; - name = _name; - baseURI = _baseURI; - _setupRole(DEFAULT_ADMIN_ROLE, _owner); - _setupRole(MINT_ADMIN_ROLE, _owner); - _setupRole(ROYALTY_ADMIN_ROLE, _owner); - } - - /** - * Checks if the current block.timestamp is out of the give timestamp range. - * @param startTime Earliest acceptable timestamp (inclusive). - * @param endTime Latest acceptable timestamp (exclusive). - * @dev A zero endTime value is always considered out of bounds. - */ - function blockTimeOutOfBounds(uint256 startTime, uint256 endTime) private view returns (bool) { - // 0 end time indicates inactive sale. - return endTime == 0 || block.timestamp < startTime || block.timestamp >= endTime; - } - - /** - * Checks the sale is active and takes payment. - * @param _tokenIds Token IDs to mint. - * @param _amounts Amounts of tokens to mint. - */ - function payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts) private { - uint256 totalAmount; - uint256 totalCost; - SaleDetails memory gSaleDetails = _globalSaleDetails; - bool globalSaleInactive = blockTimeOutOfBounds(gSaleDetails.startTime, gSaleDetails.endTime); - for (uint256 i; i < _tokenIds.length; i++) { - // Active sale test - SaleDetails memory saleDetails = _tokenSaleDetails[_tokenIds[i]]; - bool tokenSaleInactive = blockTimeOutOfBounds(saleDetails.startTime, saleDetails.endTime); - if (tokenSaleInactive) { - // Prefer token sale - if (globalSaleInactive) { - // Both sales inactive - revert SaleInactive(_tokenIds[i]); - } - // Use global sale details - totalCost += gSaleDetails.cost * _amounts[i]; - } else { - // Use token sale price - totalCost += saleDetails.cost * _amounts[i]; - } - totalAmount += _amounts[i]; - } - - if (_paymentToken == address(0)) { - // Paid in ETH - if (msg.value != totalCost) { - revert InsufficientPayment(totalCost, msg.value); - } - } else { - // Paid in ERC20 - (bool success, bytes memory data) = _paymentToken.call( - abi.encodeWithSelector(ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), totalCost) - ); - if (!success || (data.length > 0 && !abi.decode(data, (bool)))) { - revert InsufficientPayment(totalCost, 0); - } - } - } - - // - // Minting - // - - /** - * Mint tokens. - * @param _to Address to mint tokens to. - * @param _tokenIds Token IDs to mint. - * @param _amounts Amounts of tokens to mint. - * @param _data Data to pass if receiver is contract. - * @notice Sale must be active for all tokens. - */ - function mint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) - public - payable - { - payForActiveMint(_tokenIds, _amounts); - _batchMint(_to, _tokenIds, _amounts, _data); - } - - /** - * Mint tokens as admin. - * @param _to Address to mint tokens to. - * @param _tokenIds Token IDs to mint. - * @param _amounts Amounts of tokens to mint. - * @param _data Data to pass if receiver is contract. - * @notice Only callable by mint admin. - */ - function mintAdmin(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) - public - onlyRole(MINT_ADMIN_ROLE) - { - _batchMint(_to, _tokenIds, _amounts, _data); - } - - /** - * Set the global sale details. - * @param _paymentTokenAddr The ERC20 token address to accept payment in. address(0) indicates ETH. - * @param _cost The amount of payment tokens to accept for each token minted. - * @param _supplyCap The maximum number of tokens that can be minted. - * @param _startTime The start time of the sale. Tokens cannot be minted before this time. - * @param _endTime The end time of the sale. Tokens cannot be minted after this time. - * @dev A zero end time indicates an inactive sale. - */ - function setGlobalSaleDetails( - address _paymentTokenAddr, - uint256 _cost, - uint256 _supplyCap, - uint64 _startTime, - uint64 _endTime - ) - public - onlyRole(MINT_ADMIN_ROLE) - { - _paymentToken = _paymentTokenAddr; - totalSupplyCap = _supplyCap; - _globalSaleDetails = SaleDetails(_cost, _startTime, _endTime); - emit GlobalSaleDetailsUpdated(_cost, _supplyCap, _startTime, _endTime); - } - - /** - * Set the sale details for an individual token. - * @param _tokenId The token ID to set the sale details for. - * @param _cost The amount of payment tokens to accept for each token minted. - * @param _supplyCap The maximum number of tokens that can be minted. - * @param _startTime The start time of the sale. Tokens cannot be minted before this time. - * @param _endTime The end time of the sale. Tokens cannot be minted after this time. - * @dev A zero end time indicates an inactive sale. - */ - function setTokenSaleDetails( - uint256 _tokenId, - uint256 _cost, - uint256 _supplyCap, - uint64 _startTime, - uint64 _endTime - ) - public - onlyRole(MINT_ADMIN_ROLE) - { - tokenSupplyCap[_tokenId] = _supplyCap; - _tokenSaleDetails[_tokenId] = SaleDetails(_cost, _startTime, _endTime); - emit TokenSaleDetailsUpdated(_tokenId, _cost, _supplyCap, _startTime, _endTime); - } - - // - // Royalty - // - - /** - * Sets the royalty information that all ids in this contract will default to. - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - */ - function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) public onlyRole(ROYALTY_ADMIN_ROLE) { - _setDefaultRoyalty(_receiver, _feeNumerator); - } - - /** - * Sets the royalty information that a given token id in this contract will use. - * @param _tokenId The token id to set the royalty information for - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @notice This overrides the default royalty information for this token id - */ - function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) - public - onlyRole(ROYALTY_ADMIN_ROLE) - { - _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); - } - - // - // Withdraw - // - - /** - * Withdraws ETH or ERC20 tokens owned by this sale contract. - * @param _to Address to withdraw to. - * @param _amount Amount to withdraw. - * @dev Withdraws ERC20 when paymentToken is set, else ETH. - * @notice Only callable by the contract admin. - */ - function withdraw(address _to, uint256 _amount) public onlyRole(DEFAULT_ADMIN_ROLE) { - if (_paymentToken == address(0)) { - (bool success,) = _to.call{value: _amount}(""); - if (!success) { - revert WithdrawFailed(); - } - } else { - (bool success) = IERC20(_paymentToken).transfer(_to, _amount); - if (!success) { - revert WithdrawFailed(); - } - } - } - - // - // Views - // - - /** - * Get global sales details. - * @return Sale details. - * @notice Global sales details apply to all tokens. - * @notice Global sales details are overriden when token sale is active. - */ - function globalSaleDetails() external view returns (SaleDetails memory) { - return _globalSaleDetails; - } - - /** - * Get token sale details. - * @param _tokenId Token ID to get sale details for. - * @return Sale details. - * @notice Token sale details override global sale details. - */ - function tokenSaleDetails(uint256 _tokenId) external view returns (SaleDetails memory) { - return _tokenSaleDetails[_tokenId]; - } - - /** - * Get payment token. - * @return Payment token address. - * @notice address(0) indicates payment in ETH. - */ - function paymentToken() external view returns (address) { - return _paymentToken; - } - - function supportsInterface(bytes4 _interfaceId) - public - view - override (ERC1155PackedBalance, ERC1155Metadata, ERC2981, AccessControl) - returns (bool) - { - // FIXME Fix inheritance issues - return ERC1155PackedBalance.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId) - || super.supportsInterface(_interfaceId); - } -} diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedSaleFactory.sol b/src/tokens/ERC1155/Packed/ERC1155PackedSaleFactory.sol deleted file mode 100644 index 9999fd3..0000000 --- a/src/tokens/ERC1155/Packed/ERC1155PackedSaleFactory.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.4; - -import {ERC1155PackedSale} from "./ERC1155PackedSale.sol"; -import {IERC1155SaleFactory} from "../IERC1155SaleFactory.sol"; -import {ProxyDeployer} from "../../../proxies/ERC1967/ProxyDeployer.sol"; - -contract ERC1155PackedSaleFactory is IERC1155SaleFactory, ProxyDeployer { - address private immutable _implAddr; - - /** - * Creates an ERC-1155 Sale Factory. - */ - constructor() { - ERC1155PackedSale proxyImpl = new ERC1155PackedSale(); - _implAddr = address(proxyImpl); - } - - /** - * Creates an ERC-1155 Packed Sale proxy contract - * @param _owner The owner of the ERC-1155 Sale - * @param _name The name of the ERC-1155 Sale token - * @param _baseURI The base URI of the ERC-1155 Sale token - * @param _salt The deployment salt - * @return proxyAddr The address of the ERC-1155 Sale Proxy - */ - function deployERC1155Sale(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) - external - returns (address proxyAddr) - { - proxyAddr = deployProxy(_implAddr, _salt); - ERC1155PackedSale(proxyAddr).initialize(_owner, _name, _baseURI); - emit ERC1155SaleDeployed(proxyAddr); - return proxyAddr; - } -} diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedSupply.sol b/src/tokens/ERC1155/Packed/ERC1155PackedSupply.sol deleted file mode 100644 index a4b8b54..0000000 --- a/src/tokens/ERC1155/Packed/ERC1155PackedSupply.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import {ERC1155MetaPackedBalance} from - "@0xsequence/erc-1155/contracts/tokens/ERC1155PackedBalance/ERC1155MetaPackedBalance.sol"; -import {ERC1155SupplyErrors} from "../ERC1155SupplyErrors.sol"; - -/** - * An ERC1155 extension that tracks token supply. - */ -contract ERC1155PackedSupply is ERC1155MetaPackedBalance, ERC1155SupplyErrors { - // Maximum supply globally and per token. 0 indicates unlimited supply - uint256 public totalSupplyCap; - mapping(uint256 => uint256) public tokenSupplyCap; - - uint256 public totalSupply; - mapping(uint256 => uint256) public tokenSupply; - - /** - * Mint _amount of tokens of a given id - * @param _to The address to mint tokens to - * @param _id Token id to mint - * @param _amount The amount to be minted - * @param _data Data to pass if receiver is contract - */ - function _mint(address _to, uint256 _id, uint256 _amount, bytes memory _data) internal { - // Check supply cap - totalSupply += _amount; - if (totalSupplyCap > 0 && totalSupply > totalSupplyCap) { - revert InsufficientSupply(); - } - tokenSupply[_id] += _amount; - if (tokenSupplyCap[_id] > 0 && tokenSupply[_id] > tokenSupplyCap[_id]) { - revert InsufficientSupply(); - } - - //Add _amount - _updateIDBalance(_to, _id, _amount, Operations.Add); // Add amount to recipient - - // Emit event - emit TransferSingle(msg.sender, address(0x0), _to, _id, _amount); - - // Calling onReceive method if recipient is contract - _callonERC1155Received(address(0x0), _to, _id, _amount, gasleft(), _data); - } - - /** - * Mint tokens for each ids in _ids - * @param _to The address to mint tokens to - * @param _ids Array of ids to mint - * @param _amounts Array of amount of tokens to mint per id - * @param _data Data to pass if receiver is contract - */ - function _batchMint(address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data) internal { - uint256 nMint = _ids.length; - if (nMint == 0 || nMint != _amounts.length) { - revert InvalidArrayLength(); - } - - // Update supply balance - uint256 id0 = _ids[0]; - uint256 amount0 = _amounts[0]; - totalSupply += amount0; - tokenSupply[id0] += amount0; - if (tokenSupplyCap[id0] > 0 && tokenSupply[id0] > tokenSupplyCap[id0]) { - revert InsufficientSupply(); - } - // Load first bin and index where the token ID balance exists - (uint256 bin, uint256 index) = getIDBinIndex(id0); - - // Balance for current bin in memory (initialized with first transfer) - uint256 balTo = _viewUpdateBinValue(balances[_to][bin], index, amount0, Operations.Add); - - // Last bin updated - uint256 lastBin = bin; - - for (uint256 i = 1; i < nMint; i++) { - // Update supply balance - totalSupply += _amounts[i]; - tokenSupply[_ids[i]] += _amounts[i]; - if (tokenSupplyCap[_ids[i]] > 0 && tokenSupply[_ids[i]] > tokenSupplyCap[_ids[i]]) { - revert InsufficientSupply(); - } - - (bin, index) = getIDBinIndex(_ids[i]); - - // If new bin - if (bin != lastBin) { - // Update storage balance of previous bin - balances[_to][lastBin] = balTo; - balTo = balances[_to][bin]; - - // Bin will be the most recent bin - lastBin = bin; - } - - // Update memory balance - balTo = _viewUpdateBinValue(balTo, index, _amounts[i], Operations.Add); - } - - // Validate total supply cap - if (totalSupplyCap > 0 && totalSupply > totalSupplyCap) { - revert InsufficientSupply(); - } - - // Update storage of the last bin visited - balances[_to][bin] = balTo; - - // //Emit event - emit TransferBatch(msg.sender, address(0x0), _to, _ids, _amounts); - - // Calling onReceive method if recipient is contract - _callonERC1155BatchReceived(address(0x0), _to, _ids, _amounts, gasleft(), _data); - } - - /** - * Burn _amount of tokens of a given token id - * @param _from The address to burn tokens from - * @param _id Token id to burn - * @param _amount The amount to be burned - */ - function _burn(address _from, uint256 _id, uint256 _amount) internal { - // Supply - totalSupply -= _amount; - tokenSupply[_id] -= _amount; - - // Substract _amount - _updateIDBalance(_from, _id, _amount, Operations.Sub); - - // Emit event - emit TransferSingle(msg.sender, _from, address(0x0), _id, _amount); - } - - /** - * Burn tokens of given token id for each (_ids[i], _amounts[i]) pair - * @param _from The address to burn tokens from - * @param _ids Array of token ids to burn - * @param _amounts Array of the amount to be burned - */ - function _batchBurn(address _from, uint256[] memory _ids, uint256[] memory _amounts) internal { - // Number of mints to execute - uint256 nBurn = _ids.length; - if (nBurn == 0 || nBurn != _amounts.length) { - revert InvalidArrayLength(); - } - - // Executing all minting - for (uint256 i = 0; i < nBurn; i++) { - // Update supplies - totalSupply -= _amounts[i]; - tokenSupply[_ids[i]] -= _amounts[i]; - - // Update storage balance - _updateIDBalance(_from, _ids[i], _amounts[i], Operations.Sub); // Add amount to recipient - } - - // Emit batch burn event - emit TransferBatch(msg.sender, _from, address(0x0), _ids, _amounts); - } -} diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol deleted file mode 100644 index f0ba098..0000000 --- a/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import { - ERC1155PackedBalance, - ERC1155MintBurnPackedBalance -} from "@0xsequence/erc-1155/contracts/tokens/ERC1155PackedBalance/ERC1155MintBurnPackedBalance.sol"; -import {ERC1155MetaPackedBalance} from - "@0xsequence/erc-1155/contracts/tokens/ERC1155PackedBalance/ERC1155MetaPackedBalance.sol"; -import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -import {ERC2981Controlled} from "../../common/ERC2981Controlled.sol"; - -error InvalidInitialization(); - -/** - * A ready made implementation of ERC-1155. - */ -contract ERC1155PackedToken is - ERC1155MintBurnPackedBalance, - ERC1155MetaPackedBalance, - ERC1155Metadata,ERC2981Controlled -{ - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); - - address private immutable _initializer; - bool private _initialized; - - /** - * Initialize contract. - */ - constructor() ERC1155Metadata("", "") { - _initializer = msg.sender; - } - - /** - * Initialize the contract. - * @param owner Owner address. - * @param tokenName Token name. - * @param tokenBaseURI Base URI for token metadata. - * @dev This should be called immediately after deployment. - */ - function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public { - if (msg.sender != _initializer || _initialized) { - revert InvalidInitialization(); - } - _initialized = true; - - name = tokenName; - baseURI = tokenBaseURI; - - _setupRole(DEFAULT_ADMIN_ROLE, owner); - _setupRole(MINTER_ROLE, owner); - _setupRole(ROYALTY_ADMIN_ROLE, owner); - _setupRole(METADATA_ADMIN_ROLE, owner); - } - - // - // Minting - // - - /** - * Mint tokens. - * @param to Address to mint tokens to. - * @param tokenId Token ID to mint. - * @param amount Amount of tokens to mint. - * @param data Data to pass if receiver is contract. - */ - function mint(address to, uint256 tokenId, uint256 amount, bytes memory data) external onlyRole(MINTER_ROLE) { - _mint(to, tokenId, amount, data); - } - - /** - * Mint tokens. - * @param to Address to mint tokens to. - * @param tokenIds Token IDs to mint. - * @param amounts Amounts of tokens to mint. - * @param data Data to pass if receiver is contract. - */ - function batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data) - external - onlyRole(MINTER_ROLE) - { - _batchMint(to, tokenIds, amounts, data); - } - - // - // Metadata - // - - /** - * Update the base URL of token's URI. - * @param tokenBaseURI New base URL of token's URI - */ - function setBaseMetadataURI(string memory tokenBaseURI) external onlyRole(METADATA_ADMIN_ROLE) { - _setBaseMetadataURI(tokenBaseURI); - } - - /** - * Update the name of the contract. - * @param tokenName New contract name - */ - function setContractName(string memory tokenName) external onlyRole(METADATA_ADMIN_ROLE) { - _setContractName(tokenName); - } - - // - // Views - // - - /** - * Check interface support. - * @param interfaceId Interface id - * @return True if supported - */ - function supportsInterface(bytes4 interfaceId) - public - view - override (ERC1155PackedBalance, ERC1155Metadata, ERC2981Controlled) - returns (bool) - { - return ERC1155PackedBalance.supportsInterface(interfaceId) || ERC1155Metadata.supportsInterface(interfaceId) - || ERC2981Controlled.supportsInterface(interfaceId) - || super.supportsInterface(interfaceId); - } -} diff --git a/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol b/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol deleted file mode 100644 index 9581a98..0000000 --- a/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import {ERC1155PackedToken} from "./ERC1155PackedToken.sol"; -import {IERC1155PackedTokenFactory} from "./IERC1155PackedTokenFactory.sol"; -import {ProxyDeployer} from "../../../proxies/ERC1967/ProxyDeployer.sol"; - -contract ERC1155PackedTokenFactory is IERC1155PackedTokenFactory, ProxyDeployer { - address private immutable _implAddr; - - /** - * Creates an ERC-1155 Token Factory. - */ - constructor() { - ERC1155PackedToken proxyImpl = new ERC1155PackedToken(); - _implAddr = address(proxyImpl); - } - - /** - * Creates an ERC-1155 Packed Token proxy. - * @param owner The owner of the ERC-1155 Packed Token proxy - * @param name The name of the ERC-1155 Packed Token proxy - * @param baseURI The base URI of the ERC-1155 Packed Token proxy - * @param salt The deployment salt - * @return proxyAddr The address of the ERC-1155 Packed Token Proxy - * @dev The provided `salt` is hashed with the caller address for security. - */ - function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) - external - returns (address proxyAddr) - { - proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); - ERC1155PackedToken(proxyAddr).initialize(owner, name, baseURI); - emit ERC1155PackedTokenDeployed(proxyAddr); - return proxyAddr; - } -} diff --git a/src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol b/src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol deleted file mode 100644 index 27ab133..0000000 --- a/src/tokens/ERC1155/Packed/IERC1155PackedTokenFactory.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -interface IERC1155PackedTokenFactory { - /** - * Event emitted when a new ERC-1155 Packed Token proxy contract is deployed. - * @param proxyAddr The address of the deployed proxy. - */ - event ERC1155PackedTokenDeployed(address proxyAddr); - - /** - * Creates an ERC-1155 Packed Token proxy. - * @param owner The owner of the ERC-1155 Packed Token proxy - * @param name The name of the ERC-1155 Packed Token proxy - * @param baseURI The base URI of the ERC-1155 Packed Token proxy - * @param salt The deployment salt - * @return proxyAddr The address of the ERC-1155 Packed Token Proxy - */ - function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) - external - returns (address proxyAddr); -} diff --git a/src/tokens/ERC1155/Packed/README.md b/src/tokens/ERC1155/Packed/README.md deleted file mode 100644 index 670f535..0000000 --- a/src/tokens/ERC1155/Packed/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# ERC1155 Packed Contracts - -This subsection contains contracts related to the [ERC1155 token standard](https://eips.ethereum.org/EIPS/eip-1155). The implementation utilises the [0xSequence Packed Balance implementation](https://github.com/0xsequence/erc-1155/blob/master/SPECIFICATIONS.md#packed-balance) for gas efficiency. - -## ERC1155PackedToken - -This contract is a complete, ready-to-use implementation of the ERC-1155 token standard. It includes additional features from the ERC1155MintBurn, ERC1155Meta, and ERC1155Metadata contracts. These contracts provide minting capabilities, support for meta transactions, and metadata functionality. - -Meta transactions are provided by the [0xSequence ERC1155 library](https://github.com/0xsequence/erc-1155/blob/master/SPECIFICATIONS.md#meta-transactions). Please refer to library documentation for more information on meta transactions. - -The ERC1155PackedToken contract has a two-step deployment process. First, it's deployed with an empty constructor. After deployment, the `initialize` function must be called to set the owner, name, and base URI. This process is in place to support proxy deployments with the ERC1155PackedTokenFactory. - -### Functions - -* `initialize(address owner, string memory name_, string memory baseURI_)`: Initializes the token contract, setting the owner, name, and base URI. -* `mint(address to, uint256 tokenId, uint256 amount, bytes memory data)`: Mints the specified amount of tokens of a given ID to the specified address. This function is restricted to addresses with the Minter role. -* `batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data)`: Mints specified amounts of tokens of given IDs to the specified address. This function is restricted to addresses with the Minter role. -* `setBaseMetadataURI(string memory baseURI_)`: Updates the base URI for the token metadata. This function is restricted to addresses with the Metadata Admin role. -* `setContractName(string memory name_)`: Updates the contract's name. This function is restricted to addresses with the Metadata Admin role. - -## ERC1155PackedTokenFactory - -This contract deploys ERC1155PackedToken contracts. It uses a proxy pattern to create new token instances to reduce gas costs. - -The deployment uses a `salt` which is combined with the caller's address for cross chain consistency and security. - -### Functions - -* `deploy(address owner, string memory name, string memory baseURI, bytes32 salt)`: Deploys a new ERC1155PackedToken proxy contract, initializes it, and emits an ERC1155PackedTokenDeployed event. - -## Usage - -To create a new ERC1155 token: - -1. Deploy the ERC1155PackedTokenFactory contract (or use an existing deployment). -2. Call the `deploy` function on the factory, providing the desired parameters. -3. A new ERC1155PackedToken contract will be created and initialized, ready for use. - -## Dependencies - -This repository relies on the ERC1155, ERC1155MintBurn, ERC1155Meta, ERC1155Metadata contracts from 0xSequence for core ERC-1155 functionality, AccessControl from OpenZeppelin for role base permissions and the ERC2981Controlled contract for handling of royalties. diff --git a/test/tokens/ERC1155/Packed/ERC1155PackedSale.t.sol b/test/tokens/ERC1155/Packed/ERC1155PackedSale.t.sol deleted file mode 100644 index 8914395..0000000 --- a/test/tokens/ERC1155/Packed/ERC1155PackedSale.t.sol +++ /dev/null @@ -1,518 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import "forge-std/Test.sol"; -import {ERC1155PackedSale} from "src/tokens/ERC1155/Packed/ERC1155PackedSale.sol"; -import {ERC1155PackedSaleFactory} from "src/tokens/ERC1155/Packed/ERC1155PackedSaleFactory.sol"; -import {ERC1155SaleErrors} from "src/tokens/ERC1155/ERC1155SaleErrors.sol"; -import {ERC1155SupplyErrors} from "src/tokens/ERC1155/ERC1155SupplyErrors.sol"; - -import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; - -// Interfaces -import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; -import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; -import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; - -contract ERC1155PackedSaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { - // Redeclare events - event TransferBatch( - address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts - ); - - ERC1155PackedSale private token; - ERC20Mock private erc20; - uint256 private perTokenCost = 0.02 ether; - - function setUp() public { - token = new ERC1155PackedSale(); - token.initialize(address(this), "test", "ipfs://"); - - vm.deal(address(this), 100 ether); - } - - function setUpFromFactory() public { - ERC1155PackedSaleFactory factory = new ERC1155PackedSaleFactory(); - token = ERC1155PackedSale(factory.deployERC1155Sale(address(this), "test", "ipfs://", "")); - } - - function testSupportsInterface() public { - assertTrue(token.supportsInterface(type(IERC165).interfaceId)); - assertTrue(token.supportsInterface(type(IERC1155).interfaceId)); - assertTrue(token.supportsInterface(type(IERC1155Metadata).interfaceId)); - assertTrue(token.supportsInterface(type(IAccessControl).interfaceId)); - } - - // - // Minting - // - - // Minting denied when no sale active. - function testMintInactiveFail(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - { - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); - } - - // Minting denied when sale is active but not for the token. - function testMintInactiveSingleFail(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - withTokenSaleActive(tokenId + 1) - { - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); - } - - // Minting denied when token sale is expired. - function testMintExpiredSingleFail( - bool useFactory, - address mintTo, - uint256 tokenId, - uint256 amount, - uint256 startTime, - uint256 endTime - ) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - { - vm.assume(startTime > endTime); - vm.assume(block.timestamp < startTime || block.timestamp >= endTime); - token.setTokenSaleDetails(tokenId, perTokenCost, 0, uint64(startTime), uint64(endTime)); - - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); - } - - // Minting denied when global sale is expired. - function testMintExpiredGlobalFail( - bool useFactory, - address mintTo, - uint256 tokenId, - uint256 amount, - uint256 startTime, - uint256 endTime - ) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - { - vm.assume(startTime > endTime); - vm.assume(block.timestamp < startTime || block.timestamp >= endTime); - token.setGlobalSaleDetails(address(0), perTokenCost, 0, uint64(startTime), uint64(endTime)); - - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); - } - - // Minting denied when sale is active but not for all tokens in the group. - function testMintInactiveInGroupFail(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - withTokenSaleActive(tokenId) - { - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = tokenId; - tokenIds[1] = tokenId + 1; - uint256[] memory amounts = new uint256[](2); - amounts[0] = amount; - amounts[1] = amount; - - vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId + 1)); - token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, ""); - } - - // Minting denied when global supply exceeded. - function testMintGlobalSupplyExceeded( - bool useFactory, - address mintTo, - uint256 tokenId, - uint256 amount, - uint256 supplyCap - ) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - { - vm.assume(supplyCap > 0); - vm.assume(amount > supplyCap); - token.setGlobalSaleDetails( - address(0), perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) - ); - - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - vm.expectRevert(InsufficientSupply.selector); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); - } - - // Minting denied when token supply exceeded. - function testMintTokenSupplyExceeded( - bool useFactory, - address mintTo, - uint256 tokenId, - uint256 amount, - uint256 supplyCap - ) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - { - vm.assume(supplyCap > 0); - vm.assume(amount > supplyCap); - token.setTokenSaleDetails( - tokenId, perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) - ); - - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - vm.expectRevert(InsufficientSupply.selector); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); - } - - // Minting allowed when sale is active globally. - function testDebugMe(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - withGlobalSaleActive - { - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - uint256 count = token.balanceOf(mintTo, tokenId); - vm.expectEmit(true, true, true, true); - emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); - assertEq(count + amount, token.balanceOf(mintTo, tokenId)); - } - - // Minting allowed when sale is active for the token. - function testMintSingleSuccess(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - withTokenSaleActive(tokenId) - { - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - uint256 count = token.balanceOf(mintTo, tokenId); - vm.expectEmit(true, true, true, true); - emit TransferBatch(address(this), address(0), address(69), tokenIds, amounts); - token.mint{value: amount * perTokenCost}(address(69), tokenIds, amounts, ""); - assertEq(count + amount, token.balanceOf(address(69), tokenId)); - } - - // Minting allowed when sale is active for both tokens individually. - function testMintGroupSuccess(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - { - setTokenSaleActive(tokenId); - setTokenSaleActive(tokenId + 1); - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = tokenId; - tokenIds[1] = tokenId + 1; - uint256[] memory amounts = new uint256[](2); - amounts[0] = amount; - amounts[1] = amount; - - uint256 count = token.balanceOf(mintTo, tokenId); - uint256 count2 = token.balanceOf(mintTo, tokenId + 1); - vm.expectEmit(true, true, true, true); - emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, ""); - assertEq(count + amount, token.balanceOf(mintTo, tokenId)); - assertEq(count2 + amount, token.balanceOf(mintTo, tokenId + 1)); - } - - // Minting allowed when global sale is free. - function testFreeGlobalMint(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - { - token.setGlobalSaleDetails(address(0), 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - uint256 count = token.balanceOf(mintTo, tokenId); - vm.expectEmit(true, true, true, true); - emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint(mintTo, tokenIds, amounts, ""); - assertEq(count + amount, token.balanceOf(mintTo, tokenId)); - } - - // Minting allowed when token sale is free and global is not. - function testFreeTokenMint(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - withGlobalSaleActive - { - token.setTokenSaleDetails(tokenId, 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - uint256 count = token.balanceOf(mintTo, tokenId); - vm.expectEmit(true, true, true, true); - emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint(mintTo, tokenIds, amounts, ""); - assertEq(count + amount, token.balanceOf(mintTo, tokenId)); - } - - // Minting allowed when mint charged with ERC20. - function testERC20Mint(bool useFactory, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - withERC20 - { - token.setGlobalSaleDetails( - address(erc20), perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1) - ); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - uint256 cost = amount * perTokenCost; - - uint256 balanace = erc20.balanceOf(address(this)); - uint256 count = token.balanceOf(mintTo, tokenId); - vm.expectEmit(true, true, true, true); - emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint(mintTo, tokenIds, amounts, ""); - assertEq(count + amount, token.balanceOf(mintTo, tokenId)); - assertEq(balanace - cost, erc20.balanceOf(address(this))); - assertEq(cost, erc20.balanceOf(address(token))); - } - - // - // Admin minting - // - - // Admin minting denied when not admin. - function testMintAdminFail(bool useFactory, address minter, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - { - vm.assume(minter != address(this)); - vm.assume(minter != address(0)); - - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - vm.expectRevert( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(minter), - " is missing role ", - Strings.toHexString(uint256(token.MINT_ADMIN_ROLE()), 32) - ) - ); - vm.prank(minter); - token.mintAdmin(mintTo, tokenIds, amounts, ""); - } - - // Minting as admin success. - function testMintAdminSuccess(bool useFactory, address minter, address mintTo, uint256 tokenId, uint256 amount) - public - assumeSafe(mintTo, tokenId, amount) - withFactory(useFactory) - { - token.grantRole(token.MINT_ADMIN_ROLE(), minter); - - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); - - uint256 count = token.balanceOf(mintTo, tokenId); - token.mintAdmin(mintTo, tokenIds, amounts, ""); - assertEq(count + amount, token.balanceOf(mintTo, tokenId)); - } - - // - // Royalty - // - - // Token royalty fails if the caller doesn't have the ROYALTY_ADMIN_ROLE - function testSetTokenRoyaltyFail(uint256 _tokenId, address _receiver, uint96 _feeNumerator) public { - token.revokeRole(token.ROYALTY_ADMIN_ROLE(), address(this)); - - vm.expectRevert( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(address(this)), - " is missing role ", - Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) - ) - ); - token.setTokenRoyalty(_tokenId, _receiver, _feeNumerator); - } - - // Default royalty fails if the caller doesn't have the ROYALTY_ADMIN_ROLE - function testSetDefaultRoyaltyFail(address _receiver, uint96 _feeNumerator) public { - token.revokeRole(token.ROYALTY_ADMIN_ROLE(), address(this)); - - vm.expectRevert( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(address(this)), - " is missing role ", - Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) - ) - ); - token.setDefaultRoyalty(_receiver, _feeNumerator); - } - - // Royalty calculation for token with custom royalty information - function testRoyaltyInfoCustom(uint256 _tokenId, uint256 _salePrice, address _receiver, uint96 _feeNumerator) - public - { - vm.assume(_receiver != address(0)); - vm.assume(_feeNumerator < 10000); - vm.assume(_salePrice < 10000 ether); - - token.setTokenRoyalty(_tokenId, _receiver, _feeNumerator); - uint256 expectedRoyaltyAmount = (_salePrice * _feeNumerator) / 10000; - - (address receiver, uint256 royaltyAmount) = token.royaltyInfo(_tokenId, _salePrice); - - assertEq(_receiver, receiver); - assertEq(expectedRoyaltyAmount, royaltyAmount); - } - - // Test royalty calculation for token with default royalty information - function testRoyaltyInfoDefault(uint256 _tokenId, uint256 _salePrice, address _receiver, uint96 _feeNumerator) - public - { - vm.assume(_receiver != address(0)); - vm.assume(_feeNumerator < 10000); - vm.assume(_salePrice < 10000 ether); - - token.setDefaultRoyalty(_receiver, _feeNumerator); - uint256 expectedRoyaltyAmount = (_salePrice * _feeNumerator) / 10000; - - (address receiver, uint256 royaltyAmount) = token.royaltyInfo(_tokenId, _salePrice); - - assertEq(_receiver, receiver); - assertEq(expectedRoyaltyAmount, royaltyAmount); - } - - // - // Withdraw - // - - // Withdraw fails if the caller doesn't have the WITHDRAW_ROLE - function testWithdrawFail(bool useFactory, address withdrawTo, uint256 amount) public withFactory(useFactory) { - token.revokeRole(token.DEFAULT_ADMIN_ROLE(), address(this)); - - vm.expectRevert( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(address(this)), - " is missing role ", - Strings.toHexString(uint256(token.DEFAULT_ADMIN_ROLE()), 32) - ) - ); - token.withdraw(withdrawTo, amount); - } - - // Withdraw success ETH - function testWithdrawETH(bool useFactory, address withdrawTo, uint256 tokenId, uint256 amount) - public - withFactory(useFactory) - { - // Address 9 doesnt receive ETH - vm.assume(withdrawTo != address(9)); - testMintSingleSuccess(false, withdrawTo, tokenId, amount); - - uint256 tokenBalance = address(token).balance; - uint256 balance = withdrawTo.balance; - token.withdraw(withdrawTo, tokenBalance); - assertEq(tokenBalance + balance, withdrawTo.balance); - } - - // Withdraw success ERC20 - function testWithdrawERC20(bool useFactory, address withdrawTo, uint256 tokenId, uint256 amount) - public - withFactory(useFactory) - { - testERC20Mint(false, withdrawTo, tokenId, amount); - - uint256 tokenBalance = erc20.balanceOf(address(token)); - uint256 balance = erc20.balanceOf(withdrawTo); - token.withdraw(withdrawTo, tokenBalance); - assertEq(tokenBalance + balance, erc20.balanceOf(withdrawTo)); - } - - // - // Helpers - // - modifier withFactory(bool useFactory) { - if (useFactory) { - setUpFromFactory(); - } - _; - } - - modifier assumeSafe(address nonContract, uint256 tokenId, uint256 amount) { - vm.assume(uint160(nonContract) > 16); - vm.assume(nonContract.code.length == 0); - vm.assume(tokenId < 100); - vm.assume(amount > 0 && amount < 20); - _; - } - - // Create ERC20. Give this contract 1000 ERC20 tokens. Approve token to spend 100 ERC20 tokens. - modifier withERC20() { - erc20 = new ERC20Mock(); - erc20.mockMint(address(this), 1000 ether); - erc20.approve(address(token), 1000 ether); - _; - } - - modifier withGlobalSaleActive() { - token.setGlobalSaleDetails( - address(0), perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1) - ); - _; - } - - modifier withTokenSaleActive(uint256 tokenId) { - setTokenSaleActive(tokenId); - _; - } - - function setTokenSaleActive(uint256 tokenId) private { - token.setTokenSaleDetails(tokenId, perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); - } - - function singleToArray(uint256 value) private pure returns (uint256[] memory) { - uint256[] memory values = new uint256[](1); - values[0] = value; - return values; - } -} diff --git a/test/tokens/ERC1155/Packed/ERC1155PackedToken.t.sol b/test/tokens/ERC1155/Packed/ERC1155PackedToken.t.sol deleted file mode 100644 index 6a8e4c2..0000000 --- a/test/tokens/ERC1155/Packed/ERC1155PackedToken.t.sol +++ /dev/null @@ -1,312 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import "forge-std/Test.sol"; -import {ERC1155PackedToken, InvalidInitialization} from "src/tokens/ERC1155/Packed/ERC1155PackedToken.sol"; -import {ERC1155PackedTokenFactory} from "src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol"; - -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; - -// Interfaces -import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; -import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; -import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; - -contract ERC1155TokenTest is Test { - // Redeclare events - event TransferSingle( - address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _amount - ); - event TransferBatch( - address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts - ); - - ERC1155PackedToken private token; - - address owner; - - function setUp() public { - owner = makeAddr("owner"); - - vm.deal(address(this), 100 ether); - vm.deal(owner, 100 ether); - - ERC1155PackedTokenFactory factory = new ERC1155PackedTokenFactory(); - token = ERC1155PackedToken(factory.deploy(owner, "name", "baseURI", 0x0)); - } - - function testReinitializeFails() public { - vm.expectRevert(InvalidInitialization.selector); - token.initialize(owner, "name", "baseURI"); - } - - function testSupportsInterface() public { - assertTrue(token.supportsInterface(type(IERC165).interfaceId)); - assertTrue(token.supportsInterface(type(IERC1155).interfaceId)); - assertTrue(token.supportsInterface(type(IERC1155Metadata).interfaceId)); - } - - function testOwnerHasRoles() public { - assertTrue(token.hasRole(token.DEFAULT_ADMIN_ROLE(), owner)); - assertTrue(token.hasRole(token.METADATA_ADMIN_ROLE(), owner)); - assertTrue(token.hasRole(token.MINTER_ROLE(), owner)); - assertTrue(token.hasRole(token.ROYALTY_ADMIN_ROLE(), owner)); - } - - // - // Minting - // - function testMintInvalidRole(address caller) public { - vm.assume(caller != owner); - - vm.expectRevert( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(caller), - " is missing role ", - Strings.toHexString(uint256(token.MINTER_ROLE()), 32) - ) - ); - vm.prank(caller); - token.mint(caller, 1, 1, ""); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 1; - vm.expectRevert( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(caller), - " is missing role ", - Strings.toHexString(uint256(token.MINTER_ROLE()), 32) - ) - ); - vm.prank(caller); - token.batchMint(caller, tokenIds, amounts, ""); - } - - function testMintOwner(uint256 tokenId, uint256 amount) public { - vm.assume(amount > 0 && amount < 2 ** 16); - - vm.expectEmit(true, true, true, true, address(token)); - emit TransferSingle(owner, address(0), owner, tokenId, amount); - vm.prank(owner); - token.mint(owner, tokenId, amount, ""); - - assertEq(token.balanceOf(owner, tokenId), amount); - } - - function testBatchMintOwner(uint256[] memory tokenIds, uint256[] memory amounts) public { - tokenIds = boundArrayLength(tokenIds, 10); - amounts = boundArrayLength(amounts, 10); - vm.assume(tokenIds.length == amounts.length); - for (uint256 i; i < amounts.length; i++) { - amounts[i] = _bound(amounts[i], 1, 2 ** 16); - } - - vm.expectEmit(true, true, true, true, address(token)); - emit TransferBatch(owner, address(0), owner, tokenIds, amounts); - vm.prank(owner); - token.batchMint(owner, tokenIds, amounts, ""); - } - - function testMintWithRole(address minter, uint256 tokenId, uint256 amount) public { - vm.assume(minter != owner); - vm.assume(minter != address(0)); - vm.assume(amount > 0 && amount < 2 ** 16); - // Give role - vm.startPrank(owner); - token.grantRole(token.MINTER_ROLE(), minter); - vm.stopPrank(); - - vm.expectEmit(true, true, true, true, address(token)); - emit TransferSingle(minter, address(0), owner, tokenId, amount); - - vm.prank(minter); - token.mint(owner, tokenId, amount, ""); - - assertEq(token.balanceOf(owner, tokenId), amount); - } - - function testBatchMintWithRole(address minter, uint256[] memory tokenIds, uint256[] memory amounts) public { - vm.assume(minter != owner); - vm.assume(minter != address(0)); - tokenIds = boundArrayLength(tokenIds, 10); - amounts = boundArrayLength(amounts, 10); - vm.assume(tokenIds.length == amounts.length); - for (uint256 i; i < amounts.length; i++) { - amounts[i] = _bound(amounts[i], 1, 2 ** 16); - } - // Unique ids - for (uint256 i; i < tokenIds.length; i++) { - for (uint256 j; j < tokenIds.length; j++) { - if (i != j) { - vm.assume(tokenIds[i] != tokenIds[j]); - } - } - } - - // Give role - vm.startPrank(owner); - token.grantRole(token.MINTER_ROLE(), minter); - vm.stopPrank(); - - vm.expectEmit(true, true, true, true, address(token)); - emit TransferBatch(minter, address(0), owner, tokenIds, amounts); - vm.prank(minter); - token.batchMint(owner, tokenIds, amounts, ""); - } - - // - // Metadata - // - function testMetadataOwner() public { - vm.prank(owner); - token.setBaseMetadataURI("ipfs://newURI/"); - - assertEq(token.uri(0), "ipfs://newURI/0.json"); - assertEq(token.uri(1), "ipfs://newURI/1.json"); - } - - function testMetadataInvalid(address caller) public { - vm.assume(caller != owner); - vm.expectRevert( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(caller), - " is missing role ", - Strings.toHexString(uint256(token.METADATA_ADMIN_ROLE()), 32) - ) - ); - vm.prank(caller); - token.setBaseMetadataURI("ipfs://newURI/"); - } - - function testMetadataWithRole(address caller) public { - vm.assume(caller != owner); - vm.assume(caller != address(0)); - // Give role - vm.startPrank(owner); - token.grantRole(token.METADATA_ADMIN_ROLE(), caller); - vm.stopPrank(); - - vm.prank(caller); - token.setBaseMetadataURI("ipfs://newURI/"); - } - - // - // Royalty - // - function testDefaultRoyalty(address receiver, uint96 feeNumerator, uint256 salePrice) public { - vm.assume(feeNumerator <= 10000); - vm.assume(receiver != address(0)); - vm.assume(salePrice < type(uint128).max); // Buffer for overflow - - vm.prank(owner); - token.setDefaultRoyalty(receiver, feeNumerator); - - (address receiver_, uint256 amount) = token.royaltyInfo(1, salePrice); - assertEq(receiver_, receiver); - assertEq(amount, salePrice * feeNumerator / 10000); - } - - function testTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator, uint256 salePrice) public { - vm.assume(feeNumerator <= 10000); - vm.assume(receiver != address(0)); - vm.assume(tokenId != 69); // Other token id for default validation - vm.assume(salePrice < type(uint128).max); // Buffer for overflow - - vm.prank(owner); - token.setTokenRoyalty(tokenId, receiver, feeNumerator); - - (address receiver_, uint256 amount) = token.royaltyInfo(tokenId, salePrice); - assertEq(receiver_, receiver); - assertEq(amount, salePrice * feeNumerator / 10000); - - (receiver_, amount) = token.royaltyInfo(69, salePrice); - assertEq(receiver_, address(0)); - assertEq(amount, 0); - } - - function testRoyaltyWithRole( - address caller, - uint256 tokenId, - address receiver, - uint96 feeNumerator, - uint256 salePrice - ) - public - { - vm.assume(feeNumerator <= 10000); - vm.assume(receiver != address(0)); - vm.assume(caller != owner); - vm.assume(salePrice < type(uint128).max); // Buffer for overflow - - vm.startPrank(owner); - token.grantRole(token.ROYALTY_ADMIN_ROLE(), caller); - vm.stopPrank(); - - vm.prank(caller); - token.setDefaultRoyalty(receiver, feeNumerator); - - (address receiver_, uint256 amount) = token.royaltyInfo(1, salePrice); - assertEq(receiver_, receiver); - assertEq(amount, salePrice * feeNumerator / 10000); - - vm.prank(caller); - token.setTokenRoyalty(tokenId, receiver, feeNumerator); - - (receiver_, amount) = token.royaltyInfo(tokenId, salePrice); - assertEq(receiver_, receiver); - assertEq(amount, salePrice * feeNumerator / 10000); - } - - function testRoyaltyInvalidRole( - address caller, - uint256 tokenId, - address receiver, - uint96 feeNumerator, - uint256 salePrice - ) - public - { - vm.assume(feeNumerator <= 10000); - vm.assume(receiver != address(0)); - vm.assume(caller != owner); - vm.assume(salePrice < type(uint128).max); // Buffer for overflow - - vm.expectRevert( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(caller), - " is missing role ", - Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) - ) - ); - vm.prank(caller); - token.setDefaultRoyalty(receiver, feeNumerator); - - vm.expectRevert( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(caller), - " is missing role ", - Strings.toHexString(uint256(token.ROYALTY_ADMIN_ROLE()), 32) - ) - ); - vm.prank(caller); - token.setTokenRoyalty(tokenId, receiver, feeNumerator); - } - - function boundArrayLength(uint256[] memory arr, uint256 maxSize) private pure returns (uint256[] memory) { - if (arr.length <= maxSize) { - return arr; - } - uint256[] memory result = new uint256[](maxSize); - for (uint256 i; i < maxSize; i++) { - result[i] = arr[i]; - } - return result; - } -} From 1641f4f3dbb85ad3e1854f817e1dd2b9aad256d5 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 20 Jun 2023 08:49:57 +1200 Subject: [PATCH 28/49] Use ERC1155Token as a base --- src/tokens/ERC1155/ERC1155Sale.sol | 92 ++++++------------- src/tokens/ERC1155/ERC1155SaleErrors.sol | 5 + src/tokens/ERC1155/ERC1155SaleFactory.sol | 4 +- src/tokens/ERC1155/ERC1155Supply.sol | 52 +++++------ src/tokens/ERC1155/ERC1155SupplyErrors.sol | 5 +- src/tokens/ERC1155/ERC1155Token.sol | 36 +------- src/tokens/ERC1155/ERC1155TokenFactory.sol | 37 -------- src/tokens/ERC1155/ERC1155TokenMinter.sol | 83 +++++++++++++++++ .../ERC1155/ERC1155TokenMinterFactory.sol | 37 ++++++++ src/tokens/ERC1155/IERC1155TokenFactory.sol | 22 ----- .../ERC1155/IERC1155TokenMinterFactory.sol | 22 +++++ src/tokens/ERC721/ERC721SaleFactory.sol | 2 +- test/tokens/ERC1155/ERC1155Sale.t.sol | 12 +-- ...55Token.t.sol => ERC1155TokenMinter.t.sol} | 14 +-- 14 files changed, 220 insertions(+), 203 deletions(-) delete mode 100644 src/tokens/ERC1155/ERC1155TokenFactory.sol create mode 100644 src/tokens/ERC1155/ERC1155TokenMinter.sol create mode 100644 src/tokens/ERC1155/ERC1155TokenMinterFactory.sol delete mode 100644 src/tokens/ERC1155/IERC1155TokenFactory.sol create mode 100644 src/tokens/ERC1155/IERC1155TokenMinterFactory.sol rename test/tokens/ERC1155/{ERC1155Token.t.sol => ERC1155TokenMinter.t.sol} (95%) diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol index 5806703..d11805a 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -2,25 +2,16 @@ pragma solidity ^0.8.17; import {IERC1155Sale} from "./IERC1155Sale.sol"; -import {ERC1155, ERC1155Meta} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Meta.sol"; -import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -import {ERC1155Supply} from "./ERC1155Supply.sol"; +import {ERC1155Supply, ERC1155Token} from "./ERC1155Supply.sol"; import {ERC1155SaleErrors} from "./ERC1155SaleErrors.sol"; -import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; -import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract ERC1155Sale is IERC1155Sale, ERC1155SaleErrors, - ERC1155Supply, - ERC1155Meta, - ERC1155Metadata, - ERC2981, - AccessControl + ERC1155Supply { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); - bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); @@ -33,12 +24,6 @@ contract ERC1155Sale is SaleDetails private _globalSaleDetails; mapping(uint256 => SaleDetails) private _tokenSaleDetails; - /** - * Initialize with empty values. - * @dev These are overridden by initialize(). - */ - constructor() ERC1155Metadata("", "") {} - /** * Initialize the contract. * @param _owner Owner address. @@ -50,12 +35,13 @@ contract ERC1155Sale is if (_initialized) { revert InvalidInitialization(); } - _initialized = true; + ERC1155Token._initialize(_owner, _name, _baseURI); + name = _name; baseURI = _baseURI; _setupRole(DEFAULT_ADMIN_ROLE, _owner); _setupRole(MINT_ADMIN_ROLE, _owner); - _setupRole(ROYALTY_ADMIN_ROLE, _owner); + _initialized = true; } /** @@ -66,7 +52,7 @@ contract ERC1155Sale is */ function blockTimeOutOfBounds(uint256 startTime, uint256 endTime) private view returns (bool) { // 0 end time indicates inactive sale. - return endTime == 0 || block.timestamp < startTime || block.timestamp >= endTime; + return endTime == 0 || block.timestamp < startTime || block.timestamp >= endTime; // solhint-disable-line not-rely-on-time } /** @@ -75,27 +61,38 @@ contract ERC1155Sale is * @param _amounts Amounts of tokens to mint. */ function payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts) private { - uint256 totalAmount; + uint256 lastTokenId; uint256 totalCost; + uint256 totalAmount; + SaleDetails memory gSaleDetails = _globalSaleDetails; bool globalSaleInactive = blockTimeOutOfBounds(gSaleDetails.startTime, gSaleDetails.endTime); for (uint256 i; i < _tokenIds.length; i++) { + uint256 tokenId = _tokenIds[i]; + // Test tokenIds ordering + if (i != 0 && lastTokenId >= tokenId) { + revert InvalidTokenIds(); + } + lastTokenId = tokenId; + + uint256 amount = _amounts[i]; + // Active sale test - SaleDetails memory saleDetails = _tokenSaleDetails[_tokenIds[i]]; + SaleDetails memory saleDetails = _tokenSaleDetails[tokenId]; bool tokenSaleInactive = blockTimeOutOfBounds(saleDetails.startTime, saleDetails.endTime); if (tokenSaleInactive) { // Prefer token sale if (globalSaleInactive) { // Both sales inactive - revert SaleInactive(_tokenIds[i]); + revert SaleInactive(tokenId); } // Use global sale details - totalCost += gSaleDetails.cost * _amounts[i]; + totalCost += gSaleDetails.cost * amount; } else { // Use token sale price - totalCost += saleDetails.cost * _amounts[i]; + totalCost += saleDetails.cost * amount; } - totalAmount += _amounts[i]; + totalAmount += amount; } if (_paymentToken == address(0)) { @@ -125,6 +122,7 @@ contract ERC1155Sale is * @param _amounts Amounts of tokens to mint. * @param _data Data to pass if receiver is contract. * @notice Sale must be active for all tokens. + * @dev tokenIds must be sorted ascending without duplicates. */ function mint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) public @@ -169,8 +167,8 @@ contract ERC1155Sale is onlyRole(MINT_ADMIN_ROLE) { _paymentToken = _paymentTokenAddr; - totalSupplyCap = _supplyCap; _globalSaleDetails = SaleDetails(_cost, _startTime, _endTime); + totalSupplyCap = _supplyCap; emit GlobalSaleDetailsUpdated(_cost, _supplyCap, _startTime, _endTime); } @@ -193,38 +191,11 @@ contract ERC1155Sale is public onlyRole(MINT_ADMIN_ROLE) { - tokenSupplyCap[_tokenId] = _supplyCap; _tokenSaleDetails[_tokenId] = SaleDetails(_cost, _startTime, _endTime); + tokenSupplyCap[_tokenId] = _supplyCap; emit TokenSaleDetailsUpdated(_tokenId, _cost, _supplyCap, _startTime, _endTime); } - // - // Royalty - // - - /** - * Sets the royalty information that all ids in this contract will default to. - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - */ - function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) public onlyRole(ROYALTY_ADMIN_ROLE) { - _setDefaultRoyalty(_receiver, _feeNumerator); - } - - /** - * Sets the royalty information that a given token id in this contract will use. - * @param _tokenId The token id to set the royalty information for - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @notice This overrides the default royalty information for this token id - */ - function setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) - public - onlyRole(ROYALTY_ADMIN_ROLE) - { - _setTokenRoyalty(_tokenId, _receiver, _feeNumerator); - } - // // Withdraw // @@ -282,15 +253,4 @@ contract ERC1155Sale is function paymentToken() external view returns (address) { return _paymentToken; } - - function supportsInterface(bytes4 _interfaceId) - public - view - override (ERC1155, ERC1155Metadata, ERC2981, AccessControl) - returns (bool) - { - // FIXME Fix inheritance issues - return ERC1155.supportsInterface(_interfaceId) || ERC1155Metadata.supportsInterface(_interfaceId) - || super.supportsInterface(_interfaceId); - } } diff --git a/src/tokens/ERC1155/ERC1155SaleErrors.sol b/src/tokens/ERC1155/ERC1155SaleErrors.sol index 36bc1b1..6c3d82b 100644 --- a/src/tokens/ERC1155/ERC1155SaleErrors.sol +++ b/src/tokens/ERC1155/ERC1155SaleErrors.sol @@ -26,6 +26,11 @@ abstract contract ERC1155SaleErrors { */ error InsufficientPayment(uint256 expected, uint256 actual); + /** + * Invalid token IDs. + */ + error InvalidTokenIds(); + /** * Withdraw failed. */ diff --git a/src/tokens/ERC1155/ERC1155SaleFactory.sol b/src/tokens/ERC1155/ERC1155SaleFactory.sol index 971d6b6..f0a39b2 100644 --- a/src/tokens/ERC1155/ERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/ERC1155SaleFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.4; +pragma solidity ^0.8.17; import {ERC1155Sale} from "./ERC1155Sale.sol"; import {IERC1155SaleFactory} from "./IERC1155SaleFactory.sol"; @@ -28,7 +28,7 @@ contract ERC1155SaleFactory is IERC1155SaleFactory, ProxyDeployer { external returns (address proxyAddr) { - proxyAddr = deployProxy(_implAddr, _salt); + proxyAddr = _deployProxy(_implAddr, _salt); ERC1155Sale(proxyAddr).initialize(_owner, _name, _baseURI); emit ERC1155SaleDeployed(proxyAddr); return proxyAddr; diff --git a/src/tokens/ERC1155/ERC1155Supply.sol b/src/tokens/ERC1155/ERC1155Supply.sol index bbd092c..9e26748 100644 --- a/src/tokens/ERC1155/ERC1155Supply.sol +++ b/src/tokens/ERC1155/ERC1155Supply.sol @@ -1,17 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ERC1155} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155.sol"; +import {ERC1155Token} from "./ERC1155Token.sol"; import {ERC1155SupplyErrors} from "./ERC1155SupplyErrors.sol"; /** * An ERC1155 extension that tracks token supply. */ -contract ERC1155Supply is ERC1155, ERC1155SupplyErrors { - // Maximum supply globally and per token. 0 indicates unlimited supply - uint256 public totalSupplyCap; - mapping(uint256 => uint256) public tokenSupplyCap; +abstract contract ERC1155Supply is ERC1155Token, ERC1155SupplyErrors { + // Maximum supply globaly and per token. 0 indicates unlimited supply + uint256 internal totalSupplyCap; + mapping(uint256 => uint256) internal tokenSupplyCap; + // Current supply uint256 public totalSupply; mapping(uint256 => uint256) public tokenSupply; @@ -22,25 +23,18 @@ contract ERC1155Supply is ERC1155, ERC1155SupplyErrors { * @param _amount The amount to be minted * @param _data Data to pass if receiver is contract */ - function _mint(address _to, uint256 _id, uint256 _amount, bytes memory _data) internal { + function _mint(address _to, uint256 _id, uint256 _amount, bytes memory _data) internal virtual override { // Check supply cap + if (totalSupplyCap > 0 && totalSupply + _amount > totalSupplyCap) { + revert InsufficientSupply(totalSupply, _amount, totalSupplyCap); + } totalSupply += _amount; - if (totalSupplyCap > 0 && totalSupply > totalSupplyCap) { - revert InsufficientSupply(); + if (tokenSupplyCap[_id] > 0 && tokenSupply[_id] + _amount > tokenSupplyCap[_id]) { + revert InsufficientSupply(tokenSupply[_id], _amount, tokenSupplyCap[_id]); } tokenSupply[_id] += _amount; - if (tokenSupplyCap[_id] > 0 && tokenSupply[_id] > tokenSupplyCap[_id]) { - revert InsufficientSupply(); - } - - // Add _amount - balances[_to][_id] += _amount; - - // Emit event - emit TransferSingle(msg.sender, address(0x0), _to, _id, _amount); - // Calling onReceive method if recipient is contract - _callonERC1155Received(address(0x0), _to, _id, _amount, gasleft(), _data); + _mint(_to, _id, _amount, _data); } /** @@ -50,25 +44,27 @@ contract ERC1155Supply is ERC1155, ERC1155SupplyErrors { * @param _amounts Array of amount of tokens to mint per id * @param _data Data to pass if receiver is contract */ - function _batchMint(address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data) internal { + function _batchMint(address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data) internal virtual override { uint256 nMint = _ids.length; if (nMint != _amounts.length) { revert InvalidArrayLength(); } // Executing all minting + uint256 totalAmount = 0; for (uint256 i = 0; i < nMint; i++) { // Update storage balance + if (tokenSupplyCap[_ids[i]] > 0 && tokenSupply[_ids[i]] + _amounts[i] > tokenSupplyCap[_ids[i]]) { + revert InsufficientSupply(tokenSupply[_ids[i]], _amounts[i], tokenSupplyCap[_ids[i]]); + } balances[_to][_ids[i]] += _amounts[i]; - totalSupply += _amounts[i]; tokenSupply[_ids[i]] += _amounts[i]; - if (tokenSupplyCap[_ids[i]] > 0 && tokenSupply[_ids[i]] > tokenSupplyCap[_ids[i]]) { - revert InsufficientSupply(); - } + totalAmount += _amounts[i]; } - if (totalSupplyCap > 0 && totalSupply > totalSupplyCap) { - revert InsufficientSupply(); + if (totalSupplyCap > 0 && totalSupply + totalAmount > totalSupplyCap) { + revert InsufficientSupply(totalSupply, totalAmount, totalSupplyCap); } + totalSupply += totalAmount; // Emit batch mint event emit TransferBatch(msg.sender, address(0x0), _to, _ids, _amounts); @@ -83,7 +79,7 @@ contract ERC1155Supply is ERC1155, ERC1155SupplyErrors { * @param _id Token id to burn * @param _amount The amount to be burned */ - function _burn(address _from, uint256 _id, uint256 _amount) internal { + function _burn(address _from, uint256 _id, uint256 _amount) internal virtual override { // Supply totalSupply -= _amount; tokenSupply[_id] -= _amount; @@ -101,7 +97,7 @@ contract ERC1155Supply is ERC1155, ERC1155SupplyErrors { * @param _ids Array of token ids to burn * @param _amounts Array of the amount to be burned */ - function _batchBurn(address _from, uint256[] memory _ids, uint256[] memory _amounts) internal { + function _batchBurn(address _from, uint256[] memory _ids, uint256[] memory _amounts) internal virtual override { // Number of mints to execute uint256 nBurn = _ids.length; if (nBurn != _amounts.length) { diff --git a/src/tokens/ERC1155/ERC1155SupplyErrors.sol b/src/tokens/ERC1155/ERC1155SupplyErrors.sol index c439891..d33bcef 100644 --- a/src/tokens/ERC1155/ERC1155SupplyErrors.sol +++ b/src/tokens/ERC1155/ERC1155SupplyErrors.sol @@ -3,10 +3,11 @@ pragma solidity ^0.8.17; // A contract for errors for extensibility. abstract contract ERC1155SupplyErrors { + /** - * Insufficient supply. + * Insufficient supply of tokens. */ - error InsufficientSupply(); + error InsufficientSupply(uint256 currentSupply, uint256 requestedAmount, uint256 maxSupply); /** * Invalid array input length. diff --git a/src/tokens/ERC1155/ERC1155Token.sol b/src/tokens/ERC1155/ERC1155Token.sol index 943ae5f..16173d1 100644 --- a/src/tokens/ERC1155/ERC1155Token.sol +++ b/src/tokens/ERC1155/ERC1155Token.sol @@ -11,9 +11,8 @@ error InvalidInitialization(); /** * A ready made implementation of ERC-1155. */ -contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981Controlled { +abstract contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981Controlled { bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); address private immutable _initializer; bool private _initialized; @@ -31,49 +30,21 @@ contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981C * @param tokenName Token name. * @param tokenBaseURI Base URI for token metadata. * @dev This should be called immediately after deployment. + */ function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public { if (msg.sender != _initializer || _initialized) { revert InvalidInitialization(); } - _initialized = true; name = tokenName; baseURI = tokenBaseURI; _setupRole(DEFAULT_ADMIN_ROLE, owner); - _setupRole(MINTER_ROLE, owner); _setupRole(ROYALTY_ADMIN_ROLE, owner); _setupRole(METADATA_ADMIN_ROLE, owner); - } - // - // Minting - // - - /** - * Mint tokens. - * @param to Address to mint tokens to. - * @param tokenId Token ID to mint. - * @param amount Amount of tokens to mint. - * @param data Data to pass if receiver is contract. - */ - function mint(address to, uint256 tokenId, uint256 amount, bytes memory data) external onlyRole(MINTER_ROLE) { - _mint(to, tokenId, amount, data); - } - - /** - * Mint tokens. - * @param to Address to mint tokens to. - * @param tokenIds Token IDs to mint. - * @param amounts Amounts of tokens to mint. - * @param data Data to pass if receiver is contract. - */ - function batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data) - external - onlyRole(MINTER_ROLE) - { - _batchMint(to, tokenIds, amounts, data); + _initialized = true; } // @@ -108,6 +79,7 @@ contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981C function supportsInterface(bytes4 interfaceId) public view + virtual override (ERC1155, ERC1155Metadata, ERC2981Controlled) returns (bool) { diff --git a/src/tokens/ERC1155/ERC1155TokenFactory.sol b/src/tokens/ERC1155/ERC1155TokenFactory.sol deleted file mode 100644 index 564503d..0000000 --- a/src/tokens/ERC1155/ERC1155TokenFactory.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import {ERC1155Token} from "./ERC1155Token.sol"; -import {IERC1155TokenFactory} from "./IERC1155TokenFactory.sol"; -import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; - -contract ERC1155TokenFactory is IERC1155TokenFactory, ProxyDeployer { - address private immutable _implAddr; - - /** - * Creates an ERC-1155 Token Factory. - */ - constructor() { - ERC1155Token proxyImpl = new ERC1155Token(); - _implAddr = address(proxyImpl); - } - - /** - * Creates an ERC-1155 Token proxy. - * @param owner The owner of the ERC-1155 Token proxy - * @param name The name of the ERC-1155 Token proxy - * @param baseURI The base URI of the ERC-1155 Token proxy - * @param salt The deployment salt - * @return proxyAddr The address of the ERC-1155 Token Proxy - * @dev The provided `salt` is hashed with the caller address for security. - */ - function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) - external - returns (address proxyAddr) - { - proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); - ERC1155Token(proxyAddr).initialize(owner, name, baseURI); - emit ERC1155TokenDeployed(proxyAddr); - return proxyAddr; - } -} diff --git a/src/tokens/ERC1155/ERC1155TokenMinter.sol b/src/tokens/ERC1155/ERC1155TokenMinter.sol new file mode 100644 index 0000000..785dab1 --- /dev/null +++ b/src/tokens/ERC1155/ERC1155TokenMinter.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC1155MintBurn, ERC1155} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155MintBurn.sol"; +import {ERC1155Token} from "./ERC1155Token.sol"; +import {ERC2981Controlled} from "../common/ERC2981Controlled.sol"; + +error InvalidInitialization(); + +/** + * A ready made implementation of ERC-1155 capable of minting when role provided. + */ +contract ERC1155TokenMinter is ERC1155MintBurn, ERC1155Token { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + address private immutable initializer; + bool private initialized; + + constructor() { + initializer = msg.sender; + } + + /** + * Initialize the contract. + * @param owner Owner address. + * @param name_ Token name. + * @param baseURI_ Base URI for token metadata. + * @dev This should be called immediately after deployment. + */ + function initialize(address owner, string memory name_, string memory baseURI_) public virtual { + if (msg.sender != initializer || initialized) { + revert InvalidInitialization(); + } + + ERC1155Token._initialize(owner, name_, baseURI_); + + _setupRole(MINTER_ROLE, owner); + + initialized = true; + } + + // + // Minting + // + + /** + * Mint tokens. + * @param to Address to mint tokens to. + * @param tokenId Token ID to mint. + * @param amount Amount of tokens to mint. + * @param data Data to pass if receiver is contract. + */ + function mint(address to, uint256 tokenId, uint256 amount, bytes memory data) external onlyRole(MINTER_ROLE) { + _mint(to, tokenId, amount, data); + } + + /** + * Mint tokens. + * @param to Address to mint tokens to. + * @param tokenIds Token IDs to mint. + * @param amounts Amounts of tokens to mint. + * @param data Data to pass if receiver is contract. + */ + function batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data) + external + onlyRole(MINTER_ROLE) + { + _batchMint(to, tokenIds, amounts, data); + } + + // + // Views + // + + /** + * Check interface support. + * @param interfaceId Interface id + * @return True if supported + */ + function supportsInterface(bytes4 interfaceId) public view override (ERC1155Token, ERC1155) returns (bool) { + return ERC1155Token.supportsInterface(interfaceId); + } +} diff --git a/src/tokens/ERC1155/ERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/ERC1155TokenMinterFactory.sol new file mode 100644 index 0000000..82e2786 --- /dev/null +++ b/src/tokens/ERC1155/ERC1155TokenMinterFactory.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC1155TokenMinter} from "./ERC1155TokenMinter.sol"; +import {IERC1155TokenMinterFactory} from "./IERC1155TokenMinterFactory.sol"; +import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; + +contract ERC1155TokenMinterFactory is IERC1155TokenMinterFactory, ProxyDeployer { + address private immutable implAddr; + + /** + * Creates an ERC-1155 Token Minter Factory. + */ + constructor() { + ERC1155TokenMinter proxyImpl = new ERC1155TokenMinter(); + implAddr = address(proxyImpl); + } + + /** + * Creates an ERC-1155 Token Minter proxy. + * @param owner The owner of the ERC-1155 Token Minter proxy + * @param name The name of the ERC-1155 Token Minter proxy + * @param baseURI The base URI of the ERC-1155 Token Minter proxy + * @param salt The deployment salt + * @return proxyAddr The address of the ERC-1155 Token Minter Proxy + * @dev The provided `salt` is hashed with the caller address for security. + */ + function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) + external + returns (address proxyAddr) + { + proxyAddr = _deployProxy(implAddr, keccak256(abi.encode(msg.sender, salt))); + ERC1155TokenMinter(proxyAddr).initialize(owner, name, baseURI); + emit ERC1155TokenMinterDeployed(proxyAddr); + return proxyAddr; + } +} diff --git a/src/tokens/ERC1155/IERC1155TokenFactory.sol b/src/tokens/ERC1155/IERC1155TokenFactory.sol deleted file mode 100644 index a266742..0000000 --- a/src/tokens/ERC1155/IERC1155TokenFactory.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -interface IERC1155TokenFactory { - /** - * Event emitted when a new ERC-1155 Token proxy contract is deployed. - * @param proxyAddr The address of the deployed proxy. - */ - event ERC1155TokenDeployed(address proxyAddr); - - /** - * Creates an ERC-1155 Token proxy. - * @param owner The owner of the ERC-1155 Token proxy - * @param name The name of the ERC-1155 Token proxy - * @param baseURI The base URI of the ERC-1155 Token proxy - * @param salt The deployment salt - * @return proxyAddr The address of the ERC-1155 Token Proxy - */ - function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) - external - returns (address proxyAddr); -} diff --git a/src/tokens/ERC1155/IERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/IERC1155TokenMinterFactory.sol new file mode 100644 index 0000000..50af26d --- /dev/null +++ b/src/tokens/ERC1155/IERC1155TokenMinterFactory.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC1155TokenMinterFactory { + /** + * Event emitted when a new ERC-1155 Token Minter proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC1155TokenMinterDeployed(address proxyAddr); + + /** + * Creates an ERC-1155 Token Minter proxy. + * @param owner The owner of the ERC-1155 Token Minter proxy + * @param name The name of the ERC-1155 Token Minter proxy + * @param baseURI The base URI of the ERC-1155 Token Minter proxy + * @param salt The deployment salt + * @return proxyAddr The address of the ERC-1155 Token Minter Proxy + */ + function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) + external + returns (address proxyAddr); +} diff --git a/src/tokens/ERC721/ERC721SaleFactory.sol b/src/tokens/ERC721/ERC721SaleFactory.sol index 9eb49af..3410d6b 100644 --- a/src/tokens/ERC721/ERC721SaleFactory.sol +++ b/src/tokens/ERC721/ERC721SaleFactory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.4; +pragma solidity ^0.8.17; import {ERC721Sale} from "./ERC721Sale.sol"; import {IERC721SaleFactory} from "./IERC721SaleFactory.sol"; diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index 84f3287..6b7920b 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -154,16 +154,16 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { assumeSafe(mintTo, tokenId, amount) withFactory(useFactory) { - vm.assume(supplyCap > 0); + vm.assume(supplyCap > 1); vm.assume(amount > supplyCap); token.setGlobalSaleDetails( - address(0), perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) + address(0), perTokenCost, supplyCap - 1, uint64(block.timestamp), uint64(block.timestamp + 1) ); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); - vm.expectRevert(InsufficientSupply.selector); + vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap - 1)); token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); } @@ -179,16 +179,16 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { assumeSafe(mintTo, tokenId, amount) withFactory(useFactory) { - vm.assume(supplyCap > 0); + vm.assume(supplyCap > 1); vm.assume(amount > supplyCap); token.setTokenSaleDetails( - tokenId, perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) + tokenId, perTokenCost, supplyCap - 1, uint64(block.timestamp), uint64(block.timestamp + 1) ); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); - vm.expectRevert(InsufficientSupply.selector); + vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap - 1)); token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); } diff --git a/test/tokens/ERC1155/ERC1155Token.t.sol b/test/tokens/ERC1155/ERC1155TokenMinter.t.sol similarity index 95% rename from test/tokens/ERC1155/ERC1155Token.t.sol rename to test/tokens/ERC1155/ERC1155TokenMinter.t.sol index 9b6b84a..7938631 100644 --- a/test/tokens/ERC1155/ERC1155Token.t.sol +++ b/test/tokens/ERC1155/ERC1155TokenMinter.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; -import {ERC1155Token, InvalidInitialization} from "src/tokens/ERC1155/ERC1155Token.sol"; -import {ERC1155TokenFactory} from "src/tokens/ERC1155/ERC1155TokenFactory.sol"; +import {ERC1155TokenMinter, InvalidInitialization} from "src/tokens/ERC1155/ERC1155TokenMinter.sol"; +import {ERC1155TokenMinterFactory} from "src/tokens/ERC1155/ERC1155TokenMinterFactory.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -12,7 +12,7 @@ import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -contract ERC1155TokenTest is Test { +contract ERC1155TokenMinterTest is Test { // Redeclare events event TransferSingle( address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _amount @@ -21,9 +21,9 @@ contract ERC1155TokenTest is Test { address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts ); - ERC1155Token private token; + ERC1155TokenMinter private token; - address owner; + address private owner; function setUp() public { owner = makeAddr("owner"); @@ -31,8 +31,8 @@ contract ERC1155TokenTest is Test { vm.deal(address(this), 100 ether); vm.deal(owner, 100 ether); - ERC1155TokenFactory factory = new ERC1155TokenFactory(); - token = ERC1155Token(factory.deploy(owner, "name", "baseURI", 0x0)); + ERC1155TokenMinterFactory factory = new ERC1155TokenMinterFactory(); + token = ERC1155TokenMinter(factory.deploy(owner, "name", "baseURI", 0x0)); } function testReinitializeFails() public { From 9fb52830cabd0051b5830bdb2814dcf2e4ed22fc Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 20 Jun 2023 11:56:29 +1200 Subject: [PATCH 29/49] Use ERC721Token as a base --- src/tokens/ERC721/ERC721Sale.sol | 153 ++++++------------ src/tokens/ERC721/ERC721SaleFactory.sol | 24 +-- src/tokens/ERC721/ERC721Token.sol | 21 +-- src/tokens/ERC721/ERC721TokenFactory.sol | 38 ----- src/tokens/ERC721/ERC721TokenMinter.sol | 60 +++++++ .../ERC721/ERC721TokenMinterFactory.sol | 38 +++++ src/tokens/ERC721/IERC721SaleFactory.sol | 19 +++ src/tokens/ERC721/IERC721TokenFactory.sol | 23 --- .../ERC721/IERC721TokenMinterFactory.sol | 23 +++ ...721Token.t.sol => ERC721TokenMinter.t.sol} | 12 +- 10 files changed, 215 insertions(+), 196 deletions(-) delete mode 100644 src/tokens/ERC721/ERC721TokenFactory.sol create mode 100644 src/tokens/ERC721/ERC721TokenMinter.sol create mode 100644 src/tokens/ERC721/ERC721TokenMinterFactory.sol delete mode 100644 src/tokens/ERC721/IERC721TokenFactory.sol create mode 100644 src/tokens/ERC721/IERC721TokenMinterFactory.sol rename test/tokens/ERC721/{ERC721Token.t.sol => ERC721TokenMinter.t.sol} (94%) diff --git a/src/tokens/ERC721/ERC721Sale.sol b/src/tokens/ERC721/ERC721Sale.sol index f900266..88da71b 100644 --- a/src/tokens/ERC721/ERC721Sale.sol +++ b/src/tokens/ERC721/ERC721Sale.sol @@ -1,55 +1,45 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import { - ERC721AQueryable, IERC721AQueryable, ERC721A, IERC721A -} from "erc721a/contracts/extensions/ERC721AQueryable.sol"; import {IERC721Sale} from "./IERC721Sale.sol"; +import {ERC721Token} from "./ERC721Token.sol"; import {ERC721SaleErrors} from "./ERC721SaleErrors.sol"; import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -contract ERC721Sale is IERC721Sale, ERC721AQueryable, ERC2981, AccessControl, ERC721SaleErrors { +contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); - bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); - bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = + bytes4 private constant _ERC20_TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); bool private _initialized; - string private _name; - string private _symbol; - string private baseURI; SaleDetails private _saleDetails; - /** - * Initialize with empty values. - * @dev These are overridden by initialize(). - */ - constructor() ERC721A("", "") {} - /** * Initialize the contract. - * @param _owner Owner address. - * @param name_ Token name. - * @param symbol_ Token symbol. - * @param baseURI_ Base URI for token metadata. + * @param owner The owner of the contract + * @param tokenName Name of the token + * @param tokenSymbol Symbol of the token + * @param tokenBaseURI Base URI of the token * @dev This should be called immediately after deployment. */ - function initialize(address _owner, string memory name_, string memory symbol_, string memory baseURI_) public { + function initialize(address owner, string memory tokenName, string memory tokenSymbol, string memory tokenBaseURI) + public + virtual + override + { if (_initialized) { revert InvalidInitialization(); } + + ERC721Token.initialize(owner, tokenName, tokenSymbol, tokenBaseURI); + _setupRole(MINT_ADMIN_ROLE, owner); + _initialized = true; - _name = name_; - _symbol = symbol_; - baseURI = baseURI_; - _setupRole(DEFAULT_ADMIN_ROLE, _owner); - _setupRole(MINT_ADMIN_ROLE, _owner); - _setupRole(ROYALTY_ADMIN_ROLE, _owner); } /** @@ -60,14 +50,14 @@ contract ERC721Sale is IERC721Sale, ERC721AQueryable, ERC2981, AccessControl, ER */ function blockTimeOutOfBounds(uint256 startTime, uint256 endTime) private view returns (bool) { // 0 end time indicates inactive sale. - return endTime == 0 || block.timestamp < startTime || block.timestamp >= endTime; + return endTime == 0 || block.timestamp < startTime || block.timestamp >= endTime; // solhint-disable-line not-rely-on-time } /** * Checks the sale is active and takes payment. * @param _amount Amount of tokens to mint. */ - function payForActiveMint(uint256 _amount) private { + function _payForActiveMint(uint256 _amount) private { // Active sale test if (blockTimeOutOfBounds(_saleDetails.startTime, _saleDetails.endTime)) { revert SaleInactive(); @@ -83,7 +73,7 @@ contract ERC721Sale is IERC721Sale, ERC721AQueryable, ERC2981, AccessControl, ER } else { // Paid in ERC20 (bool success, bytes memory data) = - paymentToken.call(abi.encodeWithSelector(ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), total)); + paymentToken.call(abi.encodeWithSelector(_ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), total)); if (!success || (data.length > 0 && !abi.decode(data, (bool)))) { revert InsufficientPayment(total, 0); } @@ -96,64 +86,51 @@ contract ERC721Sale is IERC721Sale, ERC721AQueryable, ERC2981, AccessControl, ER /** * Mint tokens. - * @param _to Address to mint tokens to. - * @param _amount Amount of tokens to mint. + * @param to Address to mint tokens to. + * @param amount Amount of tokens to mint. * @notice Sale must be active for all tokens. */ - function mint(address _to, uint256 _amount) public payable { - uint256 currentSupply = ERC721A.totalSupply(); + function mint(address to, uint256 amount) public payable { + uint256 currentSupply = totalSupply(); uint256 supplyCap = _saleDetails.supplyCap; - if (supplyCap > 0 && currentSupply + _amount > supplyCap) { - revert InsufficientSupply(currentSupply, _amount, supplyCap); + if (supplyCap > 0 && currentSupply + amount > supplyCap) { + revert InsufficientSupply(currentSupply, amount, supplyCap); } - payForActiveMint(_amount); - _mint(_to, _amount); + _payForActiveMint(amount); + _mint(to, amount); } /** * Mint tokens as admin. - * @param _to Address to mint tokens to. - * @param _amount Amount of tokens to mint. + * @param to Address to mint tokens to. + * @param amount Amount of tokens to mint. * @notice Only callable by mint admin. */ - function mintAdmin(address _to, uint256 _amount) public onlyRole(MINT_ADMIN_ROLE) { - _mint(_to, _amount); + function mintAdmin(address to, uint256 amount) public onlyRole(MINT_ADMIN_ROLE) { + _mint(to, amount); } /** * Set the sale details. - * @param _supplyCap The maximum number of tokens that can be minted. 0 indicates unlimited supply. - * @param _cost The amount of payment tokens to accept for each token minted. - * @param _paymentToken The ERC20 token address to accept payment in. address(0) indicates ETH. - * @param _startTime The start time of the sale. Tokens cannot be minted before this time. - * @param _endTime The end time of the sale. Tokens cannot be minted after this time. + * @param supplyCap The maximum number of tokens that can be minted. 0 indicates unlimited supply. + * @param cost The amount of payment tokens to accept for each token minted. + * @param paymentToken The ERC20 token address to accept payment in. address(0) indicates ETH. + * @param startTime The start time of the sale. Tokens cannot be minted before this time. + * @param endTime The end time of the sale. Tokens cannot be minted after this time. * @dev A zero end time indicates an inactive sale. */ function setSaleDetails( - uint256 _supplyCap, - uint256 _cost, - address _paymentToken, - uint64 _startTime, - uint64 _endTime + uint256 supplyCap, + uint256 cost, + address paymentToken, + uint64 startTime, + uint64 endTime ) public onlyRole(MINT_ADMIN_ROLE) { - _saleDetails = SaleDetails(_supplyCap, _cost, _paymentToken, _startTime, _endTime); - emit SaleDetailsUpdated(_supplyCap, _cost, _paymentToken, _startTime, _endTime); - } - - // - // Royalty - // - - /** - * Sets the royalty information that all ids in this contract will default to. - * @param _receiver Address of who should be sent the royalty payment - * @param _feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - */ - function setDefaultRoyalty(address _receiver, uint96 _feeNumerator) public onlyRole(ROYALTY_ADMIN_ROLE) { - _setDefaultRoyalty(_receiver, _feeNumerator); + _saleDetails = SaleDetails(supplyCap, cost, paymentToken, startTime, endTime); + emit SaleDetailsUpdated(supplyCap, cost, paymentToken, startTime, endTime); } // @@ -162,20 +139,20 @@ contract ERC721Sale is IERC721Sale, ERC721AQueryable, ERC2981, AccessControl, ER /** * Withdraws ETH or ERC20 tokens owned by this sale contract. - * @param _to Address to withdraw to. - * @param _amount Amount to withdraw. + * @param to Address to withdraw to. + * @param amount Amount to withdraw. * @dev Withdraws ERC20 when paymentToken is set, else ETH. * @notice Only callable by the contract admin. */ - function withdraw(address _to, uint256 _amount) public onlyRole(DEFAULT_ADMIN_ROLE) { + function withdraw(address to, uint256 amount) public onlyRole(DEFAULT_ADMIN_ROLE) { address paymentToken = _saleDetails.paymentToken; if (paymentToken == address(0)) { - (bool success,) = _to.call{value: _amount}(""); + (bool success,) = to.call{value: amount}(""); if (!success) { revert WithdrawFailed(); } } else { - (bool success) = IERC20(paymentToken).transfer(_to, _amount); + (bool success) = IERC20(paymentToken).transfer(to, amount); if (!success) { revert WithdrawFailed(); } @@ -185,6 +162,7 @@ contract ERC721Sale is IERC721Sale, ERC721AQueryable, ERC2981, AccessControl, ER // // Views // + /** * Get sale details. * @return Sale details. @@ -193,38 +171,13 @@ contract ERC721Sale is IERC721Sale, ERC721AQueryable, ERC2981, AccessControl, ER return _saleDetails; } - function supportsInterface(bytes4 _interfaceId) + function supportsInterface(bytes4 interfaceId) public view - override (ERC721A, IERC721A, ERC2981, AccessControl) + virtual + override returns (bool) { - return _interfaceId == type(IERC721A).interfaceId || _interfaceId == type(IERC721AQueryable).interfaceId - || ERC721A.supportsInterface(_interfaceId) || super.supportsInterface(_interfaceId); - } - - // - // ERC721A Overrides - // - - /** - * Override the ERC721A baseURI function. - */ - function _baseURI() internal view override returns (string memory) { - return baseURI; - } - - /** - * @dev Returns the token collection name. - */ - function name() public view virtual override (ERC721A, IERC721A) returns (string memory) { - return _name; - } - - /** - * @dev Returns the token collection symbol. - */ - function symbol() public view virtual override (ERC721A, IERC721A) returns (string memory) { - return _symbol; + return interfaceId == type(IERC721Sale).interfaceId || super.supportsInterface(interfaceId); } } diff --git a/src/tokens/ERC721/ERC721SaleFactory.sol b/src/tokens/ERC721/ERC721SaleFactory.sol index 3410d6b..6f6ce9e 100644 --- a/src/tokens/ERC721/ERC721SaleFactory.sol +++ b/src/tokens/ERC721/ERC721SaleFactory.sol @@ -18,25 +18,25 @@ contract ERC721SaleFactory is IERC721SaleFactory, ProxyDeployer { /** * Creates an ERC-721 Floor Wrapper for given token contract - * @param _owner The owner of the ERC-721 Sale - * @param _name The name of the ERC-721 Sale token - * @param _symbol The symbol of the ERC-721 Sale token - * @param _baseURI The base URI of the ERC-721 Sale token - * @param _salt The deployment salt + * @param owner The owner of the ERC-721 Sale + * @param name The name of the ERC-721 Sale token + * @param symbol The symbol of the ERC-721 Sale token + * @param baseURI The base URI of the ERC-721 Sale token + * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Sale Proxy */ function deployERC721Sale( - address _owner, - string memory _name, - string memory _symbol, - string memory _baseURI, - bytes32 _salt + address owner, + string memory name, + string memory symbol, + string memory baseURI, + bytes32 salt ) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, _salt); - ERC721Sale(proxyAddr).initialize(_owner, _name, _symbol, _baseURI); + proxyAddr = _deployProxy(_implAddr, salt); + ERC721Sale(proxyAddr).initialize(owner, name, symbol, baseURI); emit ERC721SaleDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC721/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol index d1618f0..4304bd4 100644 --- a/src/tokens/ERC721/ERC721Token.sol +++ b/src/tokens/ERC721/ERC721Token.sol @@ -11,9 +11,8 @@ error InvalidInitialization(); /** * A ready made implementation of ERC-721. */ -contract ERC721Token is ERC721AQueryable, ERC2981Controlled { +abstract contract ERC721Token is ERC721AQueryable, ERC2981Controlled { bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); string private _tokenBaseURI; string private _tokenName; @@ -37,11 +36,10 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { * @param tokenBaseURI Base URI of the token * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory tokenName, string memory tokenSymbol, string memory tokenBaseURI) external { + function initialize(address owner, string memory tokenName, string memory tokenSymbol, string memory tokenBaseURI) public virtual { if (msg.sender != _initializer || _initialized) { revert InvalidInitialization(); } - _initialized = true; _tokenName = tokenName; _tokenSymbol = tokenSymbol; @@ -49,21 +47,9 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { _setupRole(DEFAULT_ADMIN_ROLE, owner); _setupRole(METADATA_ADMIN_ROLE, owner); - _setupRole(MINTER_ROLE, owner); _setupRole(ROYALTY_ADMIN_ROLE, owner); - } - - // - // Minting - // - /** - * Mint tokens. - * @param to Address to mint tokens to. - * @param amount Amount of tokens to mint. - */ - function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { - _mint(to, amount); + _initialized = true; } // @@ -90,6 +76,7 @@ contract ERC721Token is ERC721AQueryable, ERC2981Controlled { function supportsInterface(bytes4 interfaceId) public view + virtual override (ERC721A, IERC721A, ERC2981Controlled) returns (bool) { diff --git a/src/tokens/ERC721/ERC721TokenFactory.sol b/src/tokens/ERC721/ERC721TokenFactory.sol deleted file mode 100644 index 1f44491..0000000 --- a/src/tokens/ERC721/ERC721TokenFactory.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import {ERC721Token} from "./ERC721Token.sol"; -import {IERC721TokenFactory} from "./IERC721TokenFactory.sol"; -import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; - -contract ERC721TokenFactory is IERC721TokenFactory, ProxyDeployer { - address private immutable _implAddr; - - /** - * Creates an ERC-721 Token Factory. - */ - constructor() { - ERC721Token proxyImpl = new ERC721Token(); - _implAddr = address(proxyImpl); - } - - /** - * Creates an ERC-721 Token proxy. - * @param owner The owner of the ERC-721 Token proxy - * @param name The name of the ERC-721 Token proxy - * @param symbol The symbol of the ERC-721 Token proxy - * @param baseURI The base URI of the ERC-721 Token proxy - * @param salt The deployment salt - * @return proxyAddr The address of the ERC-721 Token Proxy - * @dev The provided `salt` is hashed with the caller address for security. - */ - function deploy(address owner, string memory name, string memory symbol, string memory baseURI, bytes32 salt) - external - returns (address proxyAddr) - { - proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); - ERC721Token(proxyAddr).initialize(owner, name, symbol, baseURI); - emit ERC721TokenDeployed(proxyAddr); - return proxyAddr; - } -} diff --git a/src/tokens/ERC721/ERC721TokenMinter.sol b/src/tokens/ERC721/ERC721TokenMinter.sol new file mode 100644 index 0000000..b114f1d --- /dev/null +++ b/src/tokens/ERC721/ERC721TokenMinter.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC721Token} from "./ERC721Token.sol"; + +error InvalidInitialization(); + +/** + * A ready made implementation of ERC-721 with role based minting. + */ +contract ERC721TokenMinter is ERC721Token { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + address private immutable _initializer; + bool private _initialized; + + /** + * Deploy contract. + */ + constructor() ERC721Token() { + _initializer = msg.sender; + } + + /** + * Initialize contract. + * @param owner The owner of the contract + * @param tokenName Name of the token + * @param tokenSymbol Symbol of the token + * @param tokenBaseURI Base URI of the token + * @dev This should be called immediately after deployment. + */ + function initialize(address owner, string memory tokenName, string memory tokenSymbol, string memory tokenBaseURI) + public + virtual + override + { + if (msg.sender != _initializer || _initialized) { + revert InvalidInitialization(); + } + + ERC721Token.initialize(owner, tokenName, tokenSymbol, tokenBaseURI); + + _setupRole(MINTER_ROLE, owner); + + _initialized = true; + } + + // + // Minting + // + + /** + * Mint tokens. + * @param to Address to mint tokens to. + * @param amount Amount of tokens to mint. + */ + function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { + _mint(to, amount); + } +} diff --git a/src/tokens/ERC721/ERC721TokenMinterFactory.sol b/src/tokens/ERC721/ERC721TokenMinterFactory.sol new file mode 100644 index 0000000..56fc328 --- /dev/null +++ b/src/tokens/ERC721/ERC721TokenMinterFactory.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC721TokenMinter} from "./ERC721TokenMinter.sol"; +import {IERC721TokenMinterFactory} from "./IERC721TokenMinterFactory.sol"; +import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; + +contract ERC721TokenMinterFactory is IERC721TokenMinterFactory, ProxyDeployer { + address private immutable _implAddr; + + /** + * Creates an ERC-721 Token Minter Factory. + */ + constructor() { + ERC721TokenMinter proxyImpl = new ERC721TokenMinter(); + _implAddr = address(proxyImpl); + } + + /** + * Creates an ERC-721 Token Minter proxy. + * @param owner The owner of the ERC-721 Token Minter proxy + * @param name The name of the ERC-721 Token Minter proxy + * @param symbol The symbol of the ERC-721 Token Minter proxy + * @param baseURI The base URI of the ERC-721 Token Minter proxy + * @param salt The deployment salt + * @return proxyAddr The address of the ERC-721 Token Minter Proxy + * @dev The provided `salt` is hashed with the caller address for security. + */ + function deploy(address owner, string memory name, string memory symbol, string memory baseURI, bytes32 salt) + external + returns (address proxyAddr) + { + proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); + ERC721TokenMinter(proxyAddr).initialize(owner, name, symbol, baseURI); + emit ERC721TokenMinterDeployed(proxyAddr); + return proxyAddr; + } +} diff --git a/src/tokens/ERC721/IERC721SaleFactory.sol b/src/tokens/ERC721/IERC721SaleFactory.sol index c31cf0a..7c1f7cd 100644 --- a/src/tokens/ERC721/IERC721SaleFactory.sol +++ b/src/tokens/ERC721/IERC721SaleFactory.sol @@ -7,4 +7,23 @@ interface IERC721SaleFactory { * @param proxyAddr The address of the deployed proxy. */ event ERC721SaleDeployed(address proxyAddr); + + /** + * Creates an ERC-721 Floor Wrapper for given token contract + * @param owner The owner of the ERC-721 Sale + * @param name The name of the ERC-721 Sale token + * @param symbol The symbol of the ERC-721 Sale token + * @param baseURI The base URI of the ERC-721 Sale token + * @param salt The deployment salt + * @return proxyAddr The address of the ERC-721 Sale Proxy + */ + function deployERC721Sale( + address owner, + string memory name, + string memory symbol, + string memory baseURI, + bytes32 salt + ) + external + returns (address proxyAddr); } diff --git a/src/tokens/ERC721/IERC721TokenFactory.sol b/src/tokens/ERC721/IERC721TokenFactory.sol deleted file mode 100644 index 3c44107..0000000 --- a/src/tokens/ERC721/IERC721TokenFactory.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -interface IERC721TokenFactory { - /** - * Event emitted when a new ERC-721 Token proxy contract is deployed. - * @param proxyAddr The address of the deployed proxy. - */ - event ERC721TokenDeployed(address proxyAddr); - - /** - * Creates an ERC-721 Token proxy. - * @param owner The owner of the ERC-721 Token proxy - * @param name The name of the ERC-721 Token proxy - * @param symbol The symbol of the ERC-721 Token proxy - * @param baseURI The base URI of the ERC-721 Token proxy - * @param salt The deployment salt - * @return proxyAddr The address of the ERC-721 Token Proxy - */ - function deploy(address owner, string memory name, string memory symbol, string memory baseURI, bytes32 salt) - external - returns (address proxyAddr); -} diff --git a/src/tokens/ERC721/IERC721TokenMinterFactory.sol b/src/tokens/ERC721/IERC721TokenMinterFactory.sol new file mode 100644 index 0000000..ad7bce9 --- /dev/null +++ b/src/tokens/ERC721/IERC721TokenMinterFactory.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC721TokenMinterFactory { + /** + * Event emitted when a new ERC-721 Token Minter proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC721TokenMinterDeployed(address proxyAddr); + + /** + * Creates an ERC-721 Token Minter proxy. + * @param owner The owner of the ERC-721 Token Minter proxy + * @param name The name of the ERC-721 Token Minter proxy + * @param symbol The symbol of the ERC-721 Token Minter proxy + * @param baseURI The base URI of the ERC-721 Token Minter proxy + * @param salt The deployment salt + * @return proxyAddr The address of the ERC-721 Token Minter Proxy + */ + function deploy(address owner, string memory name, string memory symbol, string memory baseURI, bytes32 salt) + external + returns (address proxyAddr); +} diff --git a/test/tokens/ERC721/ERC721Token.t.sol b/test/tokens/ERC721/ERC721TokenMinter.t.sol similarity index 94% rename from test/tokens/ERC721/ERC721Token.t.sol rename to test/tokens/ERC721/ERC721TokenMinter.t.sol index a534dbe..ef13d30 100644 --- a/test/tokens/ERC721/ERC721Token.t.sol +++ b/test/tokens/ERC721/ERC721TokenMinter.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; -import {ERC721Token, InvalidInitialization} from "src/tokens/ERC721/ERC721Token.sol"; -import {ERC721TokenFactory} from "src/tokens/ERC721/ERC721TokenFactory.sol"; +import {ERC721TokenMinter, InvalidInitialization} from "src/tokens/ERC721/ERC721TokenMinter.sol"; +import {ERC721TokenMinterFactory} from "src/tokens/ERC721/ERC721TokenMinterFactory.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -12,11 +12,11 @@ import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; import {IERC721A} from "erc721a/contracts/interfaces/IERC721A.sol"; import {IERC721AQueryable} from "erc721a/contracts/extensions/IERC721AQueryable.sol"; -contract ERC721TokenTest is Test { +contract ERC721TokenMinterTest is Test { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - ERC721Token private token; + ERC721TokenMinter private token; address owner; @@ -26,8 +26,8 @@ contract ERC721TokenTest is Test { vm.deal(address(this), 100 ether); vm.deal(owner, 100 ether); - ERC721TokenFactory factory = new ERC721TokenFactory(); - token = ERC721Token(factory.deploy(owner, "name", "symbol", "baseURI", 0x0)); + ERC721TokenMinterFactory factory = new ERC721TokenMinterFactory(); + token = ERC721TokenMinter(factory.deploy(owner, "name", "symbol", "baseURI", 0x0)); } function testReinitializeFails() public { From 969856cd2d45eb1583629e99865e558a78563188 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Thu, 22 Jun 2023 10:52:35 +1200 Subject: [PATCH 30/49] Variable naming --- src/proxies/ERC1967/Proxy.sol | 6 +- src/tokens/ERC1155/ERC1155Sale.sol | 129 +++++++++++---------- src/tokens/ERC1155/ERC1155Token.sol | 2 +- src/tokens/ERC1155/ERC1155TokenMinter.sol | 8 +- src/tokens/ERC1155/IERC1155SaleFactory.sol | 12 ++ src/tokens/ERC721/ERC721Sale.sol | 8 +- test/tokens/ERC1155/ERC1155Sale.t.sol | 1 + 7 files changed, 90 insertions(+), 76 deletions(-) diff --git a/src/proxies/ERC1967/Proxy.sol b/src/proxies/ERC1967/Proxy.sol index 0bd22b2..2de9168 100644 --- a/src/proxies/ERC1967/Proxy.sol +++ b/src/proxies/ERC1967/Proxy.sol @@ -5,7 +5,7 @@ import {IERC1967} from "./IERC1967.sol"; import {StorageSlot} from "../../utils/StorageSlot.sol"; contract Proxy is IERC1967 { - bytes32 internal constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); + bytes32 internal constant _IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); /** * Initializes the contract, setting proxy implementation address. @@ -51,13 +51,13 @@ contract Proxy is IERC1967 { * @param _implementation The address of the implementation contract. */ function _setImplementation(address _implementation) internal { - StorageSlot._getAddressSlot(IMPLEMENTATION_SLOT).value = _implementation; + StorageSlot._getAddressSlot(_IMPLEMENTATION_SLOT).value = _implementation; } /** * Returns the address of the current implementation. */ function _getImplementation() internal view returns (address) { - return StorageSlot._getAddressSlot(IMPLEMENTATION_SLOT).value; + return StorageSlot._getAddressSlot(_IMPLEMENTATION_SLOT).value; } } diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol index d11805a..4ed3f8e 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -13,7 +13,7 @@ contract ERC1155Sale is { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); - bytes4 private constant ERC20_TRANSFERFROM_SELECTOR = + bytes4 private constant _ERC20_TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); bool private _initialized; @@ -26,33 +26,34 @@ contract ERC1155Sale is /** * Initialize the contract. - * @param _owner Owner address. - * @param _name Token name. - * @param _baseURI Base URI for token metadata. + * @param owner Owner address. + * @param tokenName Token name. + * @param tokenBaseURI Base URI for token metadata. * @dev This should be called immediately after deployment. */ - function initialize(address _owner, string memory _name, string memory _baseURI) public { + function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public virtual override { if (_initialized) { revert InvalidInitialization(); } - ERC1155Token._initialize(_owner, _name, _baseURI); + ERC1155Token.initialize(owner, tokenName, tokenBaseURI); + + name = tokenName; + baseURI = tokenBaseURI; + _setupRole(DEFAULT_ADMIN_ROLE, owner); + _setupRole(MINT_ADMIN_ROLE, owner); - name = _name; - baseURI = _baseURI; - _setupRole(DEFAULT_ADMIN_ROLE, _owner); - _setupRole(MINT_ADMIN_ROLE, _owner); _initialized = true; } /** * Checks if the current block.timestamp is out of the give timestamp range. - * @param startTime Earliest acceptable timestamp (inclusive). - * @param endTime Latest acceptable timestamp (exclusive). + * @param _startTime Earliest acceptable timestamp (inclusive). + * @param _endTime Latest acceptable timestamp (exclusive). * @dev A zero endTime value is always considered out of bounds. */ - function blockTimeOutOfBounds(uint256 startTime, uint256 endTime) private view returns (bool) { + function blockTimeOutOfBounds(uint256 _startTime, uint256 _endTime) private view returns (bool) { // 0 end time indicates inactive sale. - return endTime == 0 || block.timestamp < startTime || block.timestamp >= endTime; // solhint-disable-line not-rely-on-time + return _endTime == 0 || block.timestamp < _startTime || block.timestamp >= _endTime; // solhint-disable-line not-rely-on-time } /** @@ -60,7 +61,7 @@ contract ERC1155Sale is * @param _tokenIds Token IDs to mint. * @param _amounts Amounts of tokens to mint. */ - function payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts) private { + function _payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts) private { uint256 lastTokenId; uint256 totalCost; uint256 totalAmount; @@ -103,7 +104,7 @@ contract ERC1155Sale is } else { // Paid in ERC20 (bool success, bytes memory data) = _paymentToken.call( - abi.encodeWithSelector(ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), totalCost) + abi.encodeWithSelector(_ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), totalCost) ); if (!success || (data.length > 0 && !abi.decode(data, (bool)))) { revert InsufficientPayment(totalCost, 0); @@ -117,83 +118,83 @@ contract ERC1155Sale is /** * Mint tokens. - * @param _to Address to mint tokens to. - * @param _tokenIds Token IDs to mint. - * @param _amounts Amounts of tokens to mint. - * @param _data Data to pass if receiver is contract. + * @param to Address to mint tokens to. + * @param tokenIds Token IDs to mint. + * @param amounts Amounts of tokens to mint. + * @param data Data to pass if receiver is contract. * @notice Sale must be active for all tokens. * @dev tokenIds must be sorted ascending without duplicates. */ - function mint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + function mint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data) public payable { - payForActiveMint(_tokenIds, _amounts); - _batchMint(_to, _tokenIds, _amounts, _data); + _payForActiveMint(tokenIds, amounts); + _batchMint(to, tokenIds, amounts, data); } /** * Mint tokens as admin. - * @param _to Address to mint tokens to. - * @param _tokenIds Token IDs to mint. - * @param _amounts Amounts of tokens to mint. - * @param _data Data to pass if receiver is contract. + * @param to Address to mint tokens to. + * @param tokenIds Token IDs to mint. + * @param amounts Amounts of tokens to mint. + * @param data Data to pass if receiver is contract. * @notice Only callable by mint admin. */ - function mintAdmin(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + function mintAdmin(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data) public onlyRole(MINT_ADMIN_ROLE) { - _batchMint(_to, _tokenIds, _amounts, _data); + _batchMint(to, tokenIds, amounts, data); } /** * Set the global sale details. - * @param _paymentTokenAddr The ERC20 token address to accept payment in. address(0) indicates ETH. - * @param _cost The amount of payment tokens to accept for each token minted. - * @param _supplyCap The maximum number of tokens that can be minted. - * @param _startTime The start time of the sale. Tokens cannot be minted before this time. - * @param _endTime The end time of the sale. Tokens cannot be minted after this time. + * @param paymentTokenAddr The ERC20 token address to accept payment in. address(0) indicates ETH. + * @param cost The amount of payment tokens to accept for each token minted. + * @param supplyCap The maximum number of tokens that can be minted. + * @param startTime The start time of the sale. Tokens cannot be minted before this time. + * @param endTime The end time of the sale. Tokens cannot be minted after this time. * @dev A zero end time indicates an inactive sale. */ function setGlobalSaleDetails( - address _paymentTokenAddr, - uint256 _cost, - uint256 _supplyCap, - uint64 _startTime, - uint64 _endTime + address paymentTokenAddr, + uint256 cost, + uint256 supplyCap, + uint64 startTime, + uint64 endTime ) public onlyRole(MINT_ADMIN_ROLE) { - _paymentToken = _paymentTokenAddr; - _globalSaleDetails = SaleDetails(_cost, _startTime, _endTime); - totalSupplyCap = _supplyCap; - emit GlobalSaleDetailsUpdated(_cost, _supplyCap, _startTime, _endTime); + _paymentToken = paymentTokenAddr; + _globalSaleDetails = SaleDetails(cost, startTime, endTime); + totalSupplyCap = supplyCap; + emit GlobalSaleDetailsUpdated(cost, supplyCap, startTime, endTime); } /** * Set the sale details for an individual token. - * @param _tokenId The token ID to set the sale details for. - * @param _cost The amount of payment tokens to accept for each token minted. - * @param _supplyCap The maximum number of tokens that can be minted. - * @param _startTime The start time of the sale. Tokens cannot be minted before this time. - * @param _endTime The end time of the sale. Tokens cannot be minted after this time. + * @param tokenId The token ID to set the sale details for. + * @param cost The amount of payment tokens to accept for each token minted. + * @param supplyCap The maximum number of tokens that can be minted. + * @param startTime The start time of the sale. Tokens cannot be minted before this time. + * @param endTime The end time of the sale. Tokens cannot be minted after this time. * @dev A zero end time indicates an inactive sale. */ function setTokenSaleDetails( - uint256 _tokenId, - uint256 _cost, - uint256 _supplyCap, - uint64 _startTime, - uint64 _endTime + uint256 tokenId, + uint256 cost, + uint256 supplyCap, + uint64 startTime, + uint64 endTime ) public onlyRole(MINT_ADMIN_ROLE) { - _tokenSaleDetails[_tokenId] = SaleDetails(_cost, _startTime, _endTime); - tokenSupplyCap[_tokenId] = _supplyCap; - emit TokenSaleDetailsUpdated(_tokenId, _cost, _supplyCap, _startTime, _endTime); + _tokenSaleDetails[tokenId] = SaleDetails(cost, startTime, endTime); + tokenSupplyCap[tokenId] = supplyCap; + emit TokenSaleDetailsUpdated(tokenId, cost, supplyCap, startTime, endTime); } // @@ -202,19 +203,19 @@ contract ERC1155Sale is /** * Withdraws ETH or ERC20 tokens owned by this sale contract. - * @param _to Address to withdraw to. - * @param _amount Amount to withdraw. + * @param to Address to withdraw to. + * @param amount Amount to withdraw. * @dev Withdraws ERC20 when paymentToken is set, else ETH. * @notice Only callable by the contract admin. */ - function withdraw(address _to, uint256 _amount) public onlyRole(DEFAULT_ADMIN_ROLE) { + function withdraw(address to, uint256 amount) public onlyRole(DEFAULT_ADMIN_ROLE) { if (_paymentToken == address(0)) { - (bool success,) = _to.call{value: _amount}(""); + (bool success,) = to.call{value: amount}(""); if (!success) { revert WithdrawFailed(); } } else { - (bool success) = IERC20(_paymentToken).transfer(_to, _amount); + (bool success) = IERC20(_paymentToken).transfer(to, amount); if (!success) { revert WithdrawFailed(); } @@ -237,12 +238,12 @@ contract ERC1155Sale is /** * Get token sale details. - * @param _tokenId Token ID to get sale details for. + * @param tokenId Token ID to get sale details for. * @return Sale details. * @notice Token sale details override global sale details. */ - function tokenSaleDetails(uint256 _tokenId) external view returns (SaleDetails memory) { - return _tokenSaleDetails[_tokenId]; + function tokenSaleDetails(uint256 tokenId) external view returns (SaleDetails memory) { + return _tokenSaleDetails[tokenId]; } /** diff --git a/src/tokens/ERC1155/ERC1155Token.sol b/src/tokens/ERC1155/ERC1155Token.sol index 16173d1..8c82c25 100644 --- a/src/tokens/ERC1155/ERC1155Token.sol +++ b/src/tokens/ERC1155/ERC1155Token.sol @@ -32,7 +32,7 @@ abstract contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public { + function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public virtual { if (msg.sender != _initializer || _initialized) { revert InvalidInitialization(); } diff --git a/src/tokens/ERC1155/ERC1155TokenMinter.sol b/src/tokens/ERC1155/ERC1155TokenMinter.sol index 785dab1..58bf517 100644 --- a/src/tokens/ERC1155/ERC1155TokenMinter.sol +++ b/src/tokens/ERC1155/ERC1155TokenMinter.sol @@ -23,16 +23,16 @@ contract ERC1155TokenMinter is ERC1155MintBurn, ERC1155Token { /** * Initialize the contract. * @param owner Owner address. - * @param name_ Token name. - * @param baseURI_ Base URI for token metadata. + * @param tokenName Token name. + * @param tokenBaseURI Base URI for token metadata. * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory name_, string memory baseURI_) public virtual { + function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public virtual override { if (msg.sender != initializer || initialized) { revert InvalidInitialization(); } - ERC1155Token._initialize(owner, name_, baseURI_); + ERC1155Token.initialize(owner, tokenName, tokenBaseURI); _setupRole(MINTER_ROLE, owner); diff --git a/src/tokens/ERC1155/IERC1155SaleFactory.sol b/src/tokens/ERC1155/IERC1155SaleFactory.sol index fcdf7b5..724c286 100644 --- a/src/tokens/ERC1155/IERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/IERC1155SaleFactory.sol @@ -7,4 +7,16 @@ interface IERC1155SaleFactory { * @param proxyAddr The address of the deployed proxy. */ event ERC1155SaleDeployed(address proxyAddr); + + /** + * Creates an ERC-1155 Sale proxy contract + * @param owner The owner of the ERC-1155 Sale + * @param name The name of the ERC-1155 Sale token + * @param baseURI The base URI of the ERC-1155 Sale token + * @param salt The deployment salt + * @return proxyAddr The address of the ERC-1155 Sale Proxy + */ + function deployERC1155Sale(address owner, string memory name, string memory baseURI, bytes32 salt) + external + returns (address proxyAddr); } diff --git a/src/tokens/ERC721/ERC721Sale.sol b/src/tokens/ERC721/ERC721Sale.sol index 88da71b..2b555a5 100644 --- a/src/tokens/ERC721/ERC721Sale.sol +++ b/src/tokens/ERC721/ERC721Sale.sol @@ -44,13 +44,13 @@ contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors { /** * Checks if the current block.timestamp is out of the give timestamp range. - * @param startTime Earliest acceptable timestamp (inclusive). - * @param endTime Latest acceptable timestamp (exclusive). + * @param _startTime Earliest acceptable timestamp (inclusive). + * @param _endTime Latest acceptable timestamp (exclusive). * @dev A zero endTime value is always considered out of bounds. */ - function blockTimeOutOfBounds(uint256 startTime, uint256 endTime) private view returns (bool) { + function blockTimeOutOfBounds(uint256 _startTime, uint256 _endTime) private view returns (bool) { // 0 end time indicates inactive sale. - return endTime == 0 || block.timestamp < startTime || block.timestamp >= endTime; // solhint-disable-line not-rely-on-time + return _endTime == 0 || block.timestamp < _startTime || block.timestamp >= _endTime; // solhint-disable-line not-rely-on-time } /** diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index 6b7920b..02917d6 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -458,6 +458,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { // Withdraw success ERC20 function testWithdrawERC20(bool useFactory, address withdrawTo, uint256 tokenId, uint256 amount) public + assumeSafe(withdrawTo, tokenId, amount) withFactory(useFactory) { testERC20Mint(false, withdrawTo, tokenId, amount); From 5dd4f4fc81f8ab69625aa9da1d62c795939dd3d5 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Mon, 26 Jun 2023 13:26:28 +1200 Subject: [PATCH 31/49] Use WithdrawControlled for Sale contracts --- src/tokens/ERC1155/ERC1155Sale.sol | 53 +++++++++--------------- src/tokens/ERC721/ERC721Sale.sol | 47 +++++---------------- src/tokens/common/WithdrawControlled.sol | 43 +++++++++++++++++++ test/tokens/ERC1155/ERC1155Sale.t.sol | 22 +++++++--- test/tokens/ERC721/ERC721Sale.t.sol | 22 +++++++--- 5 files changed, 107 insertions(+), 80 deletions(-) create mode 100644 src/tokens/common/WithdrawControlled.sol diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol index 4ed3f8e..a506bda 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -4,12 +4,13 @@ pragma solidity ^0.8.17; import {IERC1155Sale} from "./IERC1155Sale.sol"; import {ERC1155Supply, ERC1155Token} from "./ERC1155Supply.sol"; import {ERC1155SaleErrors} from "./ERC1155SaleErrors.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "../common/WithdrawControlled.sol"; contract ERC1155Sale is IERC1155Sale, ERC1155SaleErrors, - ERC1155Supply + ERC1155Supply, + WithdrawControlled { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); @@ -41,6 +42,7 @@ contract ERC1155Sale is baseURI = tokenBaseURI; _setupRole(DEFAULT_ADMIN_ROLE, owner); _setupRole(MINT_ADMIN_ROLE, owner); + _setupRole(WITHDRAW_ROLE, owner); _initialized = true; } @@ -103,12 +105,7 @@ contract ERC1155Sale is } } else { // Paid in ERC20 - (bool success, bytes memory data) = _paymentToken.call( - abi.encodeWithSelector(_ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), totalCost) - ); - if (!success || (data.length > 0 && !abi.decode(data, (bool)))) { - revert InsufficientPayment(totalCost, 0); - } + SafeERC20.safeTransferFrom(IERC20(_paymentToken), msg.sender, address(this), totalCost); } } @@ -197,31 +194,6 @@ contract ERC1155Sale is emit TokenSaleDetailsUpdated(tokenId, cost, supplyCap, startTime, endTime); } - // - // Withdraw - // - - /** - * Withdraws ETH or ERC20 tokens owned by this sale contract. - * @param to Address to withdraw to. - * @param amount Amount to withdraw. - * @dev Withdraws ERC20 when paymentToken is set, else ETH. - * @notice Only callable by the contract admin. - */ - function withdraw(address to, uint256 amount) public onlyRole(DEFAULT_ADMIN_ROLE) { - if (_paymentToken == address(0)) { - (bool success,) = to.call{value: amount}(""); - if (!success) { - revert WithdrawFailed(); - } - } else { - (bool success) = IERC20(_paymentToken).transfer(to, amount); - if (!success) { - revert WithdrawFailed(); - } - } - } - // // Views // @@ -254,4 +226,19 @@ contract ERC1155Sale is function paymentToken() external view returns (address) { return _paymentToken; } + + /** + * Check interface support. + * @param interfaceId Interface id + * @return True if supported + */ + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override (ERC1155Token, AccessControl) + returns (bool) + { + return interfaceId == type(IERC1155Sale).interfaceId || super.supportsInterface(interfaceId); + } } diff --git a/src/tokens/ERC721/ERC721Sale.sol b/src/tokens/ERC721/ERC721Sale.sol index 2b555a5..6b697c5 100644 --- a/src/tokens/ERC721/ERC721Sale.sol +++ b/src/tokens/ERC721/ERC721Sale.sol @@ -4,12 +4,9 @@ pragma solidity ^0.8.17; import {IERC721Sale} from "./IERC721Sale.sol"; import {ERC721Token} from "./ERC721Token.sol"; import {ERC721SaleErrors} from "./ERC721SaleErrors.sol"; -import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; +import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "../common/WithdrawControlled.sol"; -import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors { +contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors, WithdrawControlled { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); bytes4 private constant _ERC20_TRANSFERFROM_SELECTOR = @@ -38,6 +35,7 @@ contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors { ERC721Token.initialize(owner, tokenName, tokenSymbol, tokenBaseURI); _setupRole(MINT_ADMIN_ROLE, owner); + _setupRole(WITHDRAW_ROLE, owner); _initialized = true; } @@ -72,11 +70,7 @@ contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors { } } else { // Paid in ERC20 - (bool success, bytes memory data) = - paymentToken.call(abi.encodeWithSelector(_ERC20_TRANSFERFROM_SELECTOR, msg.sender, address(this), total)); - if (!success || (data.length > 0 && !abi.decode(data, (bool)))) { - revert InsufficientPayment(total, 0); - } + SafeERC20.safeTransferFrom(IERC20(paymentToken), msg.sender, address(this), total); } } @@ -133,32 +127,6 @@ contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors { emit SaleDetailsUpdated(supplyCap, cost, paymentToken, startTime, endTime); } - // - // Withdraw - // - - /** - * Withdraws ETH or ERC20 tokens owned by this sale contract. - * @param to Address to withdraw to. - * @param amount Amount to withdraw. - * @dev Withdraws ERC20 when paymentToken is set, else ETH. - * @notice Only callable by the contract admin. - */ - function withdraw(address to, uint256 amount) public onlyRole(DEFAULT_ADMIN_ROLE) { - address paymentToken = _saleDetails.paymentToken; - if (paymentToken == address(0)) { - (bool success,) = to.call{value: amount}(""); - if (!success) { - revert WithdrawFailed(); - } - } else { - (bool success) = IERC20(paymentToken).transfer(to, amount); - if (!success) { - revert WithdrawFailed(); - } - } - } - // // Views // @@ -171,11 +139,16 @@ contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors { return _saleDetails; } + /** + * Check interface support. + * @param interfaceId Interface id + * @return True if supported + */ function supportsInterface(bytes4 interfaceId) public view virtual - override + override (ERC721Token, AccessControl) returns (bool) { return interfaceId == type(IERC721Sale).interfaceId || super.supportsInterface(interfaceId); diff --git a/src/tokens/common/WithdrawControlled.sol b/src/tokens/common/WithdrawControlled.sol new file mode 100644 index 0000000..943fe06 --- /dev/null +++ b/src/tokens/common/WithdrawControlled.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +error WithdrawFailed(); + +/** + * An abstract contract that allows ETH and ERC20 tokens stored in the contract to be withdrawn. + */ +abstract contract WithdrawControlled is AccessControl { + bytes32 public constant WITHDRAW_ROLE = keccak256("WITHDRAW_ROLE"); + + // + // Withdraw + // + + /** + * Withdraws ERC20 tokens owned by this contract. + * @param token The ERC20 token address. + * @param to Address to withdraw to. + * @param value Amount to withdraw. + * @notice Only callable by an address with the withdraw role. + */ + function withdrawERC20(address token, address to, uint256 value) public onlyRole(WITHDRAW_ROLE) { + SafeERC20.safeTransfer(IERC20(token), to, value); + } + + /** + * Withdraws ETH owned by this sale contract. + * @param to Address to withdraw to. + * @param value Amount to withdraw. + * @dev Withdraws ERC20 when paymentToken is set, else ETH. + * @notice Only callable by an address with the withdraw role. + */ + function withdrawETH(address to, uint256 value) public onlyRole(WITHDRAW_ROLE) { + (bool success,) = to.call{value: value}(""); + if (!success) { + revert WithdrawFailed(); + } + } +} diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index 02917d6..bd5b5c4 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -16,6 +16,8 @@ import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +// solhint-disable no-rely-on-time + contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { // Redeclare events event TransferBatch( @@ -427,17 +429,27 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { // Withdraw fails if the caller doesn't have the WITHDRAW_ROLE function testWithdrawFail(bool useFactory, address withdrawTo, uint256 amount) public withFactory(useFactory) { - token.revokeRole(token.DEFAULT_ADMIN_ROLE(), address(this)); + token.revokeRole(token.WITHDRAW_ROLE(), address(this)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(token.WITHDRAW_ROLE()), 32) + ) + ); + token.withdrawETH(withdrawTo, amount); vm.expectRevert( abi.encodePacked( "AccessControl: account ", Strings.toHexString(address(this)), " is missing role ", - Strings.toHexString(uint256(token.DEFAULT_ADMIN_ROLE()), 32) + Strings.toHexString(uint256(token.WITHDRAW_ROLE()), 32) ) ); - token.withdraw(withdrawTo, amount); + token.withdrawERC20(address(erc20), withdrawTo, amount); } // Withdraw success ETH @@ -451,7 +463,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { uint256 tokenBalance = address(token).balance; uint256 balance = withdrawTo.balance; - token.withdraw(withdrawTo, tokenBalance); + token.withdrawETH(withdrawTo, tokenBalance); assertEq(tokenBalance + balance, withdrawTo.balance); } @@ -465,7 +477,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { uint256 tokenBalance = erc20.balanceOf(address(token)); uint256 balance = erc20.balanceOf(withdrawTo); - token.withdraw(withdrawTo, tokenBalance); + token.withdrawERC20(address(erc20), withdrawTo, tokenBalance); assertEq(tokenBalance + balance, erc20.balanceOf(withdrawTo)); } diff --git a/test/tokens/ERC721/ERC721Sale.t.sol b/test/tokens/ERC721/ERC721Sale.t.sol index bb424ba..79a529e 100644 --- a/test/tokens/ERC721/ERC721Sale.t.sol +++ b/test/tokens/ERC721/ERC721Sale.t.sol @@ -16,6 +16,8 @@ import {IERC721A} from "erc721a/contracts/interfaces/IERC721A.sol"; import {IERC721AQueryable} from "erc721a/contracts/interfaces/IERC721AQueryable.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +// solhint-disable no-rely-on-time + contract ERC721SaleTest is Test, ERC721SaleErrors { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); @@ -205,17 +207,27 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { // Withdraw fails if the caller doesn't have the WITHDRAW_ROLE function testWithdrawFail(address withdrawTo, uint256 amount) public { - token.revokeRole(token.DEFAULT_ADMIN_ROLE(), address(this)); + token.revokeRole(token.WITHDRAW_ROLE(), address(this)); + + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + Strings.toHexString(address(this)), + " is missing role ", + Strings.toHexString(uint256(token.WITHDRAW_ROLE()), 32) + ) + ); + token.withdrawETH(withdrawTo, amount); vm.expectRevert( abi.encodePacked( "AccessControl: account ", Strings.toHexString(address(this)), " is missing role ", - Strings.toHexString(uint256(token.DEFAULT_ADMIN_ROLE()), 32) + Strings.toHexString(uint256(token.WITHDRAW_ROLE()), 32) ) ); - token.withdraw(withdrawTo, amount); + token.withdrawERC20(address(erc20), withdrawTo, amount); } // Withdraw success ETH @@ -226,7 +238,7 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { uint256 tokenBalance = address(token).balance; uint256 balance = withdrawTo.balance; - token.withdraw(withdrawTo, tokenBalance); + token.withdrawETH(withdrawTo, tokenBalance); assertEq(tokenBalance + balance, withdrawTo.balance); } @@ -236,7 +248,7 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { uint256 tokenBalance = erc20.balanceOf(address(token)); uint256 balance = erc20.balanceOf(withdrawTo); - token.withdraw(withdrawTo, tokenBalance); + token.withdrawERC20(address(erc20), withdrawTo, tokenBalance); assertEq(tokenBalance + balance, erc20.balanceOf(withdrawTo)); } From 930e9af212d1666532a02fbc5e53853b71d85c3b Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 28 Jun 2023 08:00:34 +1200 Subject: [PATCH 32/49] Use minter pattern for ERC20 --- src/tokens/ERC20/ERC20Token.sol | 25 ++----- src/tokens/ERC20/ERC20TokenFactory.sol | 38 ----------- src/tokens/ERC20/ERC20TokenMinter.sol | 66 +++++++++++++++++++ src/tokens/ERC20/ERC20TokenMinterFactory.sol | 38 +++++++++++ src/tokens/ERC20/IERC20TokenFactory.sol | 23 ------- src/tokens/ERC20/IERC20TokenMinterFactory.sol | 23 +++++++ ...RC20Token.t.sol => ERC20TokenMinter.t.sol} | 12 ++-- 7 files changed, 137 insertions(+), 88 deletions(-) delete mode 100644 src/tokens/ERC20/ERC20TokenFactory.sol create mode 100644 src/tokens/ERC20/ERC20TokenMinter.sol create mode 100644 src/tokens/ERC20/ERC20TokenMinterFactory.sol delete mode 100644 src/tokens/ERC20/IERC20TokenFactory.sol create mode 100644 src/tokens/ERC20/IERC20TokenMinterFactory.sol rename test/tokens/ERC20/{ERC20Token.t.sol => ERC20TokenMinter.t.sol} (87%) diff --git a/src/tokens/ERC20/ERC20Token.sol b/src/tokens/ERC20/ERC20Token.sol index 9d53711..1238561 100644 --- a/src/tokens/ERC20/ERC20Token.sol +++ b/src/tokens/ERC20/ERC20Token.sol @@ -11,8 +11,7 @@ error InvalidInitialization(); /** * A ready made implementation of ERC-20. */ -contract ERC20Token is ERC20, AccessControl { - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); +abstract contract ERC20Token is ERC20, AccessControl { string private _tokenName; string private _tokenSymbol; @@ -21,9 +20,6 @@ contract ERC20Token is ERC20, AccessControl { address private immutable _initializer; bool private _initialized; - /** - * Deploy contract. - */ constructor() ERC20("", "") { _initializer = msg.sender; } @@ -36,31 +32,18 @@ contract ERC20Token is ERC20, AccessControl { * @param tokenDecimals Number of decimals * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory tokenName, string memory tokenSymbol, uint8 tokenDecimals) external { + function initialize(address owner, string memory tokenName, string memory tokenSymbol, uint8 tokenDecimals) public virtual { if (msg.sender != _initializer || _initialized) { revert InvalidInitialization(); } - _initialized = true; _tokenName = tokenName; _tokenSymbol = tokenSymbol; _tokenDecimals = tokenDecimals; _setupRole(DEFAULT_ADMIN_ROLE, owner); - _setupRole(MINTER_ROLE, owner); - } - - // - // Minting - // - /** - * Mint tokens. - * @param to Address to mint tokens to. - * @param amount Amount of tokens to mint. - */ - function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { - _mint(to, amount); + _initialized = true; } // @@ -72,7 +55,7 @@ contract ERC20Token is ERC20, AccessControl { * @param interfaceId Interface id * @return True if supported */ - function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IERC20).interfaceId || interfaceId == type(IERC20Metadata).interfaceId || AccessControl.supportsInterface(interfaceId) || super.supportsInterface(interfaceId); } diff --git a/src/tokens/ERC20/ERC20TokenFactory.sol b/src/tokens/ERC20/ERC20TokenFactory.sol deleted file mode 100644 index 7329f16..0000000 --- a/src/tokens/ERC20/ERC20TokenFactory.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import {ERC20Token} from "./ERC20Token.sol"; -import {IERC20TokenFactory} from "./IERC20TokenFactory.sol"; -import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; - -contract ERC20TokenFactory is IERC20TokenFactory, ProxyDeployer { - address private immutable _implAddr; - - /** - * Creates an ERC-20 Token Factory. - */ - constructor() { - ERC20Token proxyImpl = new ERC20Token(); - _implAddr = address(proxyImpl); - } - - /** - * Creates an ERC-20 Token proxy. - * @param owner The owner of the ERC-20 Token proxy - * @param name The name of the ERC-20 Token proxy - * @param symbol The symbol of the ERC-20 Token proxy - * @param decimals The decimals of the ERC-20 Token proxy - * @param salt The deployment salt - * @return proxyAddr The address of the ERC-20 Token Proxy - * @dev The provided `salt` is hashed with the caller address for security. - */ - function deploy(address owner, string memory name, string memory symbol, uint8 decimals, bytes32 salt) - external - returns (address proxyAddr) - { - proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); - ERC20Token(proxyAddr).initialize(owner, name, symbol, decimals); - emit ERC20TokenDeployed(proxyAddr); - return proxyAddr; - } -} diff --git a/src/tokens/ERC20/ERC20TokenMinter.sol b/src/tokens/ERC20/ERC20TokenMinter.sol new file mode 100644 index 0000000..c06c370 --- /dev/null +++ b/src/tokens/ERC20/ERC20TokenMinter.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC20Token} from "./ERC20Token.sol"; + +error InvalidInitialization(); + +/** + * A ready made implementation of ERC-20 capable of minting when role provided. + */ +contract ERC20TokenMinter is ERC20Token { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + address private immutable _initializer; + bool private _initialized; + + constructor() { + _initializer = msg.sender; + } + + /** + * Initialize contract. + * @param owner The owner of the contract + * @param tokenName Name of the token + * @param tokenSymbol Symbol of the token + * @param tokenDecimals Number of decimals + * @dev This should be called immediately after deployment. + */ + function initialize(address owner, string memory tokenName, string memory tokenSymbol, uint8 tokenDecimals) public virtual override { + if (msg.sender != _initializer || _initialized) { + revert InvalidInitialization(); + } + + ERC20Token.initialize(owner, tokenName, tokenSymbol, tokenDecimals); + + _setupRole(MINTER_ROLE, owner); + + _initialized = true; + } + + // + // Minting + // + + /** + * Mint tokens. + * @param to Address to mint tokens to. + * @param amount Amount of tokens to mint. + */ + function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { + _mint(to, amount); + } + + // + // Views + // + + /** + * Check interface support. + * @param interfaceId Interface id + * @return True if supported + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return ERC20Token.supportsInterface(interfaceId) || super.supportsInterface(interfaceId); + } +} diff --git a/src/tokens/ERC20/ERC20TokenMinterFactory.sol b/src/tokens/ERC20/ERC20TokenMinterFactory.sol new file mode 100644 index 0000000..fae8a4a --- /dev/null +++ b/src/tokens/ERC20/ERC20TokenMinterFactory.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {ERC20TokenMinter} from "./ERC20TokenMinter.sol"; +import {IERC20TokenMinterFactory} from "./IERC20TokenMinterFactory.sol"; +import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; + +contract ERC20TokenMinterFactory is IERC20TokenMinterFactory, ProxyDeployer { + address private immutable _implAddr; + + /** + * Creates an ERC-20 Token Minter Factory. + */ + constructor() { + ERC20TokenMinter proxyImpl = new ERC20TokenMinter(); + _implAddr = address(proxyImpl); + } + + /** + * Creates an ERC-20 Token Minter proxy. + * @param owner The owner of the ERC-20 Token Minter proxy + * @param name The name of the ERC-20 Token Minter proxy + * @param symbol The symbol of the ERC-20 Token Minter proxy + * @param decimals The decimals of the ERC-20 Token Minter proxy + * @param salt The deployment salt + * @return proxyAddr The address of the ERC-20 Token Minter Proxy + * @dev The provided `salt` is hashed with the caller address for security. + */ + function deploy(address owner, string memory name, string memory symbol, uint8 decimals, bytes32 salt) + external + returns (address proxyAddr) + { + proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); + ERC20TokenMinter(proxyAddr).initialize(owner, name, symbol, decimals); + emit ERC20TokenMinterDeployed(proxyAddr); + return proxyAddr; + } +} diff --git a/src/tokens/ERC20/IERC20TokenFactory.sol b/src/tokens/ERC20/IERC20TokenFactory.sol deleted file mode 100644 index 4594e11..0000000 --- a/src/tokens/ERC20/IERC20TokenFactory.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -interface IERC20TokenFactory { - /** - * Event emitted when a new ERC-20 Token proxy contract is deployed. - * @param proxyAddr The address of the deployed proxy. - */ - event ERC20TokenDeployed(address proxyAddr); - - /** - * Creates an ERC-20 Token proxy. - * @param owner The owner of the ERC-20 Token proxy - * @param name The name of the ERC-20 Token proxy - * @param symbol The symbol of the ERC-20 Token proxy - * @param decimals The decimals of the ERC-20 Token proxy - * @param salt The deployment salt - * @return proxyAddr The address of the ERC-20 Token Proxy - */ - function deploy(address owner, string memory name, string memory symbol, uint8 decimals, bytes32 salt) - external - returns (address proxyAddr); -} diff --git a/src/tokens/ERC20/IERC20TokenMinterFactory.sol b/src/tokens/ERC20/IERC20TokenMinterFactory.sol new file mode 100644 index 0000000..58aa642 --- /dev/null +++ b/src/tokens/ERC20/IERC20TokenMinterFactory.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC20TokenMinterFactory { + /** + * Event emitted when a new ERC-20 Token Minter proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC20TokenMinterDeployed(address proxyAddr); + + /** + * Creates an ERC-20 Token Minter proxy. + * @param owner The owner of the ERC-20 Token Minter proxy + * @param name The name of the ERC-20 Token Minter proxy + * @param symbol The symbol of the ERC-20 Token Minter proxy + * @param decimals The decimals of the ERC-20 Token Minter proxy + * @param salt The deployment salt + * @return proxyAddr The address of the ERC-20 Token Minter Proxy + */ + function deploy(address owner, string memory name, string memory symbol, uint8 decimals, bytes32 salt) + external + returns (address proxyAddr); +} diff --git a/test/tokens/ERC20/ERC20Token.t.sol b/test/tokens/ERC20/ERC20TokenMinter.t.sol similarity index 87% rename from test/tokens/ERC20/ERC20Token.t.sol rename to test/tokens/ERC20/ERC20TokenMinter.t.sol index d43c662..98acc12 100644 --- a/test/tokens/ERC20/ERC20Token.t.sol +++ b/test/tokens/ERC20/ERC20TokenMinter.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; -import {ERC20Token, InvalidInitialization} from "src/tokens/ERC20/ERC20Token.sol"; -import {ERC20TokenFactory} from "src/tokens/ERC20/ERC20TokenFactory.sol"; +import {ERC20TokenMinter, InvalidInitialization} from "src/tokens/ERC20/ERC20TokenMinter.sol"; +import {ERC20TokenMinterFactory} from "src/tokens/ERC20/ERC20TokenMinterFactory.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -12,11 +12,11 @@ import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -contract ERC20TokenTest is Test { +contract ERC20TokenMinterTest is Test { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 value); - ERC20Token private token; + ERC20TokenMinter private token; uint8 private constant DECIMALS = 18; @@ -28,8 +28,8 @@ contract ERC20TokenTest is Test { vm.deal(address(this), 100 ether); vm.deal(owner, 100 ether); - ERC20TokenFactory factory = new ERC20TokenFactory(); - token = ERC20Token(factory.deploy(owner, "name", "symbol", DECIMALS, 0x0)); + ERC20TokenMinterFactory factory = new ERC20TokenMinterFactory(); + token = ERC20TokenMinter(factory.deploy(owner, "name", "symbol", DECIMALS, 0x0)); } function testReinitializeFails() public { From 6b8ebea996a3b682647d36c4973449a79bd23ffc Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 28 Jun 2023 14:50:28 +1200 Subject: [PATCH 33/49] Fix tests --- test/tokens/ERC1155/ERC1155Sale.t.sol | 58 ++++++++++++++++++--------- test/tokens/ERC721/ERC721Sale.t.sol | 20 ++++++--- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index bd5b5c4..e7c7bcd 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -32,7 +32,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { token = new ERC1155Sale(); token.initialize(address(this), "test", "ipfs://"); - vm.deal(address(this), 100 ether); + vm.deal(address(this), 1e6 ether); } function setUpFromFactory() public { @@ -84,16 +84,22 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { address mintTo, uint256 tokenId, uint256 amount, - uint256 startTime, - uint256 endTime + uint64 startTime, + uint64 endTime ) public assumeSafe(mintTo, tokenId, amount) withFactory(useFactory) { - vm.assume(startTime > endTime); - vm.assume(block.timestamp < startTime || block.timestamp >= endTime); - token.setTokenSaleDetails(tokenId, perTokenCost, 0, uint64(startTime), uint64(endTime)); + if (startTime > endTime) { + uint64 temp = startTime; + startTime = endTime; + endTime = temp; + } + if (block.timestamp >= startTime && block.timestamp <= endTime) { + vm.warp(uint256(endTime) + 1); + } + token.setTokenSaleDetails(tokenId, perTokenCost, 0, startTime, endTime); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -108,16 +114,22 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { address mintTo, uint256 tokenId, uint256 amount, - uint256 startTime, - uint256 endTime + uint64 startTime, + uint64 endTime ) public assumeSafe(mintTo, tokenId, amount) withFactory(useFactory) { - vm.assume(startTime > endTime); - vm.assume(block.timestamp < startTime || block.timestamp >= endTime); - token.setGlobalSaleDetails(address(0), perTokenCost, 0, uint64(startTime), uint64(endTime)); + if (startTime > endTime) { + uint64 temp = startTime; + startTime = endTime; + endTime = temp; + } + if (block.timestamp >= startTime && block.timestamp <= endTime) { + vm.warp(uint256(endTime) + 1); + } + token.setGlobalSaleDetails(address(0), perTokenCost, 0, startTime, endTime); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -156,16 +168,20 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { assumeSafe(mintTo, tokenId, amount) withFactory(useFactory) { - vm.assume(supplyCap > 1); - vm.assume(amount > supplyCap); + if (supplyCap == 0 || supplyCap > 20) { + supplyCap = 1; + } + if (amount <= supplyCap) { + amount = supplyCap + 1; + } token.setGlobalSaleDetails( - address(0), perTokenCost, supplyCap - 1, uint64(block.timestamp), uint64(block.timestamp + 1) + address(0), perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) ); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); - vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap - 1)); + vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap)); token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); } @@ -181,16 +197,20 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { assumeSafe(mintTo, tokenId, amount) withFactory(useFactory) { - vm.assume(supplyCap > 1); - vm.assume(amount > supplyCap); + if (supplyCap == 0 || supplyCap > 20) { + supplyCap = 1; + } + if (amount <= supplyCap) { + amount = supplyCap + 1; + } token.setTokenSaleDetails( - tokenId, perTokenCost, supplyCap - 1, uint64(block.timestamp), uint64(block.timestamp + 1) + tokenId, perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) ); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); - vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap - 1)); + vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap)); token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); } diff --git a/test/tokens/ERC721/ERC721Sale.t.sol b/test/tokens/ERC721/ERC721Sale.t.sol index 79a529e..e2f8b58 100644 --- a/test/tokens/ERC721/ERC721Sale.t.sol +++ b/test/tokens/ERC721/ERC721Sale.t.sol @@ -61,13 +61,19 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { } // Minting denied when sale is expired. - function testMintExpiredFail(bool useFactory, address mintTo, uint256 amount, uint256 startTime, uint256 endTime) + function testMintExpiredFail(bool useFactory, address mintTo, uint256 amount, uint64 startTime, uint64 endTime) public assumeSafe(mintTo, amount) withFactory(useFactory) { - vm.assume(startTime > endTime); - vm.assume(block.timestamp < startTime || block.timestamp >= endTime); + if (startTime > endTime) { + uint64 temp = startTime; + startTime = endTime; + endTime = temp; + } + if (block.timestamp >= startTime && block.timestamp <= endTime) { + vm.warp(uint256(endTime) + 1); + } token.setSaleDetails(0, perTokenCost, address(0), uint64(startTime), uint64(endTime)); vm.expectRevert(SaleInactive.selector); @@ -80,8 +86,12 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { assumeSafe(mintTo, amount) withFactory(useFactory) { - vm.assume(supplyCap > 0); - vm.assume(amount > supplyCap); + if (supplyCap == 0 || supplyCap > 20) { + supplyCap = 1; + } + if (amount <= supplyCap) { + amount = supplyCap + 1; + } token.setSaleDetails(supplyCap, perTokenCost, address(0), uint64(block.timestamp), uint64(block.timestamp + 1)); vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap)); From 5b7f9459b63285053f49f004197f510b99411eca Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 28 Jun 2023 15:03:28 +1200 Subject: [PATCH 34/49] Align order of params --- src/tokens/ERC1155/ERC1155Sale.sol | 5 +++-- test/tokens/ERC1155/ERC1155Sale.t.sol | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/ERC1155Sale.sol index a506bda..ae2edb8 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/ERC1155Sale.sol @@ -147,17 +147,17 @@ contract ERC1155Sale is /** * Set the global sale details. - * @param paymentTokenAddr The ERC20 token address to accept payment in. address(0) indicates ETH. * @param cost The amount of payment tokens to accept for each token minted. * @param supplyCap The maximum number of tokens that can be minted. + * @param paymentTokenAddr The ERC20 token address to accept payment in. address(0) indicates ETH. * @param startTime The start time of the sale. Tokens cannot be minted before this time. * @param endTime The end time of the sale. Tokens cannot be minted after this time. * @dev A zero end time indicates an inactive sale. */ function setGlobalSaleDetails( - address paymentTokenAddr, uint256 cost, uint256 supplyCap, + address paymentTokenAddr, uint64 startTime, uint64 endTime ) @@ -178,6 +178,7 @@ contract ERC1155Sale is * @param startTime The start time of the sale. Tokens cannot be minted before this time. * @param endTime The end time of the sale. Tokens cannot be minted after this time. * @dev A zero end time indicates an inactive sale. + * @notice The payment token is set globally. */ function setTokenSaleDetails( uint256 tokenId, diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/ERC1155Sale.t.sol index e7c7bcd..1b4adcd 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/ERC1155Sale.t.sol @@ -129,7 +129,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { if (block.timestamp >= startTime && block.timestamp <= endTime) { vm.warp(uint256(endTime) + 1); } - token.setGlobalSaleDetails(address(0), perTokenCost, 0, startTime, endTime); + token.setGlobalSaleDetails(perTokenCost, 0, address(0), startTime, endTime); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -175,7 +175,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { amount = supplyCap + 1; } token.setGlobalSaleDetails( - address(0), perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) + perTokenCost, supplyCap, address(0), uint64(block.timestamp), uint64(block.timestamp + 1) ); uint256[] memory tokenIds = singleToArray(tokenId); @@ -278,7 +278,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { assumeSafe(mintTo, tokenId, amount) withFactory(useFactory) { - token.setGlobalSaleDetails(address(0), 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setGlobalSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1)); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -315,7 +315,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { withERC20 { token.setGlobalSaleDetails( - address(erc20), perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1) + perTokenCost, 0, address(erc20), uint64(block.timestamp - 1), uint64(block.timestamp + 1) ); uint256[] memory tokenIds = singleToArray(tokenId); uint256[] memory amounts = singleToArray(amount); @@ -529,7 +529,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { modifier withGlobalSaleActive() { token.setGlobalSaleDetails( - address(0), perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1) + perTokenCost, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1) ); _; } From b16bad6934f00e584073dd9796a051a256643e39 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Thu, 29 Jun 2023 09:54:38 +1200 Subject: [PATCH 35/49] Rearrange folder structure --- remappings.txt | 1 + src/tokens/ERC1155/ERC1155Token.sol | 2 +- .../ERC1155/{ => extensions/supply}/ERC1155Supply.sol | 4 ++-- .../{ => extensions/supply}/ERC1155SupplyErrors.sol | 0 .../{ => presets/minter}/ERC1155TokenMinter.sol | 9 ++++----- .../presets/minter/ERC1155TokenMinterErrors.sol | 11 +++++++++++ .../minter}/ERC1155TokenMinterFactory.sol | 6 +++--- .../minter}/IERC1155TokenMinterFactory.sol | 0 src/tokens/ERC1155/{ => presets/sale}/ERC1155Sale.sol | 8 ++++---- .../ERC1155/{ => presets/sale}/ERC1155SaleErrors.sol | 0 .../ERC1155/{ => presets/sale}/ERC1155SaleFactory.sol | 6 +++--- .../ERC1155/{ => presets/sale}/IERC1155Sale.sol | 0 .../{ => presets/sale}/IERC1155SaleFactory.sol | 0 .../ERC20/{ => presets/minter}/ERC20TokenMinter.sol | 7 +++---- .../ERC20/presets/minter/ERC20TokenMinterErrors.sol | 11 +++++++++++ .../{ => presets/minter}/ERC20TokenMinterFactory.sol | 6 +++--- .../{ => presets/minter}/IERC20TokenMinterFactory.sol | 0 src/tokens/ERC721/ERC721Token.sol | 2 +- .../ERC721/{ => presets/minter}/ERC721TokenMinter.sol | 7 +++---- .../ERC721/presets/minter/ERC721TokenMinterErrors.sol | 11 +++++++++++ .../{ => presets/minter}/ERC721TokenMinterFactory.sol | 6 +++--- .../minter}/IERC721TokenMinterFactory.sol | 0 src/tokens/ERC721/{ => presets/sale}/ERC721Sale.sol | 8 ++++---- .../ERC721/{ => presets/sale}/ERC721SaleErrors.sol | 0 .../ERC721/{ => presets/sale}/ERC721SaleFactory.sol | 6 +++--- src/tokens/ERC721/{ => presets/sale}/IERC721Sale.sol | 0 .../ERC721/{ => presets/sale}/IERC721SaleFactory.sol | 0 test/tokens/ERC1155/{ => presets}/ERC1155Sale.t.sol | 8 ++++---- .../ERC1155/{ => presets}/ERC1155TokenMinter.t.sol | 7 ++++--- test/tokens/ERC20/ERC20TokenMinter.t.sol | 7 ++++--- test/tokens/ERC721/{ => presets}/ERC721Sale.t.sol | 6 +++--- .../ERC721/{ => presets}/ERC721TokenMinter.t.sol | 7 ++++--- 32 files changed, 90 insertions(+), 56 deletions(-) rename src/tokens/ERC1155/{ => extensions/supply}/ERC1155Supply.sol (95%) rename src/tokens/ERC1155/{ => extensions/supply}/ERC1155SupplyErrors.sol (100%) rename src/tokens/ERC1155/{ => presets/minter}/ERC1155TokenMinter.sol (84%) create mode 100644 src/tokens/ERC1155/presets/minter/ERC1155TokenMinterErrors.sol rename src/tokens/ERC1155/{ => presets/minter}/ERC1155TokenMinterFactory.sol (78%) rename src/tokens/ERC1155/{ => presets/minter}/IERC1155TokenMinterFactory.sol (100%) rename src/tokens/ERC1155/{ => presets/sale}/ERC1155Sale.sol (95%) rename src/tokens/ERC1155/{ => presets/sale}/ERC1155SaleErrors.sol (100%) rename src/tokens/ERC1155/{ => presets/sale}/ERC1155SaleFactory.sol (76%) rename src/tokens/ERC1155/{ => presets/sale}/IERC1155Sale.sol (100%) rename src/tokens/ERC1155/{ => presets/sale}/IERC1155SaleFactory.sol (100%) rename src/tokens/ERC20/{ => presets/minter}/ERC20TokenMinter.sol (85%) create mode 100644 src/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol rename src/tokens/ERC20/{ => presets/minter}/ERC20TokenMinterFactory.sol (79%) rename src/tokens/ERC20/{ => presets/minter}/IERC20TokenMinterFactory.sol (100%) rename src/tokens/ERC721/{ => presets/minter}/ERC721TokenMinter.sol (83%) create mode 100644 src/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol rename src/tokens/ERC721/{ => presets/minter}/ERC721TokenMinterFactory.sol (79%) rename src/tokens/ERC721/{ => presets/minter}/IERC721TokenMinterFactory.sol (100%) rename src/tokens/ERC721/{ => presets/sale}/ERC721Sale.sol (93%) rename src/tokens/ERC721/{ => presets/sale}/ERC721SaleErrors.sol (100%) rename src/tokens/ERC721/{ => presets/sale}/ERC721SaleFactory.sol (79%) rename src/tokens/ERC721/{ => presets/sale}/IERC721Sale.sol (100%) rename src/tokens/ERC721/{ => presets/sale}/IERC721SaleFactory.sol (100%) rename test/tokens/ERC1155/{ => presets}/ERC1155Sale.t.sol (98%) rename test/tokens/ERC1155/{ => presets}/ERC1155TokenMinter.t.sol (96%) rename test/tokens/ERC721/{ => presets}/ERC721Sale.t.sol (97%) rename test/tokens/ERC721/{ => presets}/ERC721TokenMinter.t.sol (95%) diff --git a/remappings.txt b/remappings.txt index 8504885..b904f29 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,4 @@ +@0xsequence/contracts-library/=src/ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ @0xsequence/erc20-meta-token/=node_modules/@0xsequence/erc20-meta-token/ diff --git a/src/tokens/ERC1155/ERC1155Token.sol b/src/tokens/ERC1155/ERC1155Token.sol index 8c82c25..77dcb3f 100644 --- a/src/tokens/ERC1155/ERC1155Token.sol +++ b/src/tokens/ERC1155/ERC1155Token.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import {ERC1155, ERC1155MintBurn} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155MintBurn.sol"; import {ERC1155Meta} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Meta.sol"; import {ERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -import {ERC2981Controlled} from "../common/ERC2981Controlled.sol"; +import {ERC2981Controlled} from "@0xsequence/contracts-library/tokens/common/ERC2981Controlled.sol"; error InvalidInitialization(); diff --git a/src/tokens/ERC1155/ERC1155Supply.sol b/src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol similarity index 95% rename from src/tokens/ERC1155/ERC1155Supply.sol rename to src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol index 9e26748..f5d9d74 100644 --- a/src/tokens/ERC1155/ERC1155Supply.sol +++ b/src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ERC1155Token} from "./ERC1155Token.sol"; -import {ERC1155SupplyErrors} from "./ERC1155SupplyErrors.sol"; +import {ERC1155Token} from "@0xsequence/contracts-library/tokens/ERC1155/ERC1155Token.sol"; +import {ERC1155SupplyErrors} from "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/ERC1155SupplyErrors.sol"; /** * An ERC1155 extension that tracks token supply. diff --git a/src/tokens/ERC1155/ERC1155SupplyErrors.sol b/src/tokens/ERC1155/extensions/supply/ERC1155SupplyErrors.sol similarity index 100% rename from src/tokens/ERC1155/ERC1155SupplyErrors.sol rename to src/tokens/ERC1155/extensions/supply/ERC1155SupplyErrors.sol diff --git a/src/tokens/ERC1155/ERC1155TokenMinter.sol b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol similarity index 84% rename from src/tokens/ERC1155/ERC1155TokenMinter.sol rename to src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol index 58bf517..b5639d1 100644 --- a/src/tokens/ERC1155/ERC1155TokenMinter.sol +++ b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol @@ -2,15 +2,14 @@ pragma solidity ^0.8.17; import {ERC1155MintBurn, ERC1155} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155MintBurn.sol"; -import {ERC1155Token} from "./ERC1155Token.sol"; -import {ERC2981Controlled} from "../common/ERC2981Controlled.sol"; - -error InvalidInitialization(); +import {ERC1155TokenMinterErrors} from "@0xsequence/contracts-library/tokens/ERC1155//presets/minter/ERC1155TokenMinterErrors.sol"; +import {ERC1155Token} from "@0xsequence/contracts-library/tokens/ERC1155/ERC1155Token.sol"; +import {ERC2981Controlled} from "@0xsequence/contracts-library/tokens/common/ERC2981Controlled.sol"; /** * A ready made implementation of ERC-1155 capable of minting when role provided. */ -contract ERC1155TokenMinter is ERC1155MintBurn, ERC1155Token { +contract ERC1155TokenMinter is ERC1155MintBurn, ERC1155Token, ERC1155TokenMinterErrors { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); address private immutable initializer; diff --git a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterErrors.sol b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterErrors.sol new file mode 100644 index 0000000..0a60e7e --- /dev/null +++ b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterErrors.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +// A contract for errors for extensibility. +abstract contract ERC1155TokenMinterErrors { + + /** + * Invalid initialization error. + */ + error InvalidInitialization(); +} diff --git a/src/tokens/ERC1155/ERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol similarity index 78% rename from src/tokens/ERC1155/ERC1155TokenMinterFactory.sol rename to src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol index 82e2786..f59aecd 100644 --- a/src/tokens/ERC1155/ERC1155TokenMinterFactory.sol +++ b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ERC1155TokenMinter} from "./ERC1155TokenMinter.sol"; -import {IERC1155TokenMinterFactory} from "./IERC1155TokenMinterFactory.sol"; -import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; +import {ERC1155TokenMinter} from "@0xsequence/contracts-library/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol"; +import {IERC1155TokenMinterFactory} from "@0xsequence/contracts-library/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol"; +import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; contract ERC1155TokenMinterFactory is IERC1155TokenMinterFactory, ProxyDeployer { address private immutable implAddr; diff --git a/src/tokens/ERC1155/IERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol similarity index 100% rename from src/tokens/ERC1155/IERC1155TokenMinterFactory.sol rename to src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol diff --git a/src/tokens/ERC1155/ERC1155Sale.sol b/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol similarity index 95% rename from src/tokens/ERC1155/ERC1155Sale.sol rename to src/tokens/ERC1155/presets/sale/ERC1155Sale.sol index ae2edb8..08a3dab 100644 --- a/src/tokens/ERC1155/ERC1155Sale.sol +++ b/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {IERC1155Sale} from "./IERC1155Sale.sol"; -import {ERC1155Supply, ERC1155Token} from "./ERC1155Supply.sol"; -import {ERC1155SaleErrors} from "./ERC1155SaleErrors.sol"; -import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "../common/WithdrawControlled.sol"; +import {IERC1155Sale} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/IERC1155Sale.sol"; +import {ERC1155SaleErrors} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol"; +import {ERC1155Supply, ERC1155Token} from "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/ERC1155Supply.sol"; +import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; contract ERC1155Sale is IERC1155Sale, diff --git a/src/tokens/ERC1155/ERC1155SaleErrors.sol b/src/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol similarity index 100% rename from src/tokens/ERC1155/ERC1155SaleErrors.sol rename to src/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol diff --git a/src/tokens/ERC1155/ERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol similarity index 76% rename from src/tokens/ERC1155/ERC1155SaleFactory.sol rename to src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol index f0a39b2..4b03038 100644 --- a/src/tokens/ERC1155/ERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ERC1155Sale} from "./ERC1155Sale.sol"; -import {IERC1155SaleFactory} from "./IERC1155SaleFactory.sol"; -import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; +import {ERC1155Sale} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/ERC1155Sale.sol"; +import {IERC1155SaleFactory} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol"; +import {ProxyDeployer} from "@0xsequence/contracts-library//proxies/ERC1967/ProxyDeployer.sol"; contract ERC1155SaleFactory is IERC1155SaleFactory, ProxyDeployer { address private immutable _implAddr; diff --git a/src/tokens/ERC1155/IERC1155Sale.sol b/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol similarity index 100% rename from src/tokens/ERC1155/IERC1155Sale.sol rename to src/tokens/ERC1155/presets/sale/IERC1155Sale.sol diff --git a/src/tokens/ERC1155/IERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol similarity index 100% rename from src/tokens/ERC1155/IERC1155SaleFactory.sol rename to src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol diff --git a/src/tokens/ERC20/ERC20TokenMinter.sol b/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol similarity index 85% rename from src/tokens/ERC20/ERC20TokenMinter.sol rename to src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol index c06c370..125231c 100644 --- a/src/tokens/ERC20/ERC20TokenMinter.sol +++ b/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ERC20Token} from "./ERC20Token.sol"; - -error InvalidInitialization(); +import {ERC20Token} from "@0xsequence/contracts-library/tokens/ERC20/ERC20Token.sol"; +import {ERC20TokenMinterErrors} from "@0xsequence/contracts-library/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol"; /** * A ready made implementation of ERC-20 capable of minting when role provided. */ -contract ERC20TokenMinter is ERC20Token { +contract ERC20TokenMinter is ERC20Token, ERC20TokenMinterErrors { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); address private immutable _initializer; diff --git a/src/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol b/src/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol new file mode 100644 index 0000000..82aece9 --- /dev/null +++ b/src/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +// A contract for errors for extensibility. +abstract contract ERC20TokenMinterErrors { + + /** + * Invalid initialization error. + */ + error InvalidInitialization(); +} diff --git a/src/tokens/ERC20/ERC20TokenMinterFactory.sol b/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol similarity index 79% rename from src/tokens/ERC20/ERC20TokenMinterFactory.sol rename to src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol index fae8a4a..2173946 100644 --- a/src/tokens/ERC20/ERC20TokenMinterFactory.sol +++ b/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ERC20TokenMinter} from "./ERC20TokenMinter.sol"; -import {IERC20TokenMinterFactory} from "./IERC20TokenMinterFactory.sol"; -import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; +import {ERC20TokenMinter} from "@0xsequence/contracts-library/tokens/ERC20/presets/minter/ERC20TokenMinter.sol"; +import {IERC20TokenMinterFactory} from "@0xsequence/contracts-library/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol"; +import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; contract ERC20TokenMinterFactory is IERC20TokenMinterFactory, ProxyDeployer { address private immutable _implAddr; diff --git a/src/tokens/ERC20/IERC20TokenMinterFactory.sol b/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol similarity index 100% rename from src/tokens/ERC20/IERC20TokenMinterFactory.sol rename to src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol diff --git a/src/tokens/ERC721/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol index 4304bd4..173fe06 100644 --- a/src/tokens/ERC721/ERC721Token.sol +++ b/src/tokens/ERC721/ERC721Token.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import { ERC721AQueryable, IERC721AQueryable, ERC721A, IERC721A } from "erc721a/contracts/extensions/ERC721AQueryable.sol"; -import {ERC2981Controlled} from "../common/ERC2981Controlled.sol"; +import {ERC2981Controlled} from "@0xsequence/contracts-library/tokens/common/ERC2981Controlled.sol"; error InvalidInitialization(); diff --git a/src/tokens/ERC721/ERC721TokenMinter.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol similarity index 83% rename from src/tokens/ERC721/ERC721TokenMinter.sol rename to src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol index b114f1d..d7a1222 100644 --- a/src/tokens/ERC721/ERC721TokenMinter.sol +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ERC721Token} from "./ERC721Token.sol"; - -error InvalidInitialization(); +import {ERC721Token} from "@0xsequence/contracts-library/tokens/ERC721/ERC721Token.sol"; +import {ERC721TokenMinterErrors} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol"; /** * A ready made implementation of ERC-721 with role based minting. */ -contract ERC721TokenMinter is ERC721Token { +contract ERC721TokenMinter is ERC721Token, ERC721TokenMinterErrors { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); address private immutable _initializer; diff --git a/src/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol new file mode 100644 index 0000000..f5a4baa --- /dev/null +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +// A contract for errors for extensibility. +abstract contract ERC721TokenMinterErrors { + + /** + * Invalid initialization error. + */ + error InvalidInitialization(); +} diff --git a/src/tokens/ERC721/ERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol similarity index 79% rename from src/tokens/ERC721/ERC721TokenMinterFactory.sol rename to src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol index 56fc328..7caddab 100644 --- a/src/tokens/ERC721/ERC721TokenMinterFactory.sol +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ERC721TokenMinter} from "./ERC721TokenMinter.sol"; -import {IERC721TokenMinterFactory} from "./IERC721TokenMinterFactory.sol"; -import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; +import {ERC721TokenMinter} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/ERC721TokenMinter.sol"; +import {IERC721TokenMinterFactory} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol"; +import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; contract ERC721TokenMinterFactory is IERC721TokenMinterFactory, ProxyDeployer { address private immutable _implAddr; diff --git a/src/tokens/ERC721/IERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol similarity index 100% rename from src/tokens/ERC721/IERC721TokenMinterFactory.sol rename to src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol diff --git a/src/tokens/ERC721/ERC721Sale.sol b/src/tokens/ERC721/presets/sale/ERC721Sale.sol similarity index 93% rename from src/tokens/ERC721/ERC721Sale.sol rename to src/tokens/ERC721/presets/sale/ERC721Sale.sol index 6b697c5..f040745 100644 --- a/src/tokens/ERC721/ERC721Sale.sol +++ b/src/tokens/ERC721/presets/sale/ERC721Sale.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {IERC721Sale} from "./IERC721Sale.sol"; -import {ERC721Token} from "./ERC721Token.sol"; -import {ERC721SaleErrors} from "./ERC721SaleErrors.sol"; -import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "../common/WithdrawControlled.sol"; +import {IERC721Sale} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/IERC721Sale.sol"; +import {ERC721SaleErrors} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/ERC721SaleErrors.sol"; +import {ERC721Token} from "@0xsequence/contracts-library/tokens/ERC721/ERC721Token.sol"; +import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors, WithdrawControlled { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); diff --git a/src/tokens/ERC721/ERC721SaleErrors.sol b/src/tokens/ERC721/presets/sale/ERC721SaleErrors.sol similarity index 100% rename from src/tokens/ERC721/ERC721SaleErrors.sol rename to src/tokens/ERC721/presets/sale/ERC721SaleErrors.sol diff --git a/src/tokens/ERC721/ERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol similarity index 79% rename from src/tokens/ERC721/ERC721SaleFactory.sol rename to src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol index 6f6ce9e..5be6b3f 100644 --- a/src/tokens/ERC721/ERC721SaleFactory.sol +++ b/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ERC721Sale} from "./ERC721Sale.sol"; -import {IERC721SaleFactory} from "./IERC721SaleFactory.sol"; -import {ProxyDeployer} from "../../proxies/ERC1967/ProxyDeployer.sol"; +import {ERC721Sale} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/ERC721Sale.sol"; +import {IERC721SaleFactory} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/IERC721SaleFactory.sol"; +import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; contract ERC721SaleFactory is IERC721SaleFactory, ProxyDeployer { address private immutable _implAddr; diff --git a/src/tokens/ERC721/IERC721Sale.sol b/src/tokens/ERC721/presets/sale/IERC721Sale.sol similarity index 100% rename from src/tokens/ERC721/IERC721Sale.sol rename to src/tokens/ERC721/presets/sale/IERC721Sale.sol diff --git a/src/tokens/ERC721/IERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol similarity index 100% rename from src/tokens/ERC721/IERC721SaleFactory.sol rename to src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol diff --git a/test/tokens/ERC1155/ERC1155Sale.t.sol b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol similarity index 98% rename from test/tokens/ERC1155/ERC1155Sale.t.sol rename to test/tokens/ERC1155/presets/ERC1155Sale.t.sol index 1b4adcd..a7f0e25 100644 --- a/test/tokens/ERC1155/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; -import {ERC1155Sale} from "src/tokens/ERC1155/ERC1155Sale.sol"; -import {ERC1155SaleFactory} from "src/tokens/ERC1155/ERC1155SaleFactory.sol"; -import {ERC1155SaleErrors} from "src/tokens/ERC1155/ERC1155SaleErrors.sol"; -import {ERC1155SupplyErrors} from "src/tokens/ERC1155/ERC1155SupplyErrors.sol"; +import {ERC1155Sale} from "src/tokens/ERC1155/presets/sale/ERC1155Sale.sol"; +import {ERC1155SaleFactory} from "src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol"; +import {ERC1155SaleErrors} from "src/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol"; +import {ERC1155SupplyErrors} from "src/tokens/ERC1155/extensions/supply/ERC1155SupplyErrors.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; diff --git a/test/tokens/ERC1155/ERC1155TokenMinter.t.sol b/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol similarity index 96% rename from test/tokens/ERC1155/ERC1155TokenMinter.t.sol rename to test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol index 7938631..3f326f2 100644 --- a/test/tokens/ERC1155/ERC1155TokenMinter.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; -import {ERC1155TokenMinter, InvalidInitialization} from "src/tokens/ERC1155/ERC1155TokenMinter.sol"; -import {ERC1155TokenMinterFactory} from "src/tokens/ERC1155/ERC1155TokenMinterFactory.sol"; +import {ERC1155TokenMinter} from "src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol"; +import {ERC1155TokenMinterErrors} from "src/tokens/ERC1155/presets/minter/ERC1155TokenMinterErrors.sol"; +import {ERC1155TokenMinterFactory} from "src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -12,7 +13,7 @@ import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -contract ERC1155TokenMinterTest is Test { +contract ERC1155TokenMinterTest is Test, ERC1155TokenMinterErrors { // Redeclare events event TransferSingle( address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _amount diff --git a/test/tokens/ERC20/ERC20TokenMinter.t.sol b/test/tokens/ERC20/ERC20TokenMinter.t.sol index 98acc12..17e09fd 100644 --- a/test/tokens/ERC20/ERC20TokenMinter.t.sol +++ b/test/tokens/ERC20/ERC20TokenMinter.t.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; -import {ERC20TokenMinter, InvalidInitialization} from "src/tokens/ERC20/ERC20TokenMinter.sol"; -import {ERC20TokenMinterFactory} from "src/tokens/ERC20/ERC20TokenMinterFactory.sol"; +import {ERC20TokenMinter} from "src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol"; +import {ERC20TokenMinterErrors} from "src/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol"; +import {ERC20TokenMinterFactory} from "src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -12,7 +13,7 @@ import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -contract ERC20TokenMinterTest is Test { +contract ERC20TokenMinterTest is Test, ERC20TokenMinterErrors { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 value); diff --git a/test/tokens/ERC721/ERC721Sale.t.sol b/test/tokens/ERC721/presets/ERC721Sale.t.sol similarity index 97% rename from test/tokens/ERC721/ERC721Sale.t.sol rename to test/tokens/ERC721/presets/ERC721Sale.t.sol index e2f8b58..6768e72 100644 --- a/test/tokens/ERC721/ERC721Sale.t.sol +++ b/test/tokens/ERC721/presets/ERC721Sale.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; -import {ERC721Sale} from "src/tokens/ERC721/ERC721Sale.sol"; -import {ERC721SaleErrors} from "src/tokens/ERC721/ERC721SaleErrors.sol"; -import {ERC721SaleFactory} from "src/tokens/ERC721/ERC721SaleFactory.sol"; +import {ERC721Sale} from "src/tokens/ERC721/presets/sale/ERC721Sale.sol"; +import {ERC721SaleErrors} from "src/tokens/ERC721/presets/sale/ERC721SaleErrors.sol"; +import {ERC721SaleFactory} from "src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; diff --git a/test/tokens/ERC721/ERC721TokenMinter.t.sol b/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol similarity index 95% rename from test/tokens/ERC721/ERC721TokenMinter.t.sol rename to test/tokens/ERC721/presets/ERC721TokenMinter.t.sol index ef13d30..32b935e 100644 --- a/test/tokens/ERC721/ERC721TokenMinter.t.sol +++ b/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; -import {ERC721TokenMinter, InvalidInitialization} from "src/tokens/ERC721/ERC721TokenMinter.sol"; -import {ERC721TokenMinterFactory} from "src/tokens/ERC721/ERC721TokenMinterFactory.sol"; +import {ERC721TokenMinter} from "src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol"; +import {ERC721TokenMinterErrors} from "src/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol"; +import {ERC721TokenMinterFactory} from "src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -12,7 +13,7 @@ import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; import {IERC721A} from "erc721a/contracts/interfaces/IERC721A.sol"; import {IERC721AQueryable} from "erc721a/contracts/extensions/IERC721AQueryable.sol"; -contract ERC721TokenMinterTest is Test { +contract ERC721TokenMinterTest is Test, ERC721TokenMinterErrors { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); From d243bd56fcfb03459c9913d31522e833b1aaa85d Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Fri, 30 Jun 2023 12:45:07 +1200 Subject: [PATCH 36/49] Add merkle allowlist to ERC1155Sale --- .gitmodules | 3 + lib/murky | 1 + package.json | 7 +- remappings.txt | 1 + scripts/generateMerkleTree.ts | 23 ++ .../ERC1155/presets/sale/ERC1155Sale.sol | 33 +- .../ERC1155/presets/sale/IERC1155Sale.sol | 26 +- src/tokens/common/MerkleProofSingleUse.sol | 44 ++ test/tokens/ERC1155/presets/ERC1155Sale.t.sol | 228 +++++++--- test/tokens/TestHelper.sol | 15 + tsconfig.json | 27 ++ yarn.lock | 388 +++++++++++++++++- 12 files changed, 713 insertions(+), 83 deletions(-) create mode 160000 lib/murky create mode 100644 scripts/generateMerkleTree.ts create mode 100644 src/tokens/common/MerkleProofSingleUse.sol create mode 100644 test/tokens/TestHelper.sol create mode 100644 tsconfig.json diff --git a/.gitmodules b/.gitmodules index 888d42d..d6fe983 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/murky"] + path = lib/murky + url = https://github.com/dmfxyz/murky diff --git a/lib/murky b/lib/murky new file mode 160000 index 0000000..1d9566b --- /dev/null +++ b/lib/murky @@ -0,0 +1 @@ +Subproject commit 1d9566b908b9702c45d354a1caabe8ef5a69938d diff --git a/package.json b/package.json index 648eed1..33dde30 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ "dependencies": { "@0xsequence/erc-1155": "^4.0.3", "@0xsequence/erc20-meta-token": "^4.0.1", + "@openzeppelin/contracts": "^4.8.3", "erc721a": "^4.2.3", - "erc721a-upgradeable": "^4.2.3", - "@openzeppelin/contracts": "^4.8.3" + "erc721a-upgradeable": "^4.2.3" }, "lint-staged": { "**/*.sol": "yarn lint:sol && yarn format:sol" @@ -37,8 +37,11 @@ "devDependencies": { "@types/node": "^20.1.0", "dotenv": "^16.1.4", + "ethers": "^5.7.2", "husky": "^8.0.3", + "keccak256": "^1.0.6", "lint-staged": "^13.2.2", + "merkletreejs": "^0.2.32", "solhint": "^3.4.1", "ts-node": "^10.9.1", "typescript": "^5.0.4" diff --git a/remappings.txt b/remappings.txt index b904f29..7777c91 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,6 +1,7 @@ @0xsequence/contracts-library/=src/ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ +murky/=lib/murky/src/ @0xsequence/erc20-meta-token/=node_modules/@0xsequence/erc20-meta-token/ @0xsequence/erc-1155/=node_modules/@0xsequence/erc-1155/ erc721a/=node_modules/erc721a/ diff --git a/scripts/generateMerkleTree.ts b/scripts/generateMerkleTree.ts new file mode 100644 index 0000000..6b33cf0 --- /dev/null +++ b/scripts/generateMerkleTree.ts @@ -0,0 +1,23 @@ +import { MerkleTree } from 'merkletreejs' +import { utils } from 'ethers' +import keccak256 from 'keccak256' + +const generateTree = (elements: string[]) => { + const hashed = elements.map(e => utils.solidityKeccak256(['uint256'], [e])) + + const merkleTree = new MerkleTree(hashed, keccak256, { + sort: true, + sortPairs: true, + sortLeaves: true, + }) + + return { + merkleTree, + root: merkleTree.getHexRoot(), + } +} + +const generateProof = (tree: MerkleTree, element: string) => + tree.getHexProof(utils.solidityKeccak256(['uint256'], [element])) + +export { generateTree, generateProof } diff --git a/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol b/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol index 08a3dab..3810061 100644 --- a/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol +++ b/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol @@ -5,12 +5,14 @@ import {IERC1155Sale} from "@0xsequence/contracts-library/tokens/ERC1155/presets import {ERC1155SaleErrors} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol"; import {ERC1155Supply, ERC1155Token} from "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/ERC1155Supply.sol"; import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; +import {MerkleProofSingleUse} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; contract ERC1155Sale is IERC1155Sale, ERC1155SaleErrors, ERC1155Supply, - WithdrawControlled + WithdrawControlled, + MerkleProofSingleUse { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); @@ -62,8 +64,9 @@ contract ERC1155Sale is * Checks the sale is active and takes payment. * @param _tokenIds Token IDs to mint. * @param _amounts Amounts of tokens to mint. + * @param _proof Merkle proof for allowlist minting. */ - function _payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts) private { + function _payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts, bytes32[] calldata _proof) private { uint256 lastTokenId; uint256 totalCost; uint256 totalAmount; @@ -90,9 +93,11 @@ contract ERC1155Sale is revert SaleInactive(tokenId); } // Use global sale details + requireMerkleProof(gSaleDetails.merkleRoot, _proof, msg.sender); totalCost += gSaleDetails.cost * amount; } else { - // Use token sale price + // Use token sale details + requireMerkleProof(saleDetails.merkleRoot, _proof, msg.sender); totalCost += saleDetails.cost * amount; } totalAmount += amount; @@ -119,14 +124,16 @@ contract ERC1155Sale is * @param tokenIds Token IDs to mint. * @param amounts Amounts of tokens to mint. * @param data Data to pass if receiver is contract. + * @param proof Merkle proof for allowlist minting. * @notice Sale must be active for all tokens. * @dev tokenIds must be sorted ascending without duplicates. + * @dev An empty proof is supplied when no proof is required. */ - function mint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data) + function mint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data, bytes32[] calldata proof) public payable { - _payForActiveMint(tokenIds, amounts); + _payForActiveMint(tokenIds, amounts, proof); _batchMint(to, tokenIds, amounts, data); } @@ -152,6 +159,7 @@ contract ERC1155Sale is * @param paymentTokenAddr The ERC20 token address to accept payment in. address(0) indicates ETH. * @param startTime The start time of the sale. Tokens cannot be minted before this time. * @param endTime The end time of the sale. Tokens cannot be minted after this time. + * @param merkleRoot The merkle root for allowlist minting. * @dev A zero end time indicates an inactive sale. */ function setGlobalSaleDetails( @@ -159,15 +167,16 @@ contract ERC1155Sale is uint256 supplyCap, address paymentTokenAddr, uint64 startTime, - uint64 endTime + uint64 endTime, + bytes32 merkleRoot ) public onlyRole(MINT_ADMIN_ROLE) { _paymentToken = paymentTokenAddr; - _globalSaleDetails = SaleDetails(cost, startTime, endTime); + _globalSaleDetails = SaleDetails(cost, startTime, endTime, merkleRoot); totalSupplyCap = supplyCap; - emit GlobalSaleDetailsUpdated(cost, supplyCap, startTime, endTime); + emit GlobalSaleDetailsUpdated(cost, supplyCap, startTime, endTime, merkleRoot); } /** @@ -177,6 +186,7 @@ contract ERC1155Sale is * @param supplyCap The maximum number of tokens that can be minted. * @param startTime The start time of the sale. Tokens cannot be minted before this time. * @param endTime The end time of the sale. Tokens cannot be minted after this time. + * @param merkleRoot The merkle root for allowlist minting. * @dev A zero end time indicates an inactive sale. * @notice The payment token is set globally. */ @@ -185,14 +195,15 @@ contract ERC1155Sale is uint256 cost, uint256 supplyCap, uint64 startTime, - uint64 endTime + uint64 endTime, + bytes32 merkleRoot ) public onlyRole(MINT_ADMIN_ROLE) { - _tokenSaleDetails[tokenId] = SaleDetails(cost, startTime, endTime); + _tokenSaleDetails[tokenId] = SaleDetails(cost, startTime, endTime, merkleRoot); tokenSupplyCap[tokenId] = supplyCap; - emit TokenSaleDetailsUpdated(tokenId, cost, supplyCap, startTime, endTime); + emit TokenSaleDetailsUpdated(tokenId, cost, supplyCap, startTime, endTime, merkleRoot); } // diff --git a/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol b/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol index f07e905..a262dcc 100644 --- a/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol +++ b/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol @@ -2,13 +2,14 @@ pragma solidity ^0.8.17; interface IERC1155Sale { - event GlobalSaleDetailsUpdated(uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime); - event TokenSaleDetailsUpdated(uint256 tokenId, uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime); + event GlobalSaleDetailsUpdated(uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime, bytes32 merkleRoot); + event TokenSaleDetailsUpdated(uint256 tokenId, uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime, bytes32 merkleRoot); struct SaleDetails { uint256 cost; uint64 startTime; uint64 endTime; // 0 end time indicates sale inactive + bytes32 merkleRoot; // Root of allowed addresses } /** @@ -21,11 +22,11 @@ interface IERC1155Sale { /** * Get token sale details. - * @param _tokenId Token ID to get sale details for. + * @param tokenId Token ID to get sale details for. * @return Sale details. * @notice Token sale details override global sale details. */ - function tokenSaleDetails(uint256 _tokenId) external returns (SaleDetails memory); + function tokenSaleDetails(uint256 tokenId) external returns (SaleDetails memory); /** * Get payment token. @@ -36,13 +37,20 @@ interface IERC1155Sale { /** * Mint tokens. - * @param _to Address to mint tokens to. - * @param _tokenIds Token IDs to mint. - * @param _amounts Amounts of tokens to mint. - * @param _data Data to pass if receiver is contract. + * @param to Address to mint tokens to. + * @param tokenIds Token IDs to mint. + * @param amounts Amounts of tokens to mint. + * @param data Data to pass if receiver is contract. + * @param proof Merkle proof for allowlist minting. * @notice Sale must be active for all tokens. */ - function mint(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) + function mint( + address to, + uint256[] memory tokenIds, + uint256[] memory amounts, + bytes memory data, + bytes32[] calldata proof + ) external payable; } diff --git a/src/tokens/common/MerkleProofSingleUse.sol b/src/tokens/common/MerkleProofSingleUse.sol new file mode 100644 index 0000000..9c893a2 --- /dev/null +++ b/src/tokens/common/MerkleProofSingleUse.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; + +error MerkleProofInvalid(bytes32 root, bytes32[] proof, address account); + +/** + * Require single use merkle proofs per account. + */ +abstract contract MerkleProofSingleUse { + + // Stores proofs used by an account + mapping(address => mapping(bytes32 => bool)) private _proofUsed; + + /** + * Requires the given merkle proof to be valid. + * @param root Merkle root. + * @param proof Merkle proof. + * @param account Account to check. + * @notice Fails when the proof is invalid or the proof has already been claimed by this account. + * @dev This function reverts on failure. + */ + function requireMerkleProof(bytes32 root, bytes32[] calldata proof, address account) internal { + if (root != bytes32(0)) { + if (_proofUsed[account][root] || !checkMerkleProof(root, proof, account)) { + revert MerkleProofInvalid(root, proof, account); + } + _proofUsed[account][root] = true; + } + } + + /** + * Checks if the given merkle proof is valid. + * @param root Merkle root. + * @param proof Merkle proof. + * @param account Account to check. + * @return True if the proof is valid. + */ + function checkMerkleProof(bytes32 root, bytes32[] calldata proof, address account) public pure returns (bool) { + return MerkleProof.verify(proof, root, keccak256(abi.encodePacked(account))); + } + +} diff --git a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol index a7f0e25..847566d 100644 --- a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol @@ -7,8 +7,11 @@ import {ERC1155SaleFactory} from "src/tokens/ERC1155/presets/sale/ERC1155SaleFac import {ERC1155SaleErrors} from "src/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol"; import {ERC1155SupplyErrors} from "src/tokens/ERC1155/extensions/supply/ERC1155SupplyErrors.sol"; +import {Merkle} from "murky/Merkle.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {TestHelper} from "test/tokens/TestHelper.sol"; +import {MerkleProofInvalid} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; // Interfaces import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; @@ -18,7 +21,7 @@ import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol" // solhint-disable no-rely-on-time -contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { +contract ERC1155SaleTest is Test, Merkle, ERC1155SaleErrors, ERC1155SupplyErrors { // Redeclare events event TransferBatch( address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts @@ -28,6 +31,8 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { ERC20Mock private erc20; uint256 private perTokenCost = 0.02 ether; + address private constant ALLOWLIST_ADDR = 0xFA4eE536359087Fba7BD3248EE09e8Cc8347F8Ed; + function setUp() public { token = new ERC1155Sale(); token.initialize(address(this), "test", "ipfs://"); @@ -57,11 +62,11 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { assumeSafe(mintTo, tokenId, amount) withFactory(useFactory) { - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); } // Minting denied when sale is active but not for the token. @@ -71,11 +76,11 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { withFactory(useFactory) withTokenSaleActive(tokenId + 1) { - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); } // Minting denied when token sale is expired. @@ -99,13 +104,13 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { if (block.timestamp >= startTime && block.timestamp <= endTime) { vm.warp(uint256(endTime) + 1); } - token.setTokenSaleDetails(tokenId, perTokenCost, 0, startTime, endTime); + token.setTokenSaleDetails(tokenId, perTokenCost, 0, startTime, endTime, ""); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); } // Minting denied when global sale is expired. @@ -129,13 +134,13 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { if (block.timestamp >= startTime && block.timestamp <= endTime) { vm.warp(uint256(endTime) + 1); } - token.setGlobalSaleDetails(perTokenCost, 0, address(0), startTime, endTime); + token.setGlobalSaleDetails(perTokenCost, 0, address(0), startTime, endTime, ""); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId)); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); } // Minting denied when sale is active but not for all tokens in the group. @@ -153,7 +158,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { amounts[1] = amount; vm.expectRevert(abi.encodeWithSelector(SaleInactive.selector, tokenId + 1)); - token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, ""); + token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); } // Minting denied when global supply exceeded. @@ -175,14 +180,14 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { amount = supplyCap + 1; } token.setGlobalSaleDetails( - perTokenCost, supplyCap, address(0), uint64(block.timestamp), uint64(block.timestamp + 1) + perTokenCost, supplyCap, address(0), uint64(block.timestamp), uint64(block.timestamp + 1), "" ); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap)); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); } // Minting denied when token supply exceeded. @@ -204,14 +209,14 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { amount = supplyCap + 1; } token.setTokenSaleDetails( - tokenId, perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1) + tokenId, perTokenCost, supplyCap, uint64(block.timestamp), uint64(block.timestamp + 1), "" ); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap)); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); } // Minting allowed when sale is active globally. @@ -221,13 +226,13 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { withFactory(useFactory) withGlobalSaleActive { - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); uint256 count = token.balanceOf(mintTo, tokenId); vm.expectEmit(true, true, true, true); emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); assertEq(count + amount, token.balanceOf(mintTo, tokenId)); } @@ -238,13 +243,13 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { withFactory(useFactory) withTokenSaleActive(tokenId) { - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); uint256 count = token.balanceOf(mintTo, tokenId); vm.expectEmit(true, true, true, true); emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, ""); + token.mint{value: amount * perTokenCost}(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); assertEq(count + amount, token.balanceOf(mintTo, tokenId)); } @@ -267,7 +272,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { uint256 count2 = token.balanceOf(mintTo, tokenId + 1); vm.expectEmit(true, true, true, true); emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, ""); + token.mint{value: amount * perTokenCost * 2}(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); assertEq(count + amount, token.balanceOf(mintTo, tokenId)); assertEq(count2 + amount, token.balanceOf(mintTo, tokenId + 1)); } @@ -278,14 +283,14 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { assumeSafe(mintTo, tokenId, amount) withFactory(useFactory) { - token.setGlobalSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1)); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + token.setGlobalSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), ""); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); uint256 count = token.balanceOf(mintTo, tokenId); vm.expectEmit(true, true, true, true); emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint(mintTo, tokenIds, amounts, ""); + token.mint(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); assertEq(count + amount, token.balanceOf(mintTo, tokenId)); } @@ -296,14 +301,14 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { withFactory(useFactory) withGlobalSaleActive { - token.setTokenSaleDetails(tokenId, 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + token.setTokenSaleDetails(tokenId, 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1), ""); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); uint256 count = token.balanceOf(mintTo, tokenId); vm.expectEmit(true, true, true, true); emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint(mintTo, tokenIds, amounts, ""); + token.mint(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); assertEq(count + amount, token.balanceOf(mintTo, tokenId)); } @@ -315,22 +320,139 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { withERC20 { token.setGlobalSaleDetails( - perTokenCost, 0, address(erc20), uint64(block.timestamp - 1), uint64(block.timestamp + 1) + perTokenCost, 0, address(erc20), uint64(block.timestamp - 1), uint64(block.timestamp + 1), "" ); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); uint256 cost = amount * perTokenCost; uint256 balanace = erc20.balanceOf(address(this)); uint256 count = token.balanceOf(mintTo, tokenId); vm.expectEmit(true, true, true, true); emit TransferBatch(address(this), address(0), mintTo, tokenIds, amounts); - token.mint(mintTo, tokenIds, amounts, ""); + token.mint(mintTo, tokenIds, amounts, "", TestHelper.blankProof()); assertEq(count + amount, token.balanceOf(mintTo, tokenId)); assertEq(balanace - cost, erc20.balanceOf(address(this))); assertEq(cost, erc20.balanceOf(address(token))); } + // Minting with merkle success. + function testMerkleSuccess(address[] memory allowlist, uint256 senderIndex, uint256 tokenId, bool globalActive) + public + { + // Construct a merkle tree with the allowlist. + vm.assume(allowlist.length > 1); + vm.assume(senderIndex < allowlist.length); + bytes32[] memory addrs = new bytes32[](allowlist.length); + for (uint256 i = 0; i < allowlist.length; i++) { + addrs[i] = keccak256(abi.encodePacked(allowlist[i])); + } + bytes32 root = getRoot(addrs); + if (globalActive) { + token.setGlobalSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + } else { + token.setTokenSaleDetails(tokenId, 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + } + + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(uint256(1)); + bytes32[] memory proof = getProof(addrs, senderIndex); + + address sender = allowlist[senderIndex]; + vm.prank(sender); + token.mint(sender, tokenIds, amounts, "", proof); + + assertEq(1, token.balanceOf(sender, tokenId)); + } + + // Minting with merkle reuse fail. + function testMerkleReuseFail(address[] memory allowlist, uint256 senderIndex, uint256 tokenId, bool globalActive) + public + { + // Copy of testMerkleSuccess + vm.assume(allowlist.length > 1); + vm.assume(senderIndex < allowlist.length); + bytes32[] memory addrs = new bytes32[](allowlist.length); + for (uint256 i = 0; i < allowlist.length; i++) { + addrs[i] = keccak256(abi.encodePacked(allowlist[i])); + } + bytes32 root = getRoot(addrs); + if (globalActive) { + token.setGlobalSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + } else { + token.setTokenSaleDetails(tokenId, 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + } + + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(uint256(1)); + bytes32[] memory proof = getProof(addrs, senderIndex); + + address sender = allowlist[senderIndex]; + vm.prank(sender); + token.mint(sender, tokenIds, amounts, "", proof); + + assertEq(1, token.balanceOf(sender, tokenId)); + // End copy + + vm.expectRevert(abi.encodeWithSelector(MerkleProofInvalid.selector, root, proof, sender)); + vm.prank(sender); + token.mint(sender, tokenIds, amounts, "", proof); + } + + // Minting with merkle fail no proof. + function testMerkleFailNoProof(address[] memory allowlist, address sender, uint256 tokenId, bool globalActive) + public + { + // Construct a merkle tree with the allowlist. + vm.assume(allowlist.length > 1); + bytes32[] memory addrs = new bytes32[](allowlist.length); + for (uint256 i = 0; i < allowlist.length; i++) { + vm.assume(sender != allowlist[i]); + addrs[i] = keccak256(abi.encodePacked(allowlist[i])); + } + bytes32 root = getRoot(addrs); + if (globalActive) { + token.setGlobalSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + } else { + token.setTokenSaleDetails(tokenId, 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + } + + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(uint256(1)); + bytes32[] memory proof = TestHelper.blankProof(); + + vm.expectRevert(abi.encodeWithSelector(MerkleProofInvalid.selector, root, proof, sender)); + vm.prank(sender); + token.mint(sender, tokenIds, amounts, "", TestHelper.blankProof()); + } + + // Minting with merkle fail bad proof. + function testMerkleFailBadProof(address[] memory allowlist, address sender, uint256 tokenId, bool globalActive) + public + { + // Construct a merkle tree with the allowlist. + vm.assume(allowlist.length > 1); + bytes32[] memory addrs = new bytes32[](allowlist.length); + for (uint256 i = 0; i < allowlist.length; i++) { + vm.assume(sender != allowlist[i]); + addrs[i] = keccak256(abi.encodePacked(allowlist[i])); + } + bytes32 root = getRoot(addrs); + if (globalActive) { + token.setGlobalSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + } else { + token.setTokenSaleDetails(tokenId, 0, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + } + + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(uint256(1)); + bytes32[] memory proof = getProof(addrs, 1); // Wrong sender + + vm.expectRevert(abi.encodeWithSelector(MerkleProofInvalid.selector, root, proof, sender)); + vm.prank(sender); + token.mint(sender, tokenIds, amounts, "", proof); + } + // // Admin minting // @@ -344,8 +466,8 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { vm.assume(minter != address(this)); vm.assume(minter != address(0)); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); vm.expectRevert( abi.encodePacked( @@ -367,8 +489,8 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { { token.grantRole(token.MINT_ADMIN_ROLE(), minter); - uint256[] memory tokenIds = singleToArray(tokenId); - uint256[] memory amounts = singleToArray(amount); + uint256[] memory tokenIds = TestHelper.singleToArray(tokenId); + uint256[] memory amounts = TestHelper.singleToArray(amount); uint256 count = token.balanceOf(mintTo, tokenId); token.mintAdmin(mintTo, tokenIds, amounts, ""); @@ -529,7 +651,7 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { modifier withGlobalSaleActive() { token.setGlobalSaleDetails( - perTokenCost, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1) + perTokenCost, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), "" ); _; } @@ -540,12 +662,8 @@ contract ERC1155SaleTest is Test, ERC1155SaleErrors, ERC1155SupplyErrors { } function setTokenSaleActive(uint256 tokenId) private { - token.setTokenSaleDetails(tokenId, perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1)); - } - - function singleToArray(uint256 value) private pure returns (uint256[] memory) { - uint256[] memory values = new uint256[](1); - values[0] = value; - return values; + token.setTokenSaleDetails( + tokenId, perTokenCost, 0, uint64(block.timestamp - 1), uint64(block.timestamp + 1), "" + ); } } diff --git a/test/tokens/TestHelper.sol b/test/tokens/TestHelper.sol new file mode 100644 index 0000000..8235385 --- /dev/null +++ b/test/tokens/TestHelper.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +library TestHelper { + + function singleToArray(uint256 value) internal pure returns (uint256[] memory) { + uint256[] memory values = new uint256[](1); + values[0] = value; + return values; + } + + function blankProof() internal pure returns (bytes32[] memory) { + return new bytes32[](0); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0476128 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "include": ["./scripts"], + "compilerOptions": { + "module": "esnext", + "lib": ["esnext"], + "importHelpers": true, + "declaration": true, + "sourceMap": true, + "rootDir": "./", + "baseUrl": "./", + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "target": "ES6" + }, + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + } +} diff --git a/yarn.lock b/yarn.lock index 56213ca..6e430d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -447,11 +447,37 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== +"@types/bn.js@^5.1.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" + integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "20.3.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898" + integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw== + "@types/node@^20.1.0": version "20.1.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.0.tgz#258805edc37c327cf706e64c6957f241ca4c4c20" integrity sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A== +"@types/pbkdf2@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" + integrity sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ== + dependencies: + "@types/node" "*" + +"@types/secp256k1@^4.0.1": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c" + integrity sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w== + dependencies: + "@types/node" "*" + acorn-walk@^8.1.1: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" @@ -566,17 +592,44 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + bech32@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +bignumber.js@^9.0.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" + integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== + +blakejs@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" + integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== + +bn.js@4.11.6: + version "4.11.6" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== + bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.2.1: +bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -600,6 +653,52 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +browserify-aes@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + +bs58check@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== + dependencies: + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" + +buffer-reverse@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" + integrity sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -627,6 +726,14 @@ chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -699,6 +806,29 @@ cosmiconfig@^8.0.0: parse-json "^5.0.0" path-type "^4.0.0" +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" @@ -713,6 +843,11 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypto-js@^3.1.9-1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" + integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== + debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -735,7 +870,7 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -elliptic@6.5.4: +elliptic@6.5.4, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -780,6 +915,45 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== +ethereum-bloom-filters@^1.0.6: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz#3ca07f4aed698e75bd134584850260246a5fed8a" + integrity sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA== + dependencies: + js-sha3 "^0.8.0" + +ethereum-cryptography@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" + integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ== + dependencies: + "@types/pbkdf2" "^3.0.0" + "@types/secp256k1" "^4.0.1" + blakejs "^1.1.0" + browserify-aes "^1.2.0" + bs58check "^2.1.2" + create-hash "^1.2.0" + create-hmac "^1.1.7" + hash.js "^1.1.7" + keccak "^3.0.0" + pbkdf2 "^3.0.17" + randombytes "^2.1.0" + safe-buffer "^5.1.2" + scrypt-js "^3.0.0" + secp256k1 "^4.0.1" + setimmediate "^1.0.5" + +ethereumjs-util@^7.1.0: + version "7.1.5" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" + integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + rlp "^2.2.4" + ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" @@ -816,6 +990,22 @@ ethers@^5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +ethjs-unit@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" + integrity sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw== + dependencies: + bn.js "4.11.6" + number-to-bn "1.7.0" + +evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + execa@^7.0.0: version "7.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-7.1.1.tgz#3eb3c83d239488e7b409d48e8813b76bb55c9c43" @@ -884,7 +1074,16 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -911,6 +1110,11 @@ husky@^8.0.3: resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" @@ -937,7 +1141,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@^2.0.4: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -957,6 +1161,11 @@ is-fullwidth-code-point@^4.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== +is-hex-prefixed@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" + integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA== + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -972,7 +1181,7 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -js-sha3@0.8.0: +js-sha3@0.8.0, js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== @@ -1004,6 +1213,24 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +keccak256@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/keccak256/-/keccak256-1.0.6.tgz#dd32fb771558fed51ce4e45a035ae7515573da58" + integrity sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw== + dependencies: + bn.js "^5.2.0" + buffer "^6.0.3" + keccak "^3.0.2" + +keccak@^3.0.0, keccak@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.3.tgz#4bc35ad917be1ef54ff246f904c2bbbf9ac61276" + integrity sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + readable-stream "^3.6.0" + lilconfig@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" @@ -1072,11 +1299,31 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merkletreejs@^0.2.32: + version "0.2.32" + resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.2.32.tgz#cf1c0760e2904e4a1cc269108d6009459fd06223" + integrity sha512-TostQBiwYRIwSE5++jGmacu3ODcKAgqb0Y/pnIohXS7sWxh1gCkSptbmF1a43faehRDpcHf7J/kv0Ml2D/zblQ== + dependencies: + bignumber.js "^9.0.1" + buffer-reverse "^1.0.1" + crypto-js "^3.1.9-1" + treeify "^1.1.0" + web3-utils "^1.3.4" + micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -1117,6 +1364,16 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +node-addon-api@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" + integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== + +node-gyp-build@^4.2.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" + integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== + normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -1129,6 +1386,14 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" +number-to-bn@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" + integrity sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig== + dependencies: + bn.js "4.11.6" + strip-hex-prefix "1.0.0" + object-inspect@^1.12.3: version "1.12.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" @@ -1194,6 +1459,17 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pbkdf2@^3.0.17: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -1219,6 +1495,22 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" @@ -1242,6 +1534,21 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rlp@^2.2.4: + version "2.2.7" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" + integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== + dependencies: + bn.js "^5.2.0" + rxjs@^7.8.0: version "7.8.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" @@ -1249,16 +1556,43 @@ rxjs@^7.8.0: dependencies: tslib "^2.1.0" -scrypt-js@3.0.1: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +scrypt-js@3.0.1, scrypt-js@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== +secp256k1@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" + integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -1350,6 +1684,13 @@ string-width@^5.0.0: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -1369,6 +1710,13 @@ strip-final-newline@^3.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== +strip-hex-prefix@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" + integrity sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A== + dependencies: + is-hex-prefixed "1.0.0" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -1411,6 +1759,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +treeify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" + integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== + ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -1452,11 +1805,34 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +utf8@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== +web3-utils@^1.3.4: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.0.tgz#ca4c1b431a765c14ac7f773e92e0fd9377ccf578" + integrity sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg== + dependencies: + bn.js "^5.2.1" + ethereum-bloom-filters "^1.0.6" + ethereumjs-util "^7.1.0" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + utf8 "3.0.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" From 15abbd74cd7b5de3560543ee9977d58e0244bfa3 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Fri, 30 Jun 2023 13:58:00 +1200 Subject: [PATCH 37/49] Add merkle allowlist to ERC721Sale --- src/tokens/ERC721/presets/sale/ERC721Sale.sol | 21 ++- .../ERC721/presets/sale/IERC721Sale.sol | 11 +- test/tokens/ERC721/presets/ERC721Sale.t.sol | 126 +++++++++++++++--- 3 files changed, 129 insertions(+), 29 deletions(-) diff --git a/src/tokens/ERC721/presets/sale/ERC721Sale.sol b/src/tokens/ERC721/presets/sale/ERC721Sale.sol index f040745..55ca2da 100644 --- a/src/tokens/ERC721/presets/sale/ERC721Sale.sol +++ b/src/tokens/ERC721/presets/sale/ERC721Sale.sol @@ -5,8 +5,9 @@ import {IERC721Sale} from "@0xsequence/contracts-library/tokens/ERC721/presets/s import {ERC721SaleErrors} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/ERC721SaleErrors.sol"; import {ERC721Token} from "@0xsequence/contracts-library/tokens/ERC721/ERC721Token.sol"; import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; +import {MerkleProofSingleUse} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; -contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors, WithdrawControlled { +contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors, WithdrawControlled, MerkleProofSingleUse { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); bytes4 private constant _ERC20_TRANSFERFROM_SELECTOR = @@ -54,12 +55,14 @@ contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors, WithdrawContr /** * Checks the sale is active and takes payment. * @param _amount Amount of tokens to mint. + * @param _proof Merkle proof for allowlist minting. */ - function _payForActiveMint(uint256 _amount) private { + function _payForActiveMint(uint256 _amount, bytes32[] calldata _proof) private { // Active sale test if (blockTimeOutOfBounds(_saleDetails.startTime, _saleDetails.endTime)) { revert SaleInactive(); } + requireMerkleProof(_saleDetails.merkleRoot, _proof, msg.sender); uint256 total = _saleDetails.cost * _amount; address paymentToken = _saleDetails.paymentToken; @@ -82,15 +85,17 @@ contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors, WithdrawContr * Mint tokens. * @param to Address to mint tokens to. * @param amount Amount of tokens to mint. + * @param proof Merkle proof for allowlist minting. * @notice Sale must be active for all tokens. + * @dev An empty proof is supplied when no proof is required. */ - function mint(address to, uint256 amount) public payable { + function mint(address to, uint256 amount, bytes32[] calldata proof) public payable { uint256 currentSupply = totalSupply(); uint256 supplyCap = _saleDetails.supplyCap; if (supplyCap > 0 && currentSupply + amount > supplyCap) { revert InsufficientSupply(currentSupply, amount, supplyCap); } - _payForActiveMint(amount); + _payForActiveMint(amount, proof); _mint(to, amount); } @@ -111,6 +116,7 @@ contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors, WithdrawContr * @param paymentToken The ERC20 token address to accept payment in. address(0) indicates ETH. * @param startTime The start time of the sale. Tokens cannot be minted before this time. * @param endTime The end time of the sale. Tokens cannot be minted after this time. + * @param merkleRoot The merkle root for allowlist minting. * @dev A zero end time indicates an inactive sale. */ function setSaleDetails( @@ -118,13 +124,14 @@ contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors, WithdrawContr uint256 cost, address paymentToken, uint64 startTime, - uint64 endTime + uint64 endTime, + bytes32 merkleRoot ) public onlyRole(MINT_ADMIN_ROLE) { - _saleDetails = SaleDetails(supplyCap, cost, paymentToken, startTime, endTime); - emit SaleDetailsUpdated(supplyCap, cost, paymentToken, startTime, endTime); + _saleDetails = SaleDetails(supplyCap, cost, paymentToken, startTime, endTime, merkleRoot); + emit SaleDetailsUpdated(supplyCap, cost, paymentToken, startTime, endTime, merkleRoot); } // diff --git a/src/tokens/ERC721/presets/sale/IERC721Sale.sol b/src/tokens/ERC721/presets/sale/IERC721Sale.sol index 1da6651..1c555e8 100644 --- a/src/tokens/ERC721/presets/sale/IERC721Sale.sol +++ b/src/tokens/ERC721/presets/sale/IERC721Sale.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; interface IERC721Sale { - event SaleDetailsUpdated(uint256 supplyCap, uint256 cost, address paymentToken, uint64 startTime, uint64 endTime); + event SaleDetailsUpdated(uint256 supplyCap, uint256 cost, address paymentToken, uint64 startTime, uint64 endTime, bytes32 merkleRoot); struct SaleDetails { uint256 supplyCap; // 0 supply cap indicates unlimited supply @@ -10,6 +10,7 @@ interface IERC721Sale { address paymentToken; // ERC20 token address for payment. address(0) indicated payment in ETH. uint64 startTime; uint64 endTime; // 0 end time indicates sale inactive + bytes32 merkleRoot; // Root of allowed addresses } /** @@ -20,9 +21,11 @@ interface IERC721Sale { /** * Mint tokens. - * @param _to Address to mint tokens to. - * @param _amount Amount of tokens to mint. + * @param to Address to mint tokens to. + * @param amount Amount of tokens to mint. + * @param proof Merkle proof for allowlist minting. * @notice Sale must be active for all tokens. + * @dev An empty proof is supplied when no proof is required. */ - function mint(address _to, uint256 _amount) external payable; + function mint(address to, uint256 amount, bytes32[] memory proof) external payable; } diff --git a/test/tokens/ERC721/presets/ERC721Sale.t.sol b/test/tokens/ERC721/presets/ERC721Sale.t.sol index 6768e72..46bce17 100644 --- a/test/tokens/ERC721/presets/ERC721Sale.t.sol +++ b/test/tokens/ERC721/presets/ERC721Sale.t.sol @@ -6,8 +6,11 @@ import {ERC721Sale} from "src/tokens/ERC721/presets/sale/ERC721Sale.sol"; import {ERC721SaleErrors} from "src/tokens/ERC721/presets/sale/ERC721SaleErrors.sol"; import {ERC721SaleFactory} from "src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol"; +import {Merkle} from "murky/Merkle.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {TestHelper} from "test/tokens/TestHelper.sol"; +import {MerkleProofInvalid} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; // Interfaces import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; @@ -18,7 +21,7 @@ import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol" // solhint-disable no-rely-on-time -contract ERC721SaleTest is Test, ERC721SaleErrors { +contract ERC721SaleTest is Test, Merkle, ERC721SaleErrors { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); @@ -57,7 +60,7 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { withFactory(useFactory) { vm.expectRevert(SaleInactive.selector); - token.mint{value: amount * perTokenCost}(mintTo, amount); + token.mint{value: amount * perTokenCost}(mintTo, amount, TestHelper.blankProof()); } // Minting denied when sale is expired. @@ -74,10 +77,10 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { if (block.timestamp >= startTime && block.timestamp <= endTime) { vm.warp(uint256(endTime) + 1); } - token.setSaleDetails(0, perTokenCost, address(0), uint64(startTime), uint64(endTime)); + token.setSaleDetails(0, perTokenCost, address(0), uint64(startTime), uint64(endTime), ""); vm.expectRevert(SaleInactive.selector); - token.mint{value: amount * perTokenCost}(mintTo, amount); + token.mint{value: amount * perTokenCost}(mintTo, amount, TestHelper.blankProof()); } // Minting denied when supply exceeded. @@ -92,10 +95,10 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { if (amount <= supplyCap) { amount = supplyCap + 1; } - token.setSaleDetails(supplyCap, perTokenCost, address(0), uint64(block.timestamp), uint64(block.timestamp + 1)); + token.setSaleDetails(supplyCap, perTokenCost, address(0), uint64(block.timestamp), uint64(block.timestamp + 1), ""); vm.expectRevert(abi.encodeWithSelector(InsufficientSupply.selector, 0, amount, supplyCap)); - token.mint{value: amount * perTokenCost}(mintTo, amount); + token.mint{value: amount * perTokenCost}(mintTo, amount, TestHelper.blankProof()); } // Minting allowed when sale is active. @@ -108,7 +111,7 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { uint256 count = token.balanceOf(mintTo); vm.expectEmit(true, true, true, true, address(token)); emit Transfer(address(0), mintTo, 0); - token.mint{value: amount * perTokenCost}(mintTo, amount); + token.mint{value: amount * perTokenCost}(mintTo, amount, TestHelper.blankProof()); assertEq(count + amount, token.balanceOf(mintTo)); } @@ -118,12 +121,12 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { assumeSafe(mintTo, amount) withFactory(useFactory) { - token.setSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), ""); uint256 count = token.balanceOf(mintTo); vm.expectEmit(true, true, true, true, address(token)); emit Transfer(address(0), mintTo, 0); - token.mint(mintTo, amount); + token.mint(mintTo, amount, TestHelper.blankProof()); assertEq(count + amount, token.balanceOf(mintTo)); } @@ -134,19 +137,112 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { withFactory(useFactory) withERC20 { - token.setSaleDetails(0, perTokenCost, address(erc20), uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setSaleDetails(0, perTokenCost, address(erc20), uint64(block.timestamp - 1), uint64(block.timestamp + 1), ""); uint256 cost = amount * perTokenCost; uint256 balanace = erc20.balanceOf(address(this)); uint256 count = token.balanceOf(mintTo); vm.expectEmit(true, true, true, true, address(token)); emit Transfer(address(0), mintTo, 0); - token.mint(mintTo, amount); + token.mint(mintTo, amount, TestHelper.blankProof()); assertEq(count + amount, token.balanceOf(mintTo)); assertEq(balanace - cost, erc20.balanceOf(address(this))); assertEq(cost, erc20.balanceOf(address(token))); } + // Minting with merkle success. + function testMerkleSuccess(address[] memory allowlist, uint256 senderIndex) + public + { + // Construct a merkle tree with the allowlist. + vm.assume(allowlist.length > 1); + vm.assume(senderIndex < allowlist.length); + bytes32[] memory addrs = new bytes32[](allowlist.length); + for (uint256 i = 0; i < allowlist.length; i++) { + addrs[i] = keccak256(abi.encodePacked(allowlist[i])); + } + bytes32 root = getRoot(addrs); + token.setSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + + bytes32[] memory proof = getProof(addrs, senderIndex); + + address sender = allowlist[senderIndex]; + vm.prank(sender); + token.mint(sender, 1, proof); + + assertEq(1, token.balanceOf(sender)); + } + + // Minting with merkle reuse fail. + function testMerkleReuseFail(address[] memory allowlist, uint256 senderIndex) + public + { + // Copy of testMerkleSuccess + vm.assume(allowlist.length > 1); + vm.assume(senderIndex < allowlist.length); + bytes32[] memory addrs = new bytes32[](allowlist.length); + for (uint256 i = 0; i < allowlist.length; i++) { + addrs[i] = keccak256(abi.encodePacked(allowlist[i])); + } + bytes32 root = getRoot(addrs); + token.setSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + + bytes32[] memory proof = getProof(addrs, senderIndex); + + address sender = allowlist[senderIndex]; + vm.prank(sender); + token.mint(sender, 1, proof); + + assertEq(1, token.balanceOf(sender)); + // End copy + + vm.expectRevert(abi.encodeWithSelector(MerkleProofInvalid.selector, root, proof, sender)); + vm.prank(sender); + token.mint(sender, 1, proof); + } + + // Minting with merkle fail no proof. + function testMerkleFailNoProof(address[] memory allowlist, address sender) + public + { + // Construct a merkle tree with the allowlist. + vm.assume(allowlist.length > 1); + bytes32[] memory addrs = new bytes32[](allowlist.length); + for (uint256 i = 0; i < allowlist.length; i++) { + vm.assume(sender != allowlist[i]); + addrs[i] = keccak256(abi.encodePacked(allowlist[i])); + } + bytes32 root = getRoot(addrs); + token.setSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + + bytes32[] memory proof = TestHelper.blankProof(); + + vm.expectRevert(abi.encodeWithSelector(MerkleProofInvalid.selector, root, proof, sender)); + vm.prank(sender); + token.mint(sender, 1, TestHelper.blankProof()); + } + + // Minting with merkle fail bad proof. + function testMerkleFailBadProof(address[] memory allowlist, address sender, uint256 tokenId) + public + { + // Construct a merkle tree with the allowlist. + vm.assume(allowlist.length > 1); + bytes32[] memory addrs = new bytes32[](allowlist.length); + for (uint256 i = 0; i < allowlist.length; i++) { + vm.assume(sender != allowlist[i]); + addrs[i] = keccak256(abi.encodePacked(allowlist[i])); + } + bytes32 root = getRoot(addrs); + token.setSaleDetails(0, 0, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), root); + + bytes32[] memory proof = getProof(addrs, 1); // Wrong sender + + vm.expectRevert(abi.encodeWithSelector(MerkleProofInvalid.selector, root, proof, sender)); + vm.prank(sender); + token.mint(sender, 1, proof); + } + // // Admin minting // @@ -288,13 +384,7 @@ contract ERC721SaleTest is Test, ERC721SaleErrors { } modifier withSaleActive() { - token.setSaleDetails(0, perTokenCost, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1)); + token.setSaleDetails(0, perTokenCost, address(0), uint64(block.timestamp - 1), uint64(block.timestamp + 1), ""); _; } - - function singleToArray(uint256 value) private pure returns (uint256[] memory) { - uint256[] memory values = new uint256[](1); - values[0] = value; - return values; - } } From eeda959c69c199330c01c33caee54c9a690e4ecb Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Fri, 30 Jun 2023 15:13:36 +1200 Subject: [PATCH 38/49] Increase default fuzz runs --- foundry.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/foundry.toml b/foundry.toml index c8a7cb1..c719c2d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,3 +5,6 @@ libs = ['lib'] solc = "0.8.17" via_ir = true optimizer-runs = 20_000 + +[profile.default.fuzz] +runs = 1_024 From f2ff1c0dd1e92aadc77fd6b57fe7c4a30299f19c Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Mon, 3 Jul 2023 08:18:20 +1200 Subject: [PATCH 39/49] Update interface pattern --- src/proxies/ERC1967/IProxyDeployer.sol | 8 ++++ src/proxies/ERC1967/ProxyDeployer.sol | 4 +- src/proxies/ERC1967/ProxyDeployerErrors.sol | 10 ---- .../extensions/supply/ERC1155Supply.sol | 4 +- ...155SupplyErrors.sol => IERC1155Supply.sol} | 5 +- .../presets/minter/ERC1155TokenMinter.sol | 4 +- .../presets/minter/IERC1155TokenMinter.sol} | 5 +- .../minter/IERC1155TokenMinterFactory.sol | 18 +++++--- .../ERC1155/presets/sale/ERC1155Sale.sol | 2 - .../presets/sale/ERC1155SaleErrors.sol | 38 --------------- .../ERC1155/presets/sale/IERC1155Sale.sol | 46 +++++++++++++++++-- .../presets/sale/IERC1155SaleFactory.sol | 18 +++++--- .../ERC20/presets/minter/ERC20TokenMinter.sol | 4 +- .../presets/minter/IERC20TokenMinter.sol} | 5 +- .../minter/IERC20TokenMinterFactory.sol | 17 ++++--- .../presets/minter/ERC721TokenMinter.sol | 4 +- .../presets/minter/IERC721TokenMinter.sol} | 5 +- .../minter/IERC721TokenMinterFactory.sol | 17 ++++--- src/tokens/ERC721/presets/sale/ERC721Sale.sol | 3 +- .../ERC721/presets/sale/ERC721SaleErrors.sol | 35 -------------- .../ERC721/presets/sale/IERC721Sale.sol | 39 +++++++++++++++- .../presets/sale/IERC721SaleFactory.sol | 18 +++++--- test/tokens/ERC1155/presets/ERC1155Sale.t.sol | 6 +-- .../ERC1155/presets/ERC1155TokenMinter.t.sol | 4 +- test/tokens/ERC20/ERC20TokenMinter.t.sol | 4 +- test/tokens/ERC721/presets/ERC721Sale.t.sol | 6 +-- .../ERC721/presets/ERC721TokenMinter.t.sol | 4 +- 27 files changed, 181 insertions(+), 152 deletions(-) create mode 100644 src/proxies/ERC1967/IProxyDeployer.sol delete mode 100644 src/proxies/ERC1967/ProxyDeployerErrors.sol rename src/tokens/ERC1155/extensions/supply/{ERC1155SupplyErrors.sol => IERC1155Supply.sol} (77%) rename src/tokens/{ERC20/presets/minter/ERC20TokenMinterErrors.sol => ERC1155/presets/minter/IERC1155TokenMinter.sol} (60%) delete mode 100644 src/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol rename src/tokens/{ERC721/presets/minter/ERC721TokenMinterErrors.sol => ERC20/presets/minter/IERC20TokenMinter.sol} (61%) rename src/tokens/{ERC1155/presets/minter/ERC1155TokenMinterErrors.sol => ERC721/presets/minter/IERC721TokenMinter.sol} (60%) delete mode 100644 src/tokens/ERC721/presets/sale/ERC721SaleErrors.sol diff --git a/src/proxies/ERC1967/IProxyDeployer.sol b/src/proxies/ERC1967/IProxyDeployer.sol new file mode 100644 index 0000000..7512878 --- /dev/null +++ b/src/proxies/ERC1967/IProxyDeployer.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IProxyDeployerSignals { + error ProxyCreationFailed(); +} + +interface IProxyDeployer is IProxyDeployerSignals {} diff --git a/src/proxies/ERC1967/ProxyDeployer.sol b/src/proxies/ERC1967/ProxyDeployer.sol index f2aa6e8..7fb5c83 100644 --- a/src/proxies/ERC1967/ProxyDeployer.sol +++ b/src/proxies/ERC1967/ProxyDeployer.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ProxyDeployerErrors} from "./ProxyDeployerErrors.sol"; +import {IProxyDeployer} from "./IProxyDeployer.sol"; import {Proxy} from "./Proxy.sol"; -abstract contract ProxyDeployer is ProxyDeployerErrors { +abstract contract ProxyDeployer is IProxyDeployer { /** * Creates a proxy contract for a given implementation diff --git a/src/proxies/ERC1967/ProxyDeployerErrors.sol b/src/proxies/ERC1967/ProxyDeployerErrors.sol deleted file mode 100644 index cdab2c9..0000000 --- a/src/proxies/ERC1967/ProxyDeployerErrors.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -/** - * Errors for the Proxy Deployer contract. - */ -abstract contract ProxyDeployerErrors { - // Factories - error ProxyCreationFailed(); -} diff --git a/src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol b/src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol index f5d9d74..f969035 100644 --- a/src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol +++ b/src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.17; import {ERC1155Token} from "@0xsequence/contracts-library/tokens/ERC1155/ERC1155Token.sol"; -import {ERC1155SupplyErrors} from "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/ERC1155SupplyErrors.sol"; +import {IERC1155Supply} from "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/IERC1155Supply.sol"; /** * An ERC1155 extension that tracks token supply. */ -abstract contract ERC1155Supply is ERC1155Token, ERC1155SupplyErrors { +abstract contract ERC1155Supply is ERC1155Token, IERC1155Supply { // Maximum supply globaly and per token. 0 indicates unlimited supply uint256 internal totalSupplyCap; mapping(uint256 => uint256) internal tokenSupplyCap; diff --git a/src/tokens/ERC1155/extensions/supply/ERC1155SupplyErrors.sol b/src/tokens/ERC1155/extensions/supply/IERC1155Supply.sol similarity index 77% rename from src/tokens/ERC1155/extensions/supply/ERC1155SupplyErrors.sol rename to src/tokens/ERC1155/extensions/supply/IERC1155Supply.sol index d33bcef..365383d 100644 --- a/src/tokens/ERC1155/extensions/supply/ERC1155SupplyErrors.sol +++ b/src/tokens/ERC1155/extensions/supply/IERC1155Supply.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -// A contract for errors for extensibility. -abstract contract ERC1155SupplyErrors { +interface IERC1155SupplySignals { /** * Insufficient supply of tokens. @@ -14,3 +13,5 @@ abstract contract ERC1155SupplyErrors { */ error InvalidArrayLength(); } + +interface IERC1155Supply is IERC1155SupplySignals {} diff --git a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol index b5639d1..920a374 100644 --- a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol +++ b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.17; import {ERC1155MintBurn, ERC1155} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155MintBurn.sol"; -import {ERC1155TokenMinterErrors} from "@0xsequence/contracts-library/tokens/ERC1155//presets/minter/ERC1155TokenMinterErrors.sol"; +import {IERC1155TokenMinter} from "@0xsequence/contracts-library/tokens/ERC1155//presets/minter/IERC1155TokenMinter.sol"; import {ERC1155Token} from "@0xsequence/contracts-library/tokens/ERC1155/ERC1155Token.sol"; import {ERC2981Controlled} from "@0xsequence/contracts-library/tokens/common/ERC2981Controlled.sol"; /** * A ready made implementation of ERC-1155 capable of minting when role provided. */ -contract ERC1155TokenMinter is ERC1155MintBurn, ERC1155Token, ERC1155TokenMinterErrors { +contract ERC1155TokenMinter is ERC1155MintBurn, ERC1155Token, IERC1155TokenMinter { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); address private immutable initializer; diff --git a/src/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinter.sol similarity index 60% rename from src/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol rename to src/tokens/ERC1155/presets/minter/IERC1155TokenMinter.sol index 82aece9..1ae520d 100644 --- a/src/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol +++ b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinter.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -// A contract for errors for extensibility. -abstract contract ERC20TokenMinterErrors { +interface IERC1155TokenMinterSignals { /** * Invalid initialization error. */ error InvalidInitialization(); } + +interface IERC1155TokenMinter is IERC1155TokenMinterSignals {} diff --git a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol index 50af26d..28d212e 100644 --- a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol +++ b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol @@ -1,12 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -interface IERC1155TokenMinterFactory { - /** - * Event emitted when a new ERC-1155 Token Minter proxy contract is deployed. - * @param proxyAddr The address of the deployed proxy. - */ - event ERC1155TokenMinterDeployed(address proxyAddr); +interface IERC1155TokenMinterFactoryFunctions { /** * Creates an ERC-1155 Token Minter proxy. @@ -20,3 +15,14 @@ interface IERC1155TokenMinterFactory { external returns (address proxyAddr); } + +interface IERC1155TokenMinterFactorySignals { + /** + * Event emitted when a new ERC-1155 Token Minter proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC1155TokenMinterDeployed(address proxyAddr); + +} + +interface IERC1155TokenMinterFactory is IERC1155TokenMinterFactoryFunctions, IERC1155TokenMinterFactorySignals {} diff --git a/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol b/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol index 3810061..60db8f3 100644 --- a/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol +++ b/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol @@ -2,14 +2,12 @@ pragma solidity ^0.8.17; import {IERC1155Sale} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/IERC1155Sale.sol"; -import {ERC1155SaleErrors} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol"; import {ERC1155Supply, ERC1155Token} from "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/ERC1155Supply.sol"; import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; import {MerkleProofSingleUse} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; contract ERC1155Sale is IERC1155Sale, - ERC1155SaleErrors, ERC1155Supply, WithdrawControlled, MerkleProofSingleUse diff --git a/src/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol b/src/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol deleted file mode 100644 index 6c3d82b..0000000 --- a/src/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -// A contract for errors for extensibility. -abstract contract ERC1155SaleErrors { - /** - * Contract already initialized. - */ - error InvalidInitialization(); - - /** - * Sale is not active globally. - */ - error GlobalSaleInactive(); - - /** - * Sale is not active. - * @param tokenId Invalid Token ID. - */ - error SaleInactive(uint256 tokenId); - - /** - * Insufficient tokens for payment. - * @param expected Expected amount of tokens. - * @param actual Actual amount of tokens. - */ - error InsufficientPayment(uint256 expected, uint256 actual); - - /** - * Invalid token IDs. - */ - error InvalidTokenIds(); - - /** - * Withdraw failed. - */ - error WithdrawFailed(); -} diff --git a/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol b/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol index a262dcc..eb4125d 100644 --- a/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol +++ b/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -interface IERC1155Sale { - event GlobalSaleDetailsUpdated(uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime, bytes32 merkleRoot); - event TokenSaleDetailsUpdated(uint256 tokenId, uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime, bytes32 merkleRoot); +interface IERC1155SaleFunctions { struct SaleDetails { uint256 cost; @@ -54,3 +52,45 @@ interface IERC1155Sale { external payable; } + +// Events and errors are declared separately for test inheritance. +interface IERC1155SaleSignals { + + event GlobalSaleDetailsUpdated(uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime, bytes32 merkleRoot); + event TokenSaleDetailsUpdated(uint256 tokenId, uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime, bytes32 merkleRoot); + + /** + * Contract already initialized. + */ + error InvalidInitialization(); + + /** + * Sale is not active globally. + */ + error GlobalSaleInactive(); + + /** + * Sale is not active. + * @param tokenId Invalid Token ID. + */ + error SaleInactive(uint256 tokenId); + + /** + * Insufficient tokens for payment. + * @param expected Expected amount of tokens. + * @param actual Actual amount of tokens. + */ + error InsufficientPayment(uint256 expected, uint256 actual); + + /** + * Invalid token IDs. + */ + error InvalidTokenIds(); + + /** + * Withdraw failed. + */ + error WithdrawFailed(); +} + +interface IERC1155Sale is IERC1155SaleFunctions, IERC1155SaleSignals {} diff --git a/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol index 724c286..29b07de 100644 --- a/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol @@ -1,12 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -interface IERC1155SaleFactory { - /** - * Event emitted when a new ERC-1155 Sale proxy contract is deployed. - * @param proxyAddr The address of the deployed proxy. - */ - event ERC1155SaleDeployed(address proxyAddr); +interface IERC1155SaleFactoryFunctions { /** * Creates an ERC-1155 Sale proxy contract @@ -20,3 +15,14 @@ interface IERC1155SaleFactory { external returns (address proxyAddr); } + +interface IERC1155SaleFactorySignals { + /** + * Event emitted when a new ERC-1155 Sale proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC1155SaleDeployed(address proxyAddr); + +} + +interface IERC1155SaleFactory is IERC1155SaleFactoryFunctions, IERC1155SaleFactorySignals {} diff --git a/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol b/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol index 125231c..fa2216d 100644 --- a/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol +++ b/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.17; import {ERC20Token} from "@0xsequence/contracts-library/tokens/ERC20/ERC20Token.sol"; -import {ERC20TokenMinterErrors} from "@0xsequence/contracts-library/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol"; +import {IERC20TokenMinter} from "@0xsequence/contracts-library/tokens/ERC20/presets/minter/IERC20TokenMinter.sol"; /** * A ready made implementation of ERC-20 capable of minting when role provided. */ -contract ERC20TokenMinter is ERC20Token, ERC20TokenMinterErrors { +contract ERC20TokenMinter is ERC20Token, IERC20TokenMinter { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); address private immutable _initializer; diff --git a/src/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol b/src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol similarity index 61% rename from src/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol rename to src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol index f5a4baa..41c2ef4 100644 --- a/src/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol +++ b/src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -// A contract for errors for extensibility. -abstract contract ERC721TokenMinterErrors { +interface IERC20TokenMinterSignals { /** * Invalid initialization error. */ error InvalidInitialization(); } + +interface IERC20TokenMinter is IERC20TokenMinterSignals {} diff --git a/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol b/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol index 58aa642..0183ce4 100644 --- a/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol +++ b/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol @@ -1,12 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -interface IERC20TokenMinterFactory { - /** - * Event emitted when a new ERC-20 Token Minter proxy contract is deployed. - * @param proxyAddr The address of the deployed proxy. - */ - event ERC20TokenMinterDeployed(address proxyAddr); +interface IERC20TokenMinterFactoryFunctions { /** * Creates an ERC-20 Token Minter proxy. @@ -21,3 +16,13 @@ interface IERC20TokenMinterFactory { external returns (address proxyAddr); } + +interface IERC20TokenMinterFactorySignals { + /** + * Event emitted when a new ERC-20 Token Minter proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC20TokenMinterDeployed(address proxyAddr); +} + +interface IERC20TokenMinterFactory is IERC20TokenMinterFactoryFunctions, IERC20TokenMinterFactorySignals {} diff --git a/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol index d7a1222..24d4daa 100644 --- a/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.17; import {ERC721Token} from "@0xsequence/contracts-library/tokens/ERC721/ERC721Token.sol"; -import {ERC721TokenMinterErrors} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol"; +import {IERC721TokenMinter} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/IERC721TokenMinter.sol"; /** * A ready made implementation of ERC-721 with role based minting. */ -contract ERC721TokenMinter is ERC721Token, ERC721TokenMinterErrors { +contract ERC721TokenMinter is ERC721Token, IERC721TokenMinter { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); address private immutable _initializer; diff --git a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterErrors.sol b/src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol similarity index 60% rename from src/tokens/ERC1155/presets/minter/ERC1155TokenMinterErrors.sol rename to src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol index 0a60e7e..fd8a156 100644 --- a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterErrors.sol +++ b/src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -// A contract for errors for extensibility. -abstract contract ERC1155TokenMinterErrors { +interface IERC721TokenMinterSignals { /** * Invalid initialization error. */ error InvalidInitialization(); } + +interface IERC721TokenMinter is IERC721TokenMinterSignals {} diff --git a/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol index ad7bce9..bef5a80 100644 --- a/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol +++ b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol @@ -1,12 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -interface IERC721TokenMinterFactory { - /** - * Event emitted when a new ERC-721 Token Minter proxy contract is deployed. - * @param proxyAddr The address of the deployed proxy. - */ - event ERC721TokenMinterDeployed(address proxyAddr); +interface IERC721TokenMinterFactoryFunctions { /** * Creates an ERC-721 Token Minter proxy. @@ -21,3 +16,13 @@ interface IERC721TokenMinterFactory { external returns (address proxyAddr); } + +interface IERC721TokenMinterFactorySignalss { + /** + * Event emitted when a new ERC-721 Token Minter proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC721TokenMinterDeployed(address proxyAddr); +} + +interface IERC721TokenMinterFactory is IERC721TokenMinterFactoryFunctions, IERC721TokenMinterFactorySignalss {} diff --git a/src/tokens/ERC721/presets/sale/ERC721Sale.sol b/src/tokens/ERC721/presets/sale/ERC721Sale.sol index 55ca2da..84b26fb 100644 --- a/src/tokens/ERC721/presets/sale/ERC721Sale.sol +++ b/src/tokens/ERC721/presets/sale/ERC721Sale.sol @@ -2,12 +2,11 @@ pragma solidity ^0.8.17; import {IERC721Sale} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/IERC721Sale.sol"; -import {ERC721SaleErrors} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/ERC721SaleErrors.sol"; import {ERC721Token} from "@0xsequence/contracts-library/tokens/ERC721/ERC721Token.sol"; import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; import {MerkleProofSingleUse} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; -contract ERC721Sale is IERC721Sale, ERC721Token, ERC721SaleErrors, WithdrawControlled, MerkleProofSingleUse { +contract ERC721Sale is IERC721Sale, ERC721Token, WithdrawControlled, MerkleProofSingleUse { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); bytes4 private constant _ERC20_TRANSFERFROM_SELECTOR = diff --git a/src/tokens/ERC721/presets/sale/ERC721SaleErrors.sol b/src/tokens/ERC721/presets/sale/ERC721SaleErrors.sol deleted file mode 100644 index 5f837d9..0000000 --- a/src/tokens/ERC721/presets/sale/ERC721SaleErrors.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -// A contract for errors for extensibility. -abstract contract ERC721SaleErrors { - /** - * Contract already initialized. - */ - error InvalidInitialization(); - - /** - * Sale is not active. - */ - error SaleInactive(); - - /** - * Insufficient supply. - * @param currentSupply Current supply. - * @param amount Amount to mint. - * @param maxSupply Maximum supply. - */ - error InsufficientSupply(uint256 currentSupply, uint256 amount, uint256 maxSupply); - - /** - * Insufficient tokens for payment. - * @param expected Expected amount of tokens. - * @param actual Actual amount of tokens. - */ - error InsufficientPayment(uint256 expected, uint256 actual); - - /** - * Withdraw failed. - */ - error WithdrawFailed(); -} diff --git a/src/tokens/ERC721/presets/sale/IERC721Sale.sol b/src/tokens/ERC721/presets/sale/IERC721Sale.sol index 1c555e8..54566d3 100644 --- a/src/tokens/ERC721/presets/sale/IERC721Sale.sol +++ b/src/tokens/ERC721/presets/sale/IERC721Sale.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -interface IERC721Sale { - event SaleDetailsUpdated(uint256 supplyCap, uint256 cost, address paymentToken, uint64 startTime, uint64 endTime, bytes32 merkleRoot); +interface IERC721SaleFunctions { struct SaleDetails { uint256 supplyCap; // 0 supply cap indicates unlimited supply @@ -29,3 +28,39 @@ interface IERC721Sale { */ function mint(address to, uint256 amount, bytes32[] memory proof) external payable; } + +interface IERC721SaleSignals { + event SaleDetailsUpdated(uint256 supplyCap, uint256 cost, address paymentToken, uint64 startTime, uint64 endTime, bytes32 merkleRoot); + + /** + * Contract already initialized. + */ + error InvalidInitialization(); + + /** + * Sale is not active. + */ + error SaleInactive(); + + /** + * Insufficient supply. + * @param currentSupply Current supply. + * @param amount Amount to mint. + * @param maxSupply Maximum supply. + */ + error InsufficientSupply(uint256 currentSupply, uint256 amount, uint256 maxSupply); + + /** + * Insufficient tokens for payment. + * @param expected Expected amount of tokens. + * @param actual Actual amount of tokens. + */ + error InsufficientPayment(uint256 expected, uint256 actual); + + /** + * Withdraw failed. + */ + error WithdrawFailed(); +} + +interface IERC721Sale is IERC721SaleFunctions, IERC721SaleSignals {} diff --git a/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol index 7c1f7cd..4f15b10 100644 --- a/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol +++ b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol @@ -1,12 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -interface IERC721SaleFactory { - /** - * Event emitted when a new ERC-721 Sale proxy contract is deployed. - * @param proxyAddr The address of the deployed proxy. - */ - event ERC721SaleDeployed(address proxyAddr); +interface IERC721SaleFactoryFunctions { /** * Creates an ERC-721 Floor Wrapper for given token contract @@ -27,3 +22,14 @@ interface IERC721SaleFactory { external returns (address proxyAddr); } + + +interface IERC721SaleFactorySignals { + /** + * Event emitted when a new ERC-721 Sale proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC721SaleDeployed(address proxyAddr); +} + +interface IERC721SaleFactory is IERC721SaleFactoryFunctions, IERC721SaleFactorySignals {} diff --git a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol index 847566d..84d6c60 100644 --- a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; +import {IERC1155SaleSignals} from "src/tokens/ERC1155/presets/sale/IERC1155Sale.sol"; import {ERC1155Sale} from "src/tokens/ERC1155/presets/sale/ERC1155Sale.sol"; import {ERC1155SaleFactory} from "src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol"; -import {ERC1155SaleErrors} from "src/tokens/ERC1155/presets/sale/ERC1155SaleErrors.sol"; -import {ERC1155SupplyErrors} from "src/tokens/ERC1155/extensions/supply/ERC1155SupplyErrors.sol"; +import {IERC1155SupplySignals} from "src/tokens/ERC1155/extensions/supply/IERC1155Supply.sol"; import {Merkle} from "murky/Merkle.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; @@ -21,7 +21,7 @@ import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol" // solhint-disable no-rely-on-time -contract ERC1155SaleTest is Test, Merkle, ERC1155SaleErrors, ERC1155SupplyErrors { +contract ERC1155SaleTest is Test, Merkle, IERC1155SaleSignals, IERC1155SupplySignals { // Redeclare events event TransferBatch( address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts diff --git a/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol b/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol index 3f326f2..930dd80 100644 --- a/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {ERC1155TokenMinter} from "src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol"; -import {ERC1155TokenMinterErrors} from "src/tokens/ERC1155/presets/minter/ERC1155TokenMinterErrors.sol"; +import {IERC1155TokenMinterSignals} from "src/tokens/ERC1155/presets/minter/IERC1155TokenMinter.sol"; import {ERC1155TokenMinterFactory} from "src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -13,7 +13,7 @@ import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; import {IERC1155Metadata} from "@0xsequence/erc-1155/contracts/tokens/ERC1155/ERC1155Metadata.sol"; -contract ERC1155TokenMinterTest is Test, ERC1155TokenMinterErrors { +contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { // Redeclare events event TransferSingle( address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _amount diff --git a/test/tokens/ERC20/ERC20TokenMinter.t.sol b/test/tokens/ERC20/ERC20TokenMinter.t.sol index 17e09fd..b01f26c 100644 --- a/test/tokens/ERC20/ERC20TokenMinter.t.sol +++ b/test/tokens/ERC20/ERC20TokenMinter.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {ERC20TokenMinter} from "src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol"; -import {ERC20TokenMinterErrors} from "src/tokens/ERC20/presets/minter/ERC20TokenMinterErrors.sol"; +import {IERC20TokenMinterSignals} from "src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol"; import {ERC20TokenMinterFactory} from "src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -13,7 +13,7 @@ import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -contract ERC20TokenMinterTest is Test, ERC20TokenMinterErrors { +contract ERC20TokenMinterTest is Test, IERC20TokenMinterSignals { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 value); diff --git a/test/tokens/ERC721/presets/ERC721Sale.t.sol b/test/tokens/ERC721/presets/ERC721Sale.t.sol index 46bce17..9ed7a40 100644 --- a/test/tokens/ERC721/presets/ERC721Sale.t.sol +++ b/test/tokens/ERC721/presets/ERC721Sale.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {ERC721Sale} from "src/tokens/ERC721/presets/sale/ERC721Sale.sol"; -import {ERC721SaleErrors} from "src/tokens/ERC721/presets/sale/ERC721SaleErrors.sol"; +import {IERC721SaleSignals} from "src/tokens/ERC721/presets/sale/IERC721Sale.sol"; import {ERC721SaleFactory} from "src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol"; import {Merkle} from "murky/Merkle.sol"; @@ -21,7 +21,7 @@ import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol" // solhint-disable no-rely-on-time -contract ERC721SaleTest is Test, Merkle, ERC721SaleErrors { +contract ERC721SaleTest is Test, Merkle, IERC721SaleSignals { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); @@ -223,7 +223,7 @@ contract ERC721SaleTest is Test, Merkle, ERC721SaleErrors { } // Minting with merkle fail bad proof. - function testMerkleFailBadProof(address[] memory allowlist, address sender, uint256 tokenId) + function testMerkleFailBadProof(address[] memory allowlist, address sender) public { // Construct a merkle tree with the allowlist. diff --git a/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol b/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol index 32b935e..008ab38 100644 --- a/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol +++ b/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {ERC721TokenMinter} from "src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol"; -import {ERC721TokenMinterErrors} from "src/tokens/ERC721/presets/minter/ERC721TokenMinterErrors.sol"; +import {IERC721TokenMinterSignals} from "src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol"; import {ERC721TokenMinterFactory} from "src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -13,7 +13,7 @@ import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; import {IERC721A} from "erc721a/contracts/interfaces/IERC721A.sol"; import {IERC721AQueryable} from "erc721a/contracts/extensions/IERC721AQueryable.sol"; -contract ERC721TokenMinterTest is Test, ERC721TokenMinterErrors { +contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); From c14efbbd00e7a2c9c45bb8ecfa8b7bd7c4c5d43d Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Mon, 3 Jul 2023 09:11:28 +1200 Subject: [PATCH 40/49] Tidy interfaces and comments --- src/proxies/ERC1967/IProxyDeployer.sol | 19 ++++++++++ src/proxies/ERC1967/ProxyDeployer.sol | 3 ++ src/tokens/ERC1155/ERC1155Token.sol | 2 +- .../extensions/supply/ERC1155Supply.sol | 2 +- .../presets/minter/ERC1155TokenMinter.sol | 2 +- .../minter/ERC1155TokenMinterFactory.sol | 3 ++ .../presets/minter/IERC1155TokenMinter.sol | 23 ++++++++++-- .../minter/IERC1155TokenMinterFactory.sol | 2 -- .../presets/sale/ERC1155SaleFactory.sol | 3 ++ .../ERC1155/presets/sale/IERC1155Sale.sol | 6 ---- .../presets/sale/IERC1155SaleFactory.sol | 2 -- src/tokens/ERC20/ERC20Token.sol | 2 +- .../ERC20/presets/minter/ERC20TokenMinter.sol | 1 + .../minter/ERC20TokenMinterFactory.sol | 3 ++ .../presets/minter/IERC20TokenMinter.sol | 10 ++++++ .../minter/IERC20TokenMinterFactory.sol | 1 + src/tokens/ERC721/ERC721Token.sol | 2 +- .../presets/minter/ERC721TokenMinter.sol | 2 +- .../minter/ERC721TokenMinterFactory.sol | 5 ++- .../presets/minter/IERC721TokenMinter.sol | 12 ++++++- .../minter/IERC721TokenMinterFactory.sol | 1 - src/tokens/ERC721/presets/sale/ERC721Sale.sol | 7 ++-- .../ERC721/presets/sale/ERC721SaleFactory.sol | 5 ++- .../ERC721/presets/sale/IERC721Sale.sol | 35 +++++++++++++------ .../presets/sale/IERC721SaleFactory.sol | 4 +-- src/tokens/common/ERC2981Controlled.sol | 8 ++--- src/tokens/common/IERC2981Controlled.sol | 22 ++++++++++++ src/tokens/common/IMerkleProofSingleUse.sol | 28 +++++++++++++++ src/tokens/common/IWithdrawControlled.sol | 30 ++++++++++++++++ src/tokens/common/MerkleProofSingleUse.sol | 29 ++++++++------- src/tokens/common/WithdrawControlled.sol | 6 ++-- test/tokens/ERC1155/presets/ERC1155Sale.t.sol | 4 +-- test/tokens/ERC721/presets/ERC721Sale.t.sol | 4 +-- 33 files changed, 223 insertions(+), 65 deletions(-) create mode 100644 src/tokens/common/IERC2981Controlled.sol create mode 100644 src/tokens/common/IMerkleProofSingleUse.sol create mode 100644 src/tokens/common/IWithdrawControlled.sol diff --git a/src/proxies/ERC1967/IProxyDeployer.sol b/src/proxies/ERC1967/IProxyDeployer.sol index 7512878..59fa0e2 100644 --- a/src/proxies/ERC1967/IProxyDeployer.sol +++ b/src/proxies/ERC1967/IProxyDeployer.sol @@ -1,7 +1,26 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; +interface IProxyDeployerFunctions { + + /** + * Predicts the deployed wrapper proxy address for a given implementation and salt. + * @param implAddr The address of the proxy implementation + * @param salt The deployment salt + * @return proxyAddr The address of the deployed wrapper + */ + function predictProxyAddress(address implAddr, bytes32 salt) external view returns (address proxyAddr); + + // Note deployment functions are the responsibility of inheriting contracts +} + interface IProxyDeployerSignals { + + // Note success events are the responsibility of inheriting contracts + + /** + * Thrown when the proxy creation fails. + */ error ProxyCreationFailed(); } diff --git a/src/proxies/ERC1967/ProxyDeployer.sol b/src/proxies/ERC1967/ProxyDeployer.sol index 7fb5c83..f50cd34 100644 --- a/src/proxies/ERC1967/ProxyDeployer.sol +++ b/src/proxies/ERC1967/ProxyDeployer.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.17; import {IProxyDeployer} from "./IProxyDeployer.sol"; import {Proxy} from "./Proxy.sol"; +/** + * A base implementation for proxy deployer contracts. + */ abstract contract ProxyDeployer is IProxyDeployer { /** diff --git a/src/tokens/ERC1155/ERC1155Token.sol b/src/tokens/ERC1155/ERC1155Token.sol index 77dcb3f..e409bff 100644 --- a/src/tokens/ERC1155/ERC1155Token.sol +++ b/src/tokens/ERC1155/ERC1155Token.sol @@ -9,7 +9,7 @@ import {ERC2981Controlled} from "@0xsequence/contracts-library/tokens/common/ERC error InvalidInitialization(); /** - * A ready made implementation of ERC-1155. + * A standard base implementation of ERC-1155 for use in Sequence library contracts. */ abstract contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981Controlled { bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); diff --git a/src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol b/src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol index f969035..6920dfa 100644 --- a/src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol +++ b/src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol @@ -5,7 +5,7 @@ import {ERC1155Token} from "@0xsequence/contracts-library/tokens/ERC1155/ERC1155 import {IERC1155Supply} from "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/IERC1155Supply.sol"; /** - * An ERC1155 extension that tracks token supply. + * An ERC-1155 extension that tracks token supply. */ abstract contract ERC1155Supply is ERC1155Token, IERC1155Supply { // Maximum supply globaly and per token. 0 indicates unlimited supply diff --git a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol index 920a374..29a0bb8 100644 --- a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol +++ b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol @@ -7,7 +7,7 @@ import {ERC1155Token} from "@0xsequence/contracts-library/tokens/ERC1155/ERC1155 import {ERC2981Controlled} from "@0xsequence/contracts-library/tokens/common/ERC2981Controlled.sol"; /** - * A ready made implementation of ERC-1155 capable of minting when role provided. + * An implementation of ERC-1155 capable of minting when role provided. */ contract ERC1155TokenMinter is ERC1155MintBurn, ERC1155Token, IERC1155TokenMinter { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); diff --git a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol index f59aecd..8677e3d 100644 --- a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol +++ b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol @@ -5,6 +5,9 @@ import {ERC1155TokenMinter} from "@0xsequence/contracts-library/tokens/ERC1155/p import {IERC1155TokenMinterFactory} from "@0xsequence/contracts-library/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol"; import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; +/** + * Deployer of ERC-1155 Token Minter proxies. + */ contract ERC1155TokenMinterFactory is IERC1155TokenMinterFactory, ProxyDeployer { address private immutable implAddr; diff --git a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinter.sol b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinter.sol index 1ae520d..a4a9250 100644 --- a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinter.sol +++ b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinter.sol @@ -1,12 +1,31 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -interface IERC1155TokenMinterSignals { +interface IERC1155TokenMinterFunctions { + /** + * Mint tokens. + * @param to Address to mint tokens to. + * @param tokenId Token ID to mint. + * @param amount Amount of tokens to mint. + * @param data Data to pass if receiver is contract. + */ + function mint(address to, uint256 tokenId, uint256 amount, bytes memory data) external; + + /** + * Mint tokens. + * @param to Address to mint tokens to. + * @param tokenIds Token IDs to mint. + * @param amounts Amounts of tokens to mint. + * @param data Data to pass if receiver is contract. + */ + function batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data) external; +} +interface IERC1155TokenMinterSignals { /** * Invalid initialization error. */ error InvalidInitialization(); } -interface IERC1155TokenMinter is IERC1155TokenMinterSignals {} +interface IERC1155TokenMinter is IERC1155TokenMinterFunctions, IERC1155TokenMinterSignals {} diff --git a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol index 28d212e..63495a2 100644 --- a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol +++ b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.17; interface IERC1155TokenMinterFactoryFunctions { - /** * Creates an ERC-1155 Token Minter proxy. * @param owner The owner of the ERC-1155 Token Minter proxy @@ -22,7 +21,6 @@ interface IERC1155TokenMinterFactorySignals { * @param proxyAddr The address of the deployed proxy. */ event ERC1155TokenMinterDeployed(address proxyAddr); - } interface IERC1155TokenMinterFactory is IERC1155TokenMinterFactoryFunctions, IERC1155TokenMinterFactorySignals {} diff --git a/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol index 4b03038..9e093f8 100644 --- a/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol @@ -5,6 +5,9 @@ import {ERC1155Sale} from "@0xsequence/contracts-library/tokens/ERC1155/presets/ import {IERC1155SaleFactory} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol"; import {ProxyDeployer} from "@0xsequence/contracts-library//proxies/ERC1967/ProxyDeployer.sol"; +/** + * Deployer of ERC-1155 Sale proxies. + */ contract ERC1155SaleFactory is IERC1155SaleFactory, ProxyDeployer { address private immutable _implAddr; diff --git a/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol b/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol index eb4125d..50af47b 100644 --- a/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol +++ b/src/tokens/ERC1155/presets/sale/IERC1155Sale.sol @@ -53,7 +53,6 @@ interface IERC1155SaleFunctions { payable; } -// Events and errors are declared separately for test inheritance. interface IERC1155SaleSignals { event GlobalSaleDetailsUpdated(uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime, bytes32 merkleRoot); @@ -86,11 +85,6 @@ interface IERC1155SaleSignals { * Invalid token IDs. */ error InvalidTokenIds(); - - /** - * Withdraw failed. - */ - error WithdrawFailed(); } interface IERC1155Sale is IERC1155SaleFunctions, IERC1155SaleSignals {} diff --git a/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol index 29b07de..6e7ad89 100644 --- a/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.17; interface IERC1155SaleFactoryFunctions { - /** * Creates an ERC-1155 Sale proxy contract * @param owner The owner of the ERC-1155 Sale @@ -22,7 +21,6 @@ interface IERC1155SaleFactorySignals { * @param proxyAddr The address of the deployed proxy. */ event ERC1155SaleDeployed(address proxyAddr); - } interface IERC1155SaleFactory is IERC1155SaleFactoryFunctions, IERC1155SaleFactorySignals {} diff --git a/src/tokens/ERC20/ERC20Token.sol b/src/tokens/ERC20/ERC20Token.sol index 1238561..11aa363 100644 --- a/src/tokens/ERC20/ERC20Token.sol +++ b/src/tokens/ERC20/ERC20Token.sol @@ -9,7 +9,7 @@ import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; error InvalidInitialization(); /** - * A ready made implementation of ERC-20. + * A standard base implementation of ERC-20 for use in Sequence library contracts. */ abstract contract ERC20Token is ERC20, AccessControl { diff --git a/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol b/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol index fa2216d..0d08679 100644 --- a/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol +++ b/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol @@ -45,6 +45,7 @@ contract ERC20TokenMinter is ERC20Token, IERC20TokenMinter { * Mint tokens. * @param to Address to mint tokens to. * @param amount Amount of tokens to mint. + * @notice This function can only be called by a minter. */ function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { _mint(to, amount); diff --git a/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol b/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol index 2173946..eb53e50 100644 --- a/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol +++ b/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol @@ -5,6 +5,9 @@ import {ERC20TokenMinter} from "@0xsequence/contracts-library/tokens/ERC20/prese import {IERC20TokenMinterFactory} from "@0xsequence/contracts-library/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol"; import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; +/** + * Deployer of ERC-20 Token Minter proxies. + */ contract ERC20TokenMinterFactory is IERC20TokenMinterFactory, ProxyDeployer { address private immutable _implAddr; diff --git a/src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol b/src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol index 41c2ef4..aabbab9 100644 --- a/src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol +++ b/src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol @@ -1,6 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; +interface IERC20TokenMinterFunctions { + + /** + * Mint tokens. + * @param to Address to mint tokens to. + * @param amount Amount of tokens to mint. + */ + function mint(address to, uint256 amount) external; +} + interface IERC20TokenMinterSignals { /** diff --git a/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol b/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol index 0183ce4..6e0d6b6 100644 --- a/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol +++ b/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol @@ -18,6 +18,7 @@ interface IERC20TokenMinterFactoryFunctions { } interface IERC20TokenMinterFactorySignals { + /** * Event emitted when a new ERC-20 Token Minter proxy contract is deployed. * @param proxyAddr The address of the deployed proxy. diff --git a/src/tokens/ERC721/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol index 173fe06..0b16c51 100644 --- a/src/tokens/ERC721/ERC721Token.sol +++ b/src/tokens/ERC721/ERC721Token.sol @@ -9,7 +9,7 @@ import {ERC2981Controlled} from "@0xsequence/contracts-library/tokens/common/ERC error InvalidInitialization(); /** - * A ready made implementation of ERC-721. + * A standard base implementation of ERC-721 for use in Sequence library contracts. */ abstract contract ERC721Token is ERC721AQueryable, ERC2981Controlled { bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); diff --git a/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol index 24d4daa..416f467 100644 --- a/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol @@ -5,7 +5,7 @@ import {ERC721Token} from "@0xsequence/contracts-library/tokens/ERC721/ERC721Tok import {IERC721TokenMinter} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/IERC721TokenMinter.sol"; /** - * A ready made implementation of ERC-721 with role based minting. + * An implementation of ERC-721 capable of minting when role provided. */ contract ERC721TokenMinter is ERC721Token, IERC721TokenMinter { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); diff --git a/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol index 7caddab..0338dc8 100644 --- a/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {ERC721TokenMinter} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/ERC721TokenMinter.sol"; import {IERC721TokenMinterFactory} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol"; +import {ERC721TokenMinter} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/ERC721TokenMinter.sol"; import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; +/** + * Deployer of ERC-721 Token Minter proxies. + */ contract ERC721TokenMinterFactory is IERC721TokenMinterFactory, ProxyDeployer { address private immutable _implAddr; diff --git a/src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol b/src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol index fd8a156..3a2364f 100644 --- a/src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol +++ b/src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol @@ -1,6 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; +interface IERC721TokenMinterFunctions { + + /** + * Mint tokens. + * @param to Address to mint tokens to. + * @param amount Amount of tokens to mint. + */ + function mint(address to, uint256 amount) external; +} + interface IERC721TokenMinterSignals { /** @@ -9,4 +19,4 @@ interface IERC721TokenMinterSignals { error InvalidInitialization(); } -interface IERC721TokenMinter is IERC721TokenMinterSignals {} +interface IERC721TokenMinter is IERC721TokenMinterFunctions, IERC721TokenMinterSignals {} diff --git a/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol index bef5a80..a83acfb 100644 --- a/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol +++ b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.17; interface IERC721TokenMinterFactoryFunctions { - /** * Creates an ERC-721 Token Minter proxy. * @param owner The owner of the ERC-721 Token Minter proxy diff --git a/src/tokens/ERC721/presets/sale/ERC721Sale.sol b/src/tokens/ERC721/presets/sale/ERC721Sale.sol index 84b26fb..ead97a8 100644 --- a/src/tokens/ERC721/presets/sale/ERC721Sale.sol +++ b/src/tokens/ERC721/presets/sale/ERC721Sale.sol @@ -6,6 +6,9 @@ import {ERC721Token} from "@0xsequence/contracts-library/tokens/ERC721/ERC721Tok import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; import {MerkleProofSingleUse} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; +/** + * An ERC-721 token contract with primary sale mechanisms. + */ contract ERC721Sale is IERC721Sale, ERC721Token, WithdrawControlled, MerkleProofSingleUse { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); @@ -46,7 +49,7 @@ contract ERC721Sale is IERC721Sale, ERC721Token, WithdrawControlled, MerkleProof * @param _endTime Latest acceptable timestamp (exclusive). * @dev A zero endTime value is always considered out of bounds. */ - function blockTimeOutOfBounds(uint256 _startTime, uint256 _endTime) private view returns (bool) { + function _blockTimeOutOfBounds(uint256 _startTime, uint256 _endTime) private view returns (bool) { // 0 end time indicates inactive sale. return _endTime == 0 || block.timestamp < _startTime || block.timestamp >= _endTime; // solhint-disable-line not-rely-on-time } @@ -58,7 +61,7 @@ contract ERC721Sale is IERC721Sale, ERC721Token, WithdrawControlled, MerkleProof */ function _payForActiveMint(uint256 _amount, bytes32[] calldata _proof) private { // Active sale test - if (blockTimeOutOfBounds(_saleDetails.startTime, _saleDetails.endTime)) { + if (_blockTimeOutOfBounds(_saleDetails.startTime, _saleDetails.endTime)) { revert SaleInactive(); } requireMerkleProof(_saleDetails.merkleRoot, _proof, msg.sender); diff --git a/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol index 5be6b3f..c7bd7d9 100644 --- a/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol +++ b/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol @@ -5,6 +5,9 @@ import {ERC721Sale} from "@0xsequence/contracts-library/tokens/ERC721/presets/sa import {IERC721SaleFactory} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/IERC721SaleFactory.sol"; import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; +/** + * Deployer of ERC-721 Sale proxies. + */ contract ERC721SaleFactory is IERC721SaleFactory, ProxyDeployer { address private immutable _implAddr; @@ -17,7 +20,7 @@ contract ERC721SaleFactory is IERC721SaleFactory, ProxyDeployer { } /** - * Creates an ERC-721 Floor Wrapper for given token contract + * Creates an ERC-721 Sale for given token contract * @param owner The owner of the ERC-721 Sale * @param name The name of the ERC-721 Sale token * @param symbol The symbol of the ERC-721 Sale token diff --git a/src/tokens/ERC721/presets/sale/IERC721Sale.sol b/src/tokens/ERC721/presets/sale/IERC721Sale.sol index 54566d3..949d4bd 100644 --- a/src/tokens/ERC721/presets/sale/IERC721Sale.sol +++ b/src/tokens/ERC721/presets/sale/IERC721Sale.sol @@ -12,12 +12,6 @@ interface IERC721SaleFunctions { bytes32 merkleRoot; // Root of allowed addresses } - /** - * Get sale details. - * @return Sale details. - */ - function saleDetails() external view returns (SaleDetails memory); - /** * Mint tokens. * @param to Address to mint tokens to. @@ -27,6 +21,30 @@ interface IERC721SaleFunctions { * @dev An empty proof is supplied when no proof is required. */ function mint(address to, uint256 amount, bytes32[] memory proof) external payable; + + /** + * Set the sale details. + * @param supplyCap The maximum number of tokens that can be minted. 0 indicates unlimited supply. + * @param cost The amount of payment tokens to accept for each token minted. + * @param paymentToken The ERC20 token address to accept payment in. address(0) indicates ETH. + * @param startTime The start time of the sale. Tokens cannot be minted before this time. + * @param endTime The end time of the sale. Tokens cannot be minted after this time. + * @param merkleRoot The merkle root for allowlist minting. + */ + function setSaleDetails( + uint256 supplyCap, + uint256 cost, + address paymentToken, + uint64 startTime, + uint64 endTime, + bytes32 merkleRoot + ) external; + + /** + * Get sale details. + * @return Sale details. + */ + function saleDetails() external view returns (SaleDetails memory); } interface IERC721SaleSignals { @@ -56,11 +74,6 @@ interface IERC721SaleSignals { * @param actual Actual amount of tokens. */ error InsufficientPayment(uint256 expected, uint256 actual); - - /** - * Withdraw failed. - */ - error WithdrawFailed(); } interface IERC721Sale is IERC721SaleFunctions, IERC721SaleSignals {} diff --git a/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol index 4f15b10..760da51 100644 --- a/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol +++ b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; interface IERC721SaleFactoryFunctions { /** - * Creates an ERC-721 Floor Wrapper for given token contract + * Creates an ERC-721 Sale. * @param owner The owner of the ERC-721 Sale * @param name The name of the ERC-721 Sale token * @param symbol The symbol of the ERC-721 Sale token @@ -23,8 +23,8 @@ interface IERC721SaleFactoryFunctions { returns (address proxyAddr); } - interface IERC721SaleFactorySignals { + /** * Event emitted when a new ERC-721 Sale proxy contract is deployed. * @param proxyAddr The address of the deployed proxy. diff --git a/src/tokens/common/ERC2981Controlled.sol b/src/tokens/common/ERC2981Controlled.sol index 9204258..972ec71 100644 --- a/src/tokens/common/ERC2981Controlled.sol +++ b/src/tokens/common/ERC2981Controlled.sol @@ -1,16 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; +import {IERC2981Controlled} from "@0xsequence/contracts-library/tokens/common/IERC2981Controlled.sol"; import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; /** * An implementation of ERC-2981 that allows updates by roles. */ -abstract contract ERC2981Controlled is - ERC2981, - AccessControl -{ +abstract contract ERC2981Controlled is ERC2981, AccessControl, IERC2981Controlled { bytes32 public constant ROYALTY_ADMIN_ROLE = keccak256("ROYALTY_ADMIN_ROLE"); // @@ -57,6 +55,6 @@ abstract contract ERC2981Controlled is returns (bool) { return ERC2981.supportsInterface(interfaceId) || AccessControl.supportsInterface(interfaceId) - || super.supportsInterface(interfaceId); + || type(IERC2981Controlled).interfaceId == interfaceId || super.supportsInterface(interfaceId); } } diff --git a/src/tokens/common/IERC2981Controlled.sol b/src/tokens/common/IERC2981Controlled.sol new file mode 100644 index 0000000..2d9a076 --- /dev/null +++ b/src/tokens/common/IERC2981Controlled.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IERC2981ControlledFunctions { + /** + * Sets the royalty information that all ids in this contract will default to. + * @param receiver Address of who should be sent the royalty payment + * @param feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + */ + function setDefaultRoyalty(address receiver, uint96 feeNumerator) external; + + /** + * Sets the royalty information that a given token id in this contract will use. + * @param tokenId The token id to set the royalty information for + * @param receiver Address of who should be sent the royalty payment + * @param feeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @notice This overrides the default royalty information for this token id + */ + function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) external; +} + +interface IERC2981Controlled is IERC2981ControlledFunctions {} diff --git a/src/tokens/common/IMerkleProofSingleUse.sol b/src/tokens/common/IMerkleProofSingleUse.sol new file mode 100644 index 0000000..e7f64dd --- /dev/null +++ b/src/tokens/common/IMerkleProofSingleUse.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IMerkleProofSingleUseFunctions { + + /** + * Checks if the given merkle proof is valid. + * @param root Merkle root. + * @param proof Merkle proof. + * @param addr Address to check. + * @return True if the proof is valid and has not yet been used by {addr}. + */ + function checkMerkleProof(bytes32 root, bytes32[] calldata proof, address addr) external view returns (bool); +} + +interface IMerkleProofSingleUseSignals { + + /** + * Thrown when the merkle proof is invalid or has already been used. + * @param root Merkle root. + * @param proof Merkle proof. + * @param addr Address to check. + */ + error MerkleProofInvalid(bytes32 root, bytes32[] proof, address addr); + +} + +interface IMerkleProofSingleUse is IMerkleProofSingleUseFunctions, IMerkleProofSingleUseSignals {} diff --git a/src/tokens/common/IWithdrawControlled.sol b/src/tokens/common/IWithdrawControlled.sol new file mode 100644 index 0000000..7f9de7b --- /dev/null +++ b/src/tokens/common/IWithdrawControlled.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +interface IWithdrawControlledFunctions { + + /** + * Withdraws ERC20 tokens owned by this contract. + * @param token The ERC20 token address. + * @param to Address to withdraw to. + * @param value Amount to withdraw. + */ + function withdrawERC20(address token, address to, uint256 value) external; + + /** + * Withdraws ETH owned by this sale contract. + * @param to Address to withdraw to. + * @param value Amount to withdraw. + */ + function withdrawETH(address to, uint256 value) external; +} + +interface IWithdrawControlledSignals { + + /** + * Withdraw failed error. + */ + error WithdrawFailed(); +} + +interface IWithdrawControlled is IWithdrawControlledFunctions, IWithdrawControlledSignals {} diff --git a/src/tokens/common/MerkleProofSingleUse.sol b/src/tokens/common/MerkleProofSingleUse.sol index 9c893a2..13473ff 100644 --- a/src/tokens/common/MerkleProofSingleUse.sol +++ b/src/tokens/common/MerkleProofSingleUse.sol @@ -1,32 +1,31 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; +import {IMerkleProofSingleUse} from "@0xsequence/contracts-library/tokens/common/IMerkleProofSingleUse.sol"; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -error MerkleProofInvalid(bytes32 root, bytes32[] proof, address account); - /** - * Require single use merkle proofs per account. + * Require single use merkle proofs per address. */ -abstract contract MerkleProofSingleUse { +abstract contract MerkleProofSingleUse is IMerkleProofSingleUse { - // Stores proofs used by an account + // Stores proofs used by an address mapping(address => mapping(bytes32 => bool)) private _proofUsed; /** * Requires the given merkle proof to be valid. * @param root Merkle root. * @param proof Merkle proof. - * @param account Account to check. - * @notice Fails when the proof is invalid or the proof has already been claimed by this account. + * @param addr Address to check. + * @notice Fails when the proof is invalid or the proof has already been claimed by this address. * @dev This function reverts on failure. */ - function requireMerkleProof(bytes32 root, bytes32[] calldata proof, address account) internal { + function requireMerkleProof(bytes32 root, bytes32[] calldata proof, address addr) internal { if (root != bytes32(0)) { - if (_proofUsed[account][root] || !checkMerkleProof(root, proof, account)) { - revert MerkleProofInvalid(root, proof, account); + if (!checkMerkleProof(root, proof, addr)) { + revert MerkleProofInvalid(root, proof, addr); } - _proofUsed[account][root] = true; + _proofUsed[addr][root] = true; } } @@ -34,11 +33,11 @@ abstract contract MerkleProofSingleUse { * Checks if the given merkle proof is valid. * @param root Merkle root. * @param proof Merkle proof. - * @param account Account to check. - * @return True if the proof is valid. + * @param addr Address to check. + * @return True if the proof is valid and has not yet been used by {addr}. */ - function checkMerkleProof(bytes32 root, bytes32[] calldata proof, address account) public pure returns (bool) { - return MerkleProof.verify(proof, root, keccak256(abi.encodePacked(account))); + function checkMerkleProof(bytes32 root, bytes32[] calldata proof, address addr) public view returns (bool) { + return !_proofUsed[addr][root] && MerkleProof.verify(proof, root, keccak256(abi.encodePacked(addr))); } } diff --git a/src/tokens/common/WithdrawControlled.sol b/src/tokens/common/WithdrawControlled.sol index 943fe06..372cc48 100644 --- a/src/tokens/common/WithdrawControlled.sol +++ b/src/tokens/common/WithdrawControlled.sol @@ -1,15 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; +import {IWithdrawControlled} from "@0xsequence/contracts-library/tokens/common/IWithdrawControlled.sol"; import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; -error WithdrawFailed(); - /** * An abstract contract that allows ETH and ERC20 tokens stored in the contract to be withdrawn. */ -abstract contract WithdrawControlled is AccessControl { +abstract contract WithdrawControlled is AccessControl, IWithdrawControlled { bytes32 public constant WITHDRAW_ROLE = keccak256("WITHDRAW_ROLE"); // @@ -31,7 +30,6 @@ abstract contract WithdrawControlled is AccessControl { * Withdraws ETH owned by this sale contract. * @param to Address to withdraw to. * @param value Amount to withdraw. - * @dev Withdraws ERC20 when paymentToken is set, else ETH. * @notice Only callable by an address with the withdraw role. */ function withdrawETH(address to, uint256 value) public onlyRole(WITHDRAW_ROLE) { diff --git a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol index 84d6c60..5051561 100644 --- a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol @@ -11,7 +11,7 @@ import {Merkle} from "murky/Merkle.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {TestHelper} from "test/tokens/TestHelper.sol"; -import {MerkleProofInvalid} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; +import {IMerkleProofSingleUseSignals} from "@0xsequence/contracts-library/tokens/common/IMerkleProofSingleUse.sol"; // Interfaces import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; @@ -21,7 +21,7 @@ import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol" // solhint-disable no-rely-on-time -contract ERC1155SaleTest is Test, Merkle, IERC1155SaleSignals, IERC1155SupplySignals { +contract ERC1155SaleTest is Test, Merkle, IERC1155SaleSignals, IERC1155SupplySignals, IMerkleProofSingleUseSignals { // Redeclare events event TransferBatch( address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts diff --git a/test/tokens/ERC721/presets/ERC721Sale.t.sol b/test/tokens/ERC721/presets/ERC721Sale.t.sol index 9ed7a40..d28840d 100644 --- a/test/tokens/ERC721/presets/ERC721Sale.t.sol +++ b/test/tokens/ERC721/presets/ERC721Sale.t.sol @@ -10,7 +10,7 @@ import {Merkle} from "murky/Merkle.sol"; import {ERC20Mock} from "@0xsequence/erc20-meta-token/contracts/mocks/ERC20Mock.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {TestHelper} from "test/tokens/TestHelper.sol"; -import {MerkleProofInvalid} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; +import {IMerkleProofSingleUseSignals} from "@0xsequence/contracts-library/tokens/common/IMerkleProofSingleUse.sol"; // Interfaces import {IERC165} from "@0xsequence/erc-1155/contracts/interfaces/IERC165.sol"; @@ -21,7 +21,7 @@ import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol" // solhint-disable no-rely-on-time -contract ERC721SaleTest is Test, Merkle, IERC721SaleSignals { +contract ERC721SaleTest is Test, Merkle, IERC721SaleSignals, IMerkleProofSingleUseSignals { // Redeclare events event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); From 6745d50fafac9c388301553babcf73ede832cad9 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 26 Jul 2023 10:58:33 +1200 Subject: [PATCH 41/49] Add SequenceProxyFactory --- src/proxies/SequenceProxyFactory.sol | 66 +++++++ .../TransparentUpgradeableBeaconProxy.sol | 67 +++++++ src/proxies/openzeppelin/BeaconProxy.sol | 48 +++++ src/proxies/openzeppelin/ERC1967Proxy.sol | 24 +++ .../TransparentUpgradeableProxy.sol | 186 ++++++++++++++++++ test/proxies/SequenceProxyFactory.t.sol | 122 ++++++++++++ 6 files changed, 513 insertions(+) create mode 100644 src/proxies/SequenceProxyFactory.sol create mode 100644 src/proxies/TransparentUpgradeableBeaconProxy.sol create mode 100644 src/proxies/openzeppelin/BeaconProxy.sol create mode 100644 src/proxies/openzeppelin/ERC1967Proxy.sol create mode 100644 src/proxies/openzeppelin/TransparentUpgradeableProxy.sol create mode 100644 test/proxies/SequenceProxyFactory.t.sol diff --git a/src/proxies/SequenceProxyFactory.sol b/src/proxies/SequenceProxyFactory.sol new file mode 100644 index 0000000..81d7c58 --- /dev/null +++ b/src/proxies/SequenceProxyFactory.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import { + TransparentUpgradeableBeaconProxy, + ITransparentUpgradeableBeaconProxy +} from "./TransparentUpgradeableBeaconProxy.sol"; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; + +/** + * An proxy factory that deploys upgradeable beacon proxies. + * @dev The factory owner is able to upgrade the beacon implementation. + * @dev Proxy deployers are able to override the beacon reference with their own. + */ +abstract contract SequenceProxyFactory is Ownable { + UpgradeableBeacon public beacon; + + /** + * Initialize a Sequence Proxy Factory. + * @param implementation The initial beacon implementation. + * @param factoryOwner The owner of the factory. + */ + function _initialize(address implementation, address factoryOwner) internal { + beacon = new UpgradeableBeacon(implementation); + Ownable._transferOwnership(factoryOwner); + } + + /** + * Deploys and initializes a new proxy instance. + * @param _salt The deployment salt. + * @param _proxyOwner The owner of the proxy. + * @param _data The initialization data. + * @return proxyAddress The address of the deployed proxy. + */ + function _createProxy(bytes32 _salt, address _proxyOwner, bytes memory _data) internal returns (address proxyAddress) { + bytes32 saltedHash = keccak256(abi.encodePacked(_salt, _proxyOwner, address(beacon), _data)); + bytes memory bytecode = type(TransparentUpgradeableBeaconProxy).creationCode; + + proxyAddress = Create2.deploy(0, saltedHash, bytecode); + ITransparentUpgradeableBeaconProxy(payable(proxyAddress)).initialize(_proxyOwner, address(beacon), _data); + } + + /** + * Computes the address of a proxy instance. + * @param _salt The deployment salt. + * @param _proxyOwner The owner of the proxy. + * @return proxy The expected address of the deployed proxy. + */ + function _computeProxyAddress(bytes32 _salt, address _proxyOwner, bytes memory _data) internal view returns (address) { + bytes32 saltedHash = keccak256(abi.encodePacked(_salt, _proxyOwner, address(beacon), _data)); + bytes32 bytecodeHash = keccak256(type(TransparentUpgradeableBeaconProxy).creationCode); + + return Create2.computeAddress(saltedHash, bytecodeHash); + } + + /** + * Upgrades the beacon implementation. + * @param implementation The new beacon implementation. + */ + function upgradeBeacon(address implementation) public onlyOwner { + beacon.upgradeTo(implementation); + } +} diff --git a/src/proxies/TransparentUpgradeableBeaconProxy.sol b/src/proxies/TransparentUpgradeableBeaconProxy.sol new file mode 100644 index 0000000..741fd88 --- /dev/null +++ b/src/proxies/TransparentUpgradeableBeaconProxy.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {BeaconProxy, Proxy} from "./openzeppelin/BeaconProxy.sol"; +import {TransparentUpgradeableProxy, ERC1967Proxy} from "./openzeppelin/TransparentUpgradeableProxy.sol"; + +interface ITransparentUpgradeableBeaconProxy { + function initialize(address admin, address beacon, bytes memory data) external; +} + +error InvalidInitialization(); + +contract TransparentUpgradeableBeaconProxy is TransparentUpgradeableProxy, BeaconProxy { + /** + * Decode the initialization data from the msg.data and call the initialize function. + */ + function _dispatchInitialize() private returns (bytes memory) { + _requireZeroValue(); + + (address admin, address beacon, bytes memory data) = abi.decode(msg.data[4:], (address, address, bytes)); + initialize(admin, beacon, data); + + return ""; + } + + function initialize(address admin, address beacon, bytes memory data) internal { + if (_admin() != address(0)) { + // Redundant call. This function can only be called when the admin is not set. + revert InvalidInitialization(); + } + _changeAdmin(admin); + _upgradeBeaconToAndCall(beacon, data, false); + } + + /** + * @dev If the admin is not set, the fallback function is used to initialize the proxy. + * @dev If the admin is set, the fallback function is used to delegatecall the implementation. + */ + function _fallback() internal override (TransparentUpgradeableProxy, Proxy) { + if (_getAdmin() == address(0)) { + bytes memory ret; + bytes4 selector = msg.sig; + if (selector == ITransparentUpgradeableBeaconProxy.initialize.selector) { + ret = _dispatchInitialize(); + // solhint-disable-next-line no-inline-assembly + assembly { + return(add(ret, 0x20), mload(ret)) + } + } + // When the admin is not set, the fallback function is used to initialize the proxy. + revert InvalidInitialization(); + } + TransparentUpgradeableProxy._fallback(); + } + + /** + * Returns the current implementation address. + * @dev This is the implementation address set by the admin, or the beacon implementation. + */ + function _implementation() internal view override (ERC1967Proxy, BeaconProxy) returns (address) { + address implementation = ERC1967Proxy._implementation(); + if (implementation != address(0)) { + return implementation; + } + return BeaconProxy._implementation(); + } +} diff --git a/src/proxies/openzeppelin/BeaconProxy.sol b/src/proxies/openzeppelin/BeaconProxy.sol new file mode 100644 index 0000000..d7891a6 --- /dev/null +++ b/src/proxies/openzeppelin/BeaconProxy.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (proxy/beacon/BeaconProxy.sol) + +// Note: This implementation is an exact copy with the constructor removed, and pragma and imports updated. + +pragma solidity ^0.8.17; + +import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; +import "@openzeppelin/contracts/proxy/Proxy.sol"; +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol"; + +/** + * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}. + * + * The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't + * conflict with the storage layout of the implementation behind the proxy. + * + * _Available since v3.4._ + */ +contract BeaconProxy is Proxy, ERC1967Upgrade { + /** + * @dev Returns the current beacon address. + */ + function _beacon() internal view virtual returns (address) { + return _getBeacon(); + } + + /** + * @dev Returns the current implementation address of the associated beacon. + */ + function _implementation() internal view virtual override returns (address) { + return IBeacon(_getBeacon()).implementation(); + } + + /** + * @dev Changes the proxy to use a new beacon. Deprecated: see {_upgradeBeaconToAndCall}. + * + * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. + * + * Requirements: + * + * - `beacon` must be a contract. + * - The implementation returned by `beacon` must be a contract. + */ + function _setBeacon(address beacon, bytes memory data) internal virtual { + _upgradeBeaconToAndCall(beacon, data, false); + } +} diff --git a/src/proxies/openzeppelin/ERC1967Proxy.sol b/src/proxies/openzeppelin/ERC1967Proxy.sol new file mode 100644 index 0000000..0f400d8 --- /dev/null +++ b/src/proxies/openzeppelin/ERC1967Proxy.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol) + +// Note: This implementation is an exact copy with the constructor removed, and pragma and imports updated. + +pragma solidity ^0.8.17; + +import "@openzeppelin/contracts/proxy/Proxy.sol"; +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol"; + +/** + * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an + * implementation address that can be changed. This address is stored in storage in the location specified by + * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the + * implementation behind the proxy. + */ +contract ERC1967Proxy is Proxy, ERC1967Upgrade { + /** + * @dev Returns the current implementation address. + */ + function _implementation() internal view virtual override returns (address impl) { + return ERC1967Upgrade._getImplementation(); + } +} diff --git a/src/proxies/openzeppelin/TransparentUpgradeableProxy.sol b/src/proxies/openzeppelin/TransparentUpgradeableProxy.sol new file mode 100644 index 0000000..a51f30e --- /dev/null +++ b/src/proxies/openzeppelin/TransparentUpgradeableProxy.sol @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (proxy/transparent/TransparentUpgradeableProxy.sol) + +// Note: This implementation is an exact copy with the constructor removed, and pragma and imports updated. + +pragma solidity ^0.8.17; + +import "./ERC1967Proxy.sol"; + +/** + * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy} + * does not implement this interface directly, and some of its functions are implemented by an internal dispatch + * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not + * include them in the ABI so this interface must be used to interact with it. + */ +interface ITransparentUpgradeableProxy is IERC1967 { + function admin() external view returns (address); + + function implementation() external view returns (address); + + function changeAdmin(address) external; + + function upgradeTo(address) external; + + function upgradeToAndCall(address, bytes memory) external payable; +} + +/** + * @dev This contract implements a proxy that is upgradeable by an admin. + * + * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector + * clashing], which can potentially be used in an attack, this contract uses the + * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two + * things that go hand in hand: + * + * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if + * that call matches one of the admin functions exposed by the proxy itself. + * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the + * implementation. If the admin tries to call a function on the implementation it will fail with an error that says + * "admin cannot fallback to proxy target". + * + * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing + * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due + * to sudden errors when trying to call a function from the proxy implementation. + * + * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, + * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. + * + * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not + * inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch + * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to + * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the + * implementation. + * + * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler + * will not check that there are no selector conflicts, due to the note above. A selector clash between any new function + * and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could + * render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised. + */ +contract TransparentUpgradeableProxy is ERC1967Proxy { + + /** + * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. + * + * CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the + * implementation provides a function with the same selector. + */ + modifier ifAdmin() { + if (msg.sender == _getAdmin()) { + _; + } else { + _fallback(); + } + } + + /** + * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior + */ + function _fallback() internal virtual override { + if (msg.sender == _getAdmin()) { + bytes memory ret; + bytes4 selector = msg.sig; + if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) { + ret = _dispatchUpgradeTo(); + } else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) { + ret = _dispatchUpgradeToAndCall(); + } else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) { + ret = _dispatchChangeAdmin(); + } else if (selector == ITransparentUpgradeableProxy.admin.selector) { + ret = _dispatchAdmin(); + } else if (selector == ITransparentUpgradeableProxy.implementation.selector) { + ret = _dispatchImplementation(); + } else { + revert("TransparentUpgradeableProxy: admin cannot fallback to proxy target"); + } + assembly { + return(add(ret, 0x20), mload(ret)) + } + } else { + super._fallback(); + } + } + + /** + * @dev Returns the current admin. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the + * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` + */ + function _dispatchAdmin() private returns (bytes memory) { + _requireZeroValue(); + + address admin = _getAdmin(); + return abi.encode(admin); + } + + /** + * @dev Returns the current implementation. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the + * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` + */ + function _dispatchImplementation() private returns (bytes memory) { + _requireZeroValue(); + + address implementation = _implementation(); + return abi.encode(implementation); + } + + /** + * @dev Changes the admin of the proxy. + * + * Emits an {AdminChanged} event. + */ + function _dispatchChangeAdmin() private returns (bytes memory) { + _requireZeroValue(); + + address newAdmin = abi.decode(msg.data[4:], (address)); + _changeAdmin(newAdmin); + + return ""; + } + + /** + * @dev Upgrade the implementation of the proxy. + */ + function _dispatchUpgradeTo() private returns (bytes memory) { + _requireZeroValue(); + + address newImplementation = abi.decode(msg.data[4:], (address)); + _upgradeToAndCall(newImplementation, bytes(""), false); + + return ""; + } + + /** + * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified + * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the + * proxied contract. + */ + function _dispatchUpgradeToAndCall() private returns (bytes memory) { + (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); + _upgradeToAndCall(newImplementation, data, true); + + return ""; + } + + /** + * @dev Returns the current admin. + * + * CAUTION: This function is deprecated. Use {ERC1967Upgrade-_getAdmin} instead. + */ + function _admin() internal view virtual returns (address) { + return _getAdmin(); + } + + /** + * @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to + * emulate some proxy functions being non-payable while still allowing value to pass through. + */ + function _requireZeroValue() internal { + require(msg.value == 0); + } +} diff --git a/test/proxies/SequenceProxyFactory.t.sol b/test/proxies/SequenceProxyFactory.t.sol new file mode 100644 index 0000000..8178544 --- /dev/null +++ b/test/proxies/SequenceProxyFactory.t.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {SequenceProxyFactory} from "src/proxies/SequenceProxyFactory.sol"; +import {ITransparentUpgradeableProxy} from "src/proxies/openzeppelin/TransparentUpgradeableProxy.sol"; + +contract MockImplementationV1 { + function getValue() public pure virtual returns (uint256) { + return 1; + } +} + +contract MockImplementationV2 is MockImplementationV1 { + function getValue() public pure virtual override returns (uint256) { + return 2; + } +} + +contract MockImplementationV3 is MockImplementationV1 { + function getValue() public pure virtual override returns (uint256) { + return 3; + } +} + +contract PublicSequenceProxyFactory is SequenceProxyFactory { + constructor(address implementation, address factoryOwner) { + _initialize(implementation, factoryOwner); + } + + function createProxy(bytes32 salt, address proxyOwner, bytes memory data) external returns (address proxyAddress) { + return _createProxy(salt, proxyOwner, data); + } + + function computeProxyAddress(bytes32 salt, address proxyOwner, bytes memory data) external view returns (address) { + return _computeProxyAddress(salt, proxyOwner, data); + } +} + +contract SequenceProxyFactoryTest is Test { + + PublicSequenceProxyFactory private factory; + address private proxyOwner; + address private impl1; + address private impl2; + address private impl3; + + function setUp() public { + proxyOwner = makeAddr("proxyOwner"); + impl1 = address(new MockImplementationV1()); + impl2 = address(new MockImplementationV2()); + impl3 = address(new MockImplementationV3()); + factory = new PublicSequenceProxyFactory(impl1, address(this)); + } + + function testDeployProxy() public { + address proxy = factory.createProxy(bytes32(""), proxyOwner, bytes("")); + + assertTrue(proxy != address(0)); + uint256 value = MockImplementationV1(proxy).getValue(); + assertEq(value, 1); + } + + function testDeployProxyAfterUpgrade() public { + factory.upgradeBeacon(impl2); + + address proxy = factory.createProxy(bytes32(""), proxyOwner, bytes("")); + + assertTrue(proxy != address(0)); + uint256 value = MockImplementationV1(proxy).getValue(); + assertEq(value, 2); + } + + function testUpgradeAfterDeploy() public { + address proxy = factory.createProxy(bytes32(""), proxyOwner, bytes("")); + assertTrue(proxy != address(0)); + + factory.upgradeBeacon(impl2); + uint256 value = MockImplementationV1(proxy).getValue(); + assertEq(value, 2); + } + + function testProxyOwnerUpgrade() public { + address proxy = factory.createProxy(bytes32(""), proxyOwner, bytes("")); + assertTrue(proxy != address(0)); + + vm.prank(proxyOwner); + ITransparentUpgradeableProxy(payable(proxy)).upgradeTo(impl2); + + uint256 value = MockImplementationV1(proxy).getValue(); + assertEq(value, 2); + } + + function testProxyOwnerUpgradeUnaffectedByBeaconUpgrades() public { + address proxy = factory.createProxy(bytes32(""), proxyOwner, bytes("")); + assertTrue(proxy != address(0)); + + vm.prank(proxyOwner); + ITransparentUpgradeableProxy(payable(proxy)).upgradeTo(impl3); + + // Upgrade beacon + factory.upgradeBeacon(impl2); + + uint256 value = MockImplementationV1(proxy).getValue(); + assertEq(value, 3); + } + + function testAddressCompute() public { + address expected = factory.createProxy(bytes32(""), proxyOwner, bytes("")); + address actual = factory.computeProxyAddress(bytes32(""), proxyOwner, bytes("")); + assertEq(actual, expected); + } + + function testDuplicateDeploysFail() public { + address proxy = factory.createProxy(bytes32(""), proxyOwner, bytes("")); + assertTrue(proxy != address(0)); + + vm.expectRevert("Create2: Failed on deploy"); + factory.createProxy(bytes32(""), proxyOwner, bytes("")); + } + +} From 5aeac624004beff3f532e59c17732a795c093182 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 26 Jul 2023 12:56:39 +1200 Subject: [PATCH 42/49] Migrate to use Sequence Proxy Factory --- src/proxies/ERC1967/IERC1967.sol | 8 -- src/proxies/ERC1967/IProxyDeployer.sol | 27 ------- src/proxies/ERC1967/Proxy.sol | 63 --------------- src/proxies/ERC1967/ProxyDeployer.sol | 76 ------------------- src/proxies/ERC1967/README.md | 27 ------- src/proxies/README.md | 36 +++++++++ .../minter/ERC1155TokenMinterFactory.sol | 26 ++++--- .../minter/IERC1155TokenMinterFactory.sol | 7 +- .../presets/sale/ERC1155SaleFactory.sol | 29 +++---- .../presets/sale/IERC1155SaleFactory.sol | 6 +- .../minter/ERC20TokenMinterFactory.sol | 33 +++++--- .../minter/IERC20TokenMinterFactory.sol | 16 +++- .../minter/ERC721TokenMinterFactory.sol | 32 +++++--- .../minter/IERC721TokenMinterFactory.sol | 15 +++- .../ERC721/presets/sale/ERC721SaleFactory.sol | 25 +++--- .../presets/sale/IERC721SaleFactory.sol | 11 ++- test/tokens/ERC1155/presets/ERC1155Sale.t.sol | 12 ++- .../ERC1155/presets/ERC1155TokenMinter.t.sol | 13 +++- test/tokens/ERC20/ERC20TokenMinter.t.sol | 10 ++- test/tokens/ERC721/presets/ERC721Sale.t.sol | 8 +- .../ERC721/presets/ERC721TokenMinter.t.sol | 14 +++- 21 files changed, 205 insertions(+), 289 deletions(-) delete mode 100644 src/proxies/ERC1967/IERC1967.sol delete mode 100644 src/proxies/ERC1967/IProxyDeployer.sol delete mode 100644 src/proxies/ERC1967/Proxy.sol delete mode 100644 src/proxies/ERC1967/ProxyDeployer.sol delete mode 100644 src/proxies/ERC1967/README.md create mode 100644 src/proxies/README.md diff --git a/src/proxies/ERC1967/IERC1967.sol b/src/proxies/ERC1967/IERC1967.sol deleted file mode 100644 index 058f89b..0000000 --- a/src/proxies/ERC1967/IERC1967.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -interface IERC1967 { - event Upgraded(address indexed implementation); - event AdminChanged(address previousAdmin, address newAdmin); - event BeaconUpgraded(address indexed beacon); -} diff --git a/src/proxies/ERC1967/IProxyDeployer.sol b/src/proxies/ERC1967/IProxyDeployer.sol deleted file mode 100644 index 59fa0e2..0000000 --- a/src/proxies/ERC1967/IProxyDeployer.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -interface IProxyDeployerFunctions { - - /** - * Predicts the deployed wrapper proxy address for a given implementation and salt. - * @param implAddr The address of the proxy implementation - * @param salt The deployment salt - * @return proxyAddr The address of the deployed wrapper - */ - function predictProxyAddress(address implAddr, bytes32 salt) external view returns (address proxyAddr); - - // Note deployment functions are the responsibility of inheriting contracts -} - -interface IProxyDeployerSignals { - - // Note success events are the responsibility of inheriting contracts - - /** - * Thrown when the proxy creation fails. - */ - error ProxyCreationFailed(); -} - -interface IProxyDeployer is IProxyDeployerSignals {} diff --git a/src/proxies/ERC1967/Proxy.sol b/src/proxies/ERC1967/Proxy.sol deleted file mode 100644 index 2de9168..0000000 --- a/src/proxies/ERC1967/Proxy.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import {IERC1967} from "./IERC1967.sol"; -import {StorageSlot} from "../../utils/StorageSlot.sol"; - -contract Proxy is IERC1967 { - bytes32 internal constant _IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); - - /** - * Initializes the contract, setting proxy implementation address. - */ - constructor(address implementation) { - _setImplementation(implementation); - emit Upgraded(implementation); - } - - /** - * Forward calls to the proxy implementation contract. - */ - receive() external payable { - _proxy(); - } - - /** - * Forward calls to the proxy implementation contract. - */ - fallback() external payable { - _proxy(); - } - - /** - * Forward calls to the proxy implementation contract. - */ - function _proxy() private { - address target = _getImplementation(); - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - calldatacopy(ptr, 0, calldatasize()) - let result := delegatecall(gas(), target, ptr, calldatasize(), 0, 0) - let size := returndatasize() - returndatacopy(ptr, 0, size) - switch result - case 0 { revert(ptr, size) } - default { return(ptr, size) } - } - } - - /** - * Set the implementation address. - * @param _implementation The address of the implementation contract. - */ - function _setImplementation(address _implementation) internal { - StorageSlot._getAddressSlot(_IMPLEMENTATION_SLOT).value = _implementation; - } - - /** - * Returns the address of the current implementation. - */ - function _getImplementation() internal view returns (address) { - return StorageSlot._getAddressSlot(_IMPLEMENTATION_SLOT).value; - } -} diff --git a/src/proxies/ERC1967/ProxyDeployer.sol b/src/proxies/ERC1967/ProxyDeployer.sol deleted file mode 100644 index f50cd34..0000000 --- a/src/proxies/ERC1967/ProxyDeployer.sol +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.17; - -import {IProxyDeployer} from "./IProxyDeployer.sol"; -import {Proxy} from "./Proxy.sol"; - -/** - * A base implementation for proxy deployer contracts. - */ -abstract contract ProxyDeployer is IProxyDeployer { - - /** - * Creates a proxy contract for a given implementation - * @param _implAddr The address of the proxy implementation - * @param _salt The deployment salt - * @return proxyAddr The address of the deployed proxy - */ - function _deployProxy(address _implAddr, bytes32 _salt) internal returns (address proxyAddr) { - bytes memory code = _getProxyCode(_implAddr); - - // Deploy it - assembly { // solhint-disable-line no-inline-assembly - proxyAddr := create2(0, add(code, 32), mload(code), _salt) - } - if (proxyAddr == address(0)) { - revert ProxyCreationFailed(); - } - return proxyAddr; - } - - /** - * Predict the deployed wrapper proxy address for a given implementation. - * @param implAddr The address of the proxy implementation - * @param salt The deployment salt - * @return proxyAddr The address of the deployed wrapper - */ - function predictProxyAddress(address implAddr, bytes32 salt) public view returns (address proxyAddr) { - bytes memory code = _getProxyCode(implAddr); - return _predictProxyAddress(code, salt); - } - - /** - * Predict the deployed wrapper proxy address for a given implementation. - * @param _code The code of the wrapper implementation - * @param _salt The deployment salt - * @return proxyAddr The address of the deployed wrapper - */ - function _predictProxyAddress(bytes memory _code, bytes32 _salt) private view returns (address proxyAddr) { - address deployer = address(this); - bytes32 data = keccak256(abi.encodePacked(bytes1(0xff), deployer, _salt, keccak256(_code))); - return address(uint160(uint256(data))); - } - - /** - * Returns the code of the proxy contract for a given implementation - * @param _implAddr The address of the proxy implementation - * @return code The code of the proxy contract - */ - function _getProxyCode(address _implAddr) private pure returns (bytes memory code) { - return abi.encodePacked(type(Proxy).creationCode, abi.encode(_implAddr)); - } - - /** - * Checks if an address is a contract - * @param _addr The address to check - * @return result True if the address is a contract - */ - function _isContract(address _addr) internal view returns (bool result) { - uint256 csize; - // solhint-disable-next-line no-inline-assembly - assembly { - csize := extcodesize(_addr) - } - return csize != 0; - } -} diff --git a/src/proxies/ERC1967/README.md b/src/proxies/ERC1967/README.md deleted file mode 100644 index 378ff8c..0000000 --- a/src/proxies/ERC1967/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# ERC1967 Proxies - -This subsection of the repository contains the implementation of [ERC1967 proxy contracts](https://eips.ethereum.org/EIPS/eip-1967). ERC1967 defines a standard for storage slots of upgradeable smart contract proxies. These proxies delegate calls to an implementation contract, which allows the logic of the contract to be upgraded without changing the address of the contract. - -## Features - -* **IERC1967**: This interface defines the standard events emitted by ERC1967 proxies - `Upgraded`, `AdminChanged`, and `BeaconUpgraded`. - -* **Proxy**: This is the core contract that acts as a ERC1967 proxy. It contains logic to forward calls to an implementation contract, allowing the contract to change its behavior over time without changing its address. - -* **ProxyDeployer**: This contract provides a helper function for deploying new proxies. It contains the logic to compute the address of a proxy before it is deployed, as well as a function to check if an address is a contract. - -**Note:** The current implementations do not support upgradeable proxies. - -## Usage - -To use the contracts in this section, import the desired contracts from the "proxies/ERC1967" directory and use the provided functions to deploy and interact with proxy contracts. For example: - -```solidity -import {ProxyDeployer} from "./proxies/ERC1967/ProxyDeployer.sol"; - -contract MyContractFactory is ProxyDeployer { - function deployNewContract(address implementation) public returns (address) { - return _deployProxy(implementation, keccak256(abi.encode(msg.sender))); - } -} -``` diff --git a/src/proxies/README.md b/src/proxies/README.md new file mode 100644 index 0000000..3272733 --- /dev/null +++ b/src/proxies/README.md @@ -0,0 +1,36 @@ +# Proxies + +This subsection of the repository contains the implementation of proxy contracts. +These proxies delegate calls to an implementation contract, which allows the logic of the contract to be upgraded without changing the address of the contract. + +## Features + +### Transparent Upgradeable Beacon Proxy + +This proxy follows the [Transparent Upgradeable Proxy](https://docs.openzeppelin.com/contracts/4.x/api/proxy#TransparentUpgradeableProxy) pattern from OpenZeppelin, unless the implementation is unset, then it uses the [Beacon Proxy](https://docs.openzeppelin.com/contracts/4.x/api/proxy#BeaconProxy) pattern. + +This allows multiple proxies to be upgraded simultaneously, while also allowing individual upgrades to contracts. + +### Sequence Proxy Factory + +A factory for deploying Transparent Upgradeable Beacon Proxies. + +## Usage + +To use the contracts in this section, import the Sequence Proxy Factory contract and use the call the internal functions. For example: + +```solidity +import {SequenceProxyFactory} from "./proxies/SequenceProxyFactory.sol"; + +contract MyContractFactory is SequenceProxyFactory { + + constructor(address factoryOwner) { + MyImplementation impl = new MyImplementation(); + SequenceProxyFactory._initialize(address(impl), factoryOwner); + } + + function deploy(address proxyOwner, bytes32 salt) external returns (address) { + return _createProxy(salt, proxyOwner, ""); + } +} +``` diff --git a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol index 8677e3d..281e908 100644 --- a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol +++ b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol @@ -2,38 +2,40 @@ pragma solidity ^0.8.17; import {ERC1155TokenMinter} from "@0xsequence/contracts-library/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol"; -import {IERC1155TokenMinterFactory} from "@0xsequence/contracts-library/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol"; -import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; +import {IERC1155TokenMinterFactory} from + "@0xsequence/contracts-library/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol"; +import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/SequenceProxyFactory.sol"; /** * Deployer of ERC-1155 Token Minter proxies. */ -contract ERC1155TokenMinterFactory is IERC1155TokenMinterFactory, ProxyDeployer { - address private immutable implAddr; - +contract ERC1155TokenMinterFactory is IERC1155TokenMinterFactory, SequenceProxyFactory { /** * Creates an ERC-1155 Token Minter Factory. + * @param factoryOwner The owner of the ERC-1155 Token Minter Factory */ - constructor() { - ERC1155TokenMinter proxyImpl = new ERC1155TokenMinter(); - implAddr = address(proxyImpl); + constructor(address factoryOwner) { + ERC1155TokenMinter impl = new ERC1155TokenMinter(); + SequenceProxyFactory._initialize(address(impl), factoryOwner); } /** * Creates an ERC-1155 Token Minter proxy. - * @param owner The owner of the ERC-1155 Token Minter proxy + * @param proxyOwner The owner of the ERC-1155 Token Minter proxy + * @param tokenOwner The owner of the ERC-1155 Token Minter implementation * @param name The name of the ERC-1155 Token Minter proxy * @param baseURI The base URI of the ERC-1155 Token Minter proxy * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Token Minter Proxy * @dev The provided `salt` is hashed with the caller address for security. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. */ - function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) + function deploy(address proxyOwner, address tokenOwner, string memory name, string memory baseURI, bytes32 salt) external returns (address proxyAddr) { - proxyAddr = _deployProxy(implAddr, keccak256(abi.encode(msg.sender, salt))); - ERC1155TokenMinter(proxyAddr).initialize(owner, name, baseURI); + proxyAddr = _createProxy(salt, proxyOwner, ""); + ERC1155TokenMinter(proxyAddr).initialize(tokenOwner, name, baseURI); emit ERC1155TokenMinterDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol index 63495a2..9dc0ff8 100644 --- a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol +++ b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol @@ -4,13 +4,16 @@ pragma solidity ^0.8.17; interface IERC1155TokenMinterFactoryFunctions { /** * Creates an ERC-1155 Token Minter proxy. - * @param owner The owner of the ERC-1155 Token Minter proxy + * @param proxyOwner The owner of the ERC-1155 Token Minter proxy + * @param tokenOwner The owner of the ERC-1155 Token Minter implementation * @param name The name of the ERC-1155 Token Minter proxy * @param baseURI The base URI of the ERC-1155 Token Minter proxy * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Token Minter Proxy + * @dev The provided `salt` is hashed with the caller address for security. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. */ - function deploy(address owner, string memory name, string memory baseURI, bytes32 salt) + function deploy(address proxyOwner, address tokenOwner, string memory name, string memory baseURI, bytes32 salt) external returns (address proxyAddr); } diff --git a/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol index 9e093f8..80134b4 100644 --- a/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol @@ -3,36 +3,37 @@ pragma solidity ^0.8.17; import {ERC1155Sale} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/ERC1155Sale.sol"; import {IERC1155SaleFactory} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol"; -import {ProxyDeployer} from "@0xsequence/contracts-library//proxies/ERC1967/ProxyDeployer.sol"; +import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/SequenceProxyFactory.sol"; /** * Deployer of ERC-1155 Sale proxies. */ -contract ERC1155SaleFactory is IERC1155SaleFactory, ProxyDeployer { - address private immutable _implAddr; - +contract ERC1155SaleFactory is IERC1155SaleFactory, SequenceProxyFactory { /** * Creates an ERC-1155 Sale Factory. + * @param factoryOwner The owner of the ERC-1155 Sale Factory */ - constructor() { - ERC1155Sale proxyImpl = new ERC1155Sale(); - _implAddr = address(proxyImpl); + constructor(address factoryOwner) { + ERC1155Sale impl = new ERC1155Sale(); + SequenceProxyFactory._initialize(address(impl), factoryOwner); } /** * Creates an ERC-1155 Sale proxy contract - * @param _owner The owner of the ERC-1155 Sale - * @param _name The name of the ERC-1155 Sale token - * @param _baseURI The base URI of the ERC-1155 Sale token - * @param _salt The deployment salt + * @param proxyOwner The owner of the ERC-1155 Sale proxy + * @param tokenOwner The owner of the ERC-1155 Sale implementation + * @param name The name of the ERC-1155 Sale token + * @param baseURI The base URI of the ERC-1155 Sale token + * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Sale Proxy + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. */ - function deployERC1155Sale(address _owner, string memory _name, string memory _baseURI, bytes32 _salt) + function deploy(address proxyOwner, address tokenOwner, string memory name, string memory baseURI, bytes32 salt) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, _salt); - ERC1155Sale(proxyAddr).initialize(_owner, _name, _baseURI); + proxyAddr = _createProxy(salt, proxyOwner, ""); + ERC1155Sale(proxyAddr).initialize(tokenOwner, name, baseURI); emit ERC1155SaleDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol index 6e7ad89..d5b393d 100644 --- a/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol @@ -4,13 +4,15 @@ pragma solidity ^0.8.17; interface IERC1155SaleFactoryFunctions { /** * Creates an ERC-1155 Sale proxy contract - * @param owner The owner of the ERC-1155 Sale + * @param proxyOwner The owner of the ERC-1155 Sale proxy + * @param tokenOwner The owner of the ERC-1155 Sale implementation * @param name The name of the ERC-1155 Sale token * @param baseURI The base URI of the ERC-1155 Sale token * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Sale Proxy + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. */ - function deployERC1155Sale(address owner, string memory name, string memory baseURI, bytes32 salt) + function deploy(address proxyOwner, address tokenOwner, string memory name, string memory baseURI, bytes32 salt) external returns (address proxyAddr); } diff --git a/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol b/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol index eb53e50..be95da7 100644 --- a/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol +++ b/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol @@ -2,39 +2,48 @@ pragma solidity ^0.8.17; import {ERC20TokenMinter} from "@0xsequence/contracts-library/tokens/ERC20/presets/minter/ERC20TokenMinter.sol"; -import {IERC20TokenMinterFactory} from "@0xsequence/contracts-library/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol"; -import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; +import {IERC20TokenMinterFactory} from + "@0xsequence/contracts-library/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol"; +import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/SequenceProxyFactory.sol"; /** * Deployer of ERC-20 Token Minter proxies. */ -contract ERC20TokenMinterFactory is IERC20TokenMinterFactory, ProxyDeployer { - address private immutable _implAddr; - +contract ERC20TokenMinterFactory is IERC20TokenMinterFactory, SequenceProxyFactory { /** * Creates an ERC-20 Token Minter Factory. + * @param factoryOwner The owner of the ERC-20 Token Minter Factory */ - constructor() { - ERC20TokenMinter proxyImpl = new ERC20TokenMinter(); - _implAddr = address(proxyImpl); + constructor(address factoryOwner) { + ERC20TokenMinter impl = new ERC20TokenMinter(); + SequenceProxyFactory._initialize(address(impl), factoryOwner); } /** * Creates an ERC-20 Token Minter proxy. - * @param owner The owner of the ERC-20 Token Minter proxy + * @param proxyOwner The owner of the ERC-20 Token Minter proxy + * @param tokenOwner The owner of the ERC-20 Token Minter implementation * @param name The name of the ERC-20 Token Minter proxy * @param symbol The symbol of the ERC-20 Token Minter proxy * @param decimals The decimals of the ERC-20 Token Minter proxy * @param salt The deployment salt * @return proxyAddr The address of the ERC-20 Token Minter Proxy * @dev The provided `salt` is hashed with the caller address for security. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. */ - function deploy(address owner, string memory name, string memory symbol, uint8 decimals, bytes32 salt) + function deploy( + address proxyOwner, + address tokenOwner, + string memory name, + string memory symbol, + uint8 decimals, + bytes32 salt + ) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); - ERC20TokenMinter(proxyAddr).initialize(owner, name, symbol, decimals); + proxyAddr = _createProxy(salt, proxyOwner, ""); + ERC20TokenMinter(proxyAddr).initialize(tokenOwner, name, symbol, decimals); emit ERC20TokenMinterDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol b/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol index 6e0d6b6..4ce9483 100644 --- a/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol +++ b/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol @@ -2,23 +2,31 @@ pragma solidity ^0.8.17; interface IERC20TokenMinterFactoryFunctions { - /** * Creates an ERC-20 Token Minter proxy. - * @param owner The owner of the ERC-20 Token Minter proxy + * @param proxyOwner The owner of the ERC-20 Token Minter proxy + * @param tokenOwner The owner of the ERC-20 Token Minter implementation * @param name The name of the ERC-20 Token Minter proxy * @param symbol The symbol of the ERC-20 Token Minter proxy * @param decimals The decimals of the ERC-20 Token Minter proxy * @param salt The deployment salt * @return proxyAddr The address of the ERC-20 Token Minter Proxy + * @dev The provided `salt` is hashed with the caller address for security. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. */ - function deploy(address owner, string memory name, string memory symbol, uint8 decimals, bytes32 salt) + function deploy( + address proxyOwner, + address tokenOwner, + string memory name, + string memory symbol, + uint8 decimals, + bytes32 salt + ) external returns (address proxyAddr); } interface IERC20TokenMinterFactorySignals { - /** * Event emitted when a new ERC-20 Token Minter proxy contract is deployed. * @param proxyAddr The address of the deployed proxy. diff --git a/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol index 0338dc8..822ac62 100644 --- a/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol @@ -1,40 +1,50 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; -import {IERC721TokenMinterFactory} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol"; +import {IERC721TokenMinterFactory} from + "@0xsequence/contracts-library/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol"; import {ERC721TokenMinter} from "@0xsequence/contracts-library/tokens/ERC721/presets/minter/ERC721TokenMinter.sol"; -import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; +import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/SequenceProxyFactory.sol"; /** * Deployer of ERC-721 Token Minter proxies. */ -contract ERC721TokenMinterFactory is IERC721TokenMinterFactory, ProxyDeployer { - address private immutable _implAddr; +contract ERC721TokenMinterFactory is IERC721TokenMinterFactory, SequenceProxyFactory { /** * Creates an ERC-721 Token Minter Factory. + * @param factoryOwner The owner of the ERC-721 Token Minter Factory */ - constructor() { - ERC721TokenMinter proxyImpl = new ERC721TokenMinter(); - _implAddr = address(proxyImpl); + constructor(address factoryOwner) { + ERC721TokenMinter impl = new ERC721TokenMinter(); + SequenceProxyFactory._initialize(address(impl), factoryOwner); } /** * Creates an ERC-721 Token Minter proxy. - * @param owner The owner of the ERC-721 Token Minter proxy + * @param proxyOwner The owner of the ERC-721 Token Minter proxy + * @param tokenOwner The owner of the ERC-721 Token Minter implementation * @param name The name of the ERC-721 Token Minter proxy * @param symbol The symbol of the ERC-721 Token Minter proxy * @param baseURI The base URI of the ERC-721 Token Minter proxy * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Token Minter Proxy * @dev The provided `salt` is hashed with the caller address for security. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. */ - function deploy(address owner, string memory name, string memory symbol, string memory baseURI, bytes32 salt) + function deploy( + address proxyOwner, + address tokenOwner, + string memory name, + string memory symbol, + string memory baseURI, + bytes32 salt + ) external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, keccak256(abi.encode(msg.sender, salt))); - ERC721TokenMinter(proxyAddr).initialize(owner, name, symbol, baseURI); + proxyAddr = _createProxy(salt, proxyOwner, ""); + ERC721TokenMinter(proxyAddr).initialize(tokenOwner, name, symbol, baseURI); emit ERC721TokenMinterDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol index a83acfb..8a3da54 100644 --- a/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol +++ b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol @@ -2,16 +2,27 @@ pragma solidity ^0.8.17; interface IERC721TokenMinterFactoryFunctions { + /** * Creates an ERC-721 Token Minter proxy. - * @param owner The owner of the ERC-721 Token Minter proxy + * @param proxyOwner The owner of the ERC-721 Token Minter proxy + * @param tokenOwner The owner of the ERC-721 Token Minter implementation * @param name The name of the ERC-721 Token Minter proxy * @param symbol The symbol of the ERC-721 Token Minter proxy * @param baseURI The base URI of the ERC-721 Token Minter proxy * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Token Minter Proxy + * @dev The provided `salt` is hashed with the caller address for security. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. */ - function deploy(address owner, string memory name, string memory symbol, string memory baseURI, bytes32 salt) + function deploy( + address proxyOwner, + address tokenOwner, + string memory name, + string memory symbol, + string memory baseURI, + bytes32 salt + ) external returns (address proxyAddr); } diff --git a/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol index c7bd7d9..494b19b 100644 --- a/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol +++ b/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol @@ -3,33 +3,36 @@ pragma solidity ^0.8.17; import {ERC721Sale} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/ERC721Sale.sol"; import {IERC721SaleFactory} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/IERC721SaleFactory.sol"; -import {ProxyDeployer} from "@0xsequence/contracts-library/proxies/ERC1967/ProxyDeployer.sol"; +import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/SequenceProxyFactory.sol"; /** * Deployer of ERC-721 Sale proxies. */ -contract ERC721SaleFactory is IERC721SaleFactory, ProxyDeployer { - address private immutable _implAddr; +contract ERC721SaleFactory is IERC721SaleFactory, SequenceProxyFactory { /** * Creates an ERC-721 Sale Factory. + * @param factoryOwner The owner of the ERC-721 Sale Factory */ - constructor() { - ERC721Sale proxyImpl = new ERC721Sale(); - _implAddr = address(proxyImpl); + constructor(address factoryOwner) { + ERC721Sale impl = new ERC721Sale(); + SequenceProxyFactory._initialize(address(impl), factoryOwner); } /** * Creates an ERC-721 Sale for given token contract - * @param owner The owner of the ERC-721 Sale + * @param proxyOwner The owner of the ERC-721 Sale proxy + * @param tokenOwner The owner of the ERC-721 Sale implementation * @param name The name of the ERC-721 Sale token * @param symbol The symbol of the ERC-721 Sale token * @param baseURI The base URI of the ERC-721 Sale token * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Sale Proxy + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-721 Sale functions. */ - function deployERC721Sale( - address owner, + function deploy( + address proxyOwner, + address tokenOwner, string memory name, string memory symbol, string memory baseURI, @@ -38,8 +41,8 @@ contract ERC721SaleFactory is IERC721SaleFactory, ProxyDeployer { external returns (address proxyAddr) { - proxyAddr = _deployProxy(_implAddr, salt); - ERC721Sale(proxyAddr).initialize(owner, name, symbol, baseURI); + proxyAddr = _createProxy(salt, proxyOwner, ""); + ERC721Sale(proxyAddr).initialize(tokenOwner, name, symbol, baseURI); emit ERC721SaleDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol index 760da51..8a14e46 100644 --- a/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol +++ b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol @@ -4,16 +4,19 @@ pragma solidity ^0.8.17; interface IERC721SaleFactoryFunctions { /** - * Creates an ERC-721 Sale. - * @param owner The owner of the ERC-721 Sale + * Creates an ERC-721 Sale for given token contract + * @param proxyOwner The owner of the ERC-721 Sale proxy + * @param tokenOwner The owner of the ERC-721 Sale implementation * @param name The name of the ERC-721 Sale token * @param symbol The symbol of the ERC-721 Sale token * @param baseURI The base URI of the ERC-721 Sale token * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Sale Proxy + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-721 Sale functions. */ - function deployERC721Sale( - address owner, + function deploy( + address proxyOwner, + address tokenOwner, string memory name, string memory symbol, string memory baseURI, diff --git a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol index 5051561..2ea249c 100644 --- a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol @@ -31,9 +31,13 @@ contract ERC1155SaleTest is Test, Merkle, IERC1155SaleSignals, IERC1155SupplySig ERC20Mock private erc20; uint256 private perTokenCost = 0.02 ether; + address private proxyOwner; + address private constant ALLOWLIST_ADDR = 0xFA4eE536359087Fba7BD3248EE09e8Cc8347F8Ed; function setUp() public { + proxyOwner = makeAddr("proxyOwner"); + token = new ERC1155Sale(); token.initialize(address(this), "test", "ipfs://"); @@ -41,8 +45,8 @@ contract ERC1155SaleTest is Test, Merkle, IERC1155SaleSignals, IERC1155SupplySig } function setUpFromFactory() public { - ERC1155SaleFactory factory = new ERC1155SaleFactory(); - token = ERC1155Sale(factory.deployERC1155Sale(address(this), "test", "ipfs://", "")); + ERC1155SaleFactory factory = new ERC1155SaleFactory(address(this)); + token = ERC1155Sale(factory.deploy(proxyOwner, address(this), "test", "ipfs://", "")); } function testSupportsInterface() public { @@ -599,8 +603,7 @@ contract ERC1155SaleTest is Test, Merkle, IERC1155SaleSignals, IERC1155SupplySig public withFactory(useFactory) { - // Address 9 doesnt receive ETH - vm.assume(withdrawTo != address(9)); + vm.assume(uint160(withdrawTo) > 16); testMintSingleSuccess(false, withdrawTo, tokenId, amount); uint256 tokenBalance = address(token).balance; @@ -635,6 +638,7 @@ contract ERC1155SaleTest is Test, Merkle, IERC1155SaleSignals, IERC1155SupplySig modifier assumeSafe(address nonContract, uint256 tokenId, uint256 amount) { vm.assume(uint160(nonContract) > 16); + vm.assume(nonContract != proxyOwner); vm.assume(nonContract.code.length == 0); vm.assume(tokenId < 100); vm.assume(amount > 0 && amount < 20); diff --git a/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol b/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol index 930dd80..80e0831 100644 --- a/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol @@ -24,16 +24,18 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { ERC1155TokenMinter private token; + address private proxyOwner; address private owner; function setUp() public { owner = makeAddr("owner"); + proxyOwner = makeAddr("proxyOwner"); vm.deal(address(this), 100 ether); vm.deal(owner, 100 ether); - ERC1155TokenMinterFactory factory = new ERC1155TokenMinterFactory(); - token = ERC1155TokenMinter(factory.deploy(owner, "name", "baseURI", 0x0)); + ERC1155TokenMinterFactory factory = new ERC1155TokenMinterFactory(address(this)); + token = ERC1155TokenMinter(factory.deploy(proxyOwner, owner, "name", "baseURI", 0x0)); } function testReinitializeFails() public { @@ -59,6 +61,7 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { // function testMintInvalidRole(address caller) public { vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.expectRevert( abi.encodePacked( @@ -122,6 +125,7 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { function testMintWithRole(address minter, uint256 tokenId, uint256 amount) public { vm.assume(minter != owner); + vm.assume(minter != proxyOwner); vm.assume(minter != address(0)); // Give role vm.startPrank(owner); @@ -139,6 +143,7 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { function testBatchMintWithRole(address minter, uint256[] memory tokenIds, uint256[] memory amounts) public { vm.assume(minter != owner); + vm.assume(minter != proxyOwner); vm.assume(minter != address(0)); tokenIds = boundArrayLength(tokenIds, 10); amounts = boundArrayLength(amounts, 10); @@ -179,6 +184,7 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { function testMetadataInvalid(address caller) public { vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.expectRevert( abi.encodePacked( "AccessControl: account ", @@ -193,6 +199,7 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { function testMetadataWithRole(address caller) public { vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.assume(caller != address(0)); // Give role vm.startPrank(owner); @@ -249,6 +256,7 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { vm.assume(feeNumerator <= 10000); vm.assume(receiver != address(0)); vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.assume(salePrice < type(uint128).max); // Buffer for overflow vm.startPrank(owner); @@ -282,6 +290,7 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { vm.assume(feeNumerator <= 10000); vm.assume(receiver != address(0)); vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.assume(salePrice < type(uint128).max); // Buffer for overflow vm.expectRevert( diff --git a/test/tokens/ERC20/ERC20TokenMinter.t.sol b/test/tokens/ERC20/ERC20TokenMinter.t.sol index b01f26c..792caf2 100644 --- a/test/tokens/ERC20/ERC20TokenMinter.t.sol +++ b/test/tokens/ERC20/ERC20TokenMinter.t.sol @@ -21,16 +21,18 @@ contract ERC20TokenMinterTest is Test, IERC20TokenMinterSignals { uint8 private constant DECIMALS = 18; - address owner; + address private proxyOwner; + address private owner; // Token owner function setUp() public { owner = makeAddr("owner"); + proxyOwner = makeAddr("proxyOwner"); vm.deal(address(this), 100 ether); vm.deal(owner, 100 ether); - ERC20TokenMinterFactory factory = new ERC20TokenMinterFactory(); - token = ERC20TokenMinter(factory.deploy(owner, "name", "symbol", DECIMALS, 0x0)); + ERC20TokenMinterFactory factory = new ERC20TokenMinterFactory(address(this)); + token = ERC20TokenMinter(factory.deploy(proxyOwner, owner, "name", "symbol", DECIMALS, 0x0)); } function testReinitializeFails() public { @@ -60,6 +62,7 @@ contract ERC20TokenMinterTest is Test, IERC20TokenMinterSignals { // function testMintInvalidRole(address caller, uint256 amount) public { vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.assume(amount > 0); vm.expectRevert( @@ -88,6 +91,7 @@ contract ERC20TokenMinterTest is Test, IERC20TokenMinterSignals { function testMintWithRole(address minter, uint256 amount) public { vm.assume(minter != owner); + vm.assume(minter != proxyOwner); vm.assume(minter != address(0)); vm.assume(amount > 0); // Give role diff --git a/test/tokens/ERC721/presets/ERC721Sale.t.sol b/test/tokens/ERC721/presets/ERC721Sale.t.sol index d28840d..91cf73d 100644 --- a/test/tokens/ERC721/presets/ERC721Sale.t.sol +++ b/test/tokens/ERC721/presets/ERC721Sale.t.sol @@ -29,7 +29,11 @@ contract ERC721SaleTest is Test, Merkle, IERC721SaleSignals, IMerkleProofSingleU ERC20Mock private erc20; uint256 private perTokenCost = 0.02 ether; + address private proxyOwner; + function setUp() public { + proxyOwner = makeAddr("proxyOwner"); + token = new ERC721Sale(); token.initialize(address(this), "test", "test", "ipfs://"); @@ -37,8 +41,8 @@ contract ERC721SaleTest is Test, Merkle, IERC721SaleSignals, IMerkleProofSingleU } function setUpFromFactory() public { - ERC721SaleFactory factory = new ERC721SaleFactory(); - token = ERC721Sale(factory.deployERC721Sale(address(this), "test", "test", "ipfs://", "")); + ERC721SaleFactory factory = new ERC721SaleFactory(address(this)); + token = ERC721Sale(factory.deploy(proxyOwner, address(this), "test", "test", "ipfs://", "")); } function testSupportsInterface() public { diff --git a/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol b/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol index 008ab38..cacbcd2 100644 --- a/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol +++ b/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol @@ -19,16 +19,18 @@ contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { ERC721TokenMinter private token; - address owner; + address private proxyOwner; + address private owner; function setUp() public { owner = makeAddr("owner"); + proxyOwner = makeAddr("proxyOwner"); vm.deal(address(this), 100 ether); vm.deal(owner, 100 ether); - ERC721TokenMinterFactory factory = new ERC721TokenMinterFactory(); - token = ERC721TokenMinter(factory.deploy(owner, "name", "symbol", "baseURI", 0x0)); + ERC721TokenMinterFactory factory = new ERC721TokenMinterFactory(address(this)); + token = ERC721TokenMinter(factory.deploy(proxyOwner, owner, "name", "symbol", "baseURI", 0x0)); } function testReinitializeFails() public { @@ -61,6 +63,7 @@ contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { // function testMintInvalidRole(address caller) public { vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.expectRevert( abi.encodePacked( @@ -86,6 +89,7 @@ contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { function testMintWithRole(address minter) public { vm.assume(minter != owner); + vm.assume(minter != proxyOwner); vm.assume(minter != address(0)); // Give role vm.startPrank(owner); @@ -136,6 +140,7 @@ contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { function testMetadataInvalid(address caller) public { vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.expectRevert( abi.encodePacked( "AccessControl: account ", @@ -150,6 +155,7 @@ contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { function testMetadataWithRole(address caller) public { vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.assume(caller != address(0)); // Give role vm.startPrank(owner); @@ -206,6 +212,7 @@ contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { vm.assume(feeNumerator <= 10000); vm.assume(receiver != address(0)); vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.assume(salePrice < type(uint128).max); // Buffer for overflow vm.startPrank(owner); @@ -239,6 +246,7 @@ contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { vm.assume(feeNumerator <= 10000); vm.assume(receiver != address(0)); vm.assume(caller != owner); + vm.assume(caller != proxyOwner); vm.assume(salePrice < type(uint128).max); // Buffer for overflow vm.expectRevert( From 572a35a83541e362483a4b9ee50234cfa05de43f Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Fri, 4 Aug 2023 11:02:44 +1200 Subject: [PATCH 43/49] Update READMEs --- README.md | 14 ++++----- src/tokens/ERC1155/README.md | 32 ++++++++++---------- src/tokens/ERC20/README.md | 27 ++++++----------- src/tokens/ERC721/README.md | 33 +++++++++++---------- src/tokens/common/README.md | 57 ++++++++++++++++++++++++++++++++++-- 5 files changed, 104 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 1e263d1..6aa9787 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ This repository provides a set of smart contracts to facilitate the creation and ## Features -* **ERC20TokenFactory**: Allows for the easy creation of new ERC20 tokens through a factory contract and also provides functionality for minting new tokens. +Base and preset **implementations of common token standards**: -* **ERC721TokenFactory**: Similar to the ERC20TokenFactory, but for ERC721 (non-fungible) tokens. It allows for the creation and minting of ERC721 tokens, and also supports ERC2981 royalty information. +* ERC-20 +* ERC-721 +* ERC-1155 -* **ERC1155TokenFactory**: A factory for creating ERC1155 tokens, which can represent semi-fungible items. This contract also supports minting and updating metadata, as well as ERC2981 royalty information. +**Common token functionality**, such as the `ERC2981-Controlled` contract which provides a way to handle royalties in NFTs. -* **Common Token Functionality**: This contains contracts that can be used for additional functionalities, such as the `ERC2981Controlled` contract which provides a way to handle royalties in NFTs. - -* **Proxies**: This section contains contracts implementing ERC1967 compliant proxies for upgradeability. +**Proxy** contracts and factories implementing ERC-1967 and with upgradeability. ## Usage @@ -39,7 +39,7 @@ yarn deploy ## Dependencies -The contracts in this repository are built with Solidity ^0.8.17 and use OpenZeppelin and Azuki contracts for standards implementation and additional functionalities such as access control. +The contracts in this repository are built with Solidity ^0.8.17 and use 0xSequence, OpenZeppelin and Azuki contracts for standards implementation and additional functionalities such as access control. ## License diff --git a/src/tokens/ERC1155/README.md b/src/tokens/ERC1155/README.md index e1ba95c..6e495be 100644 --- a/src/tokens/ERC1155/README.md +++ b/src/tokens/ERC1155/README.md @@ -4,37 +4,35 @@ This subsection contains contracts related to the [ERC1155 token standard](https ## ERC1155Token -This contract is a complete, ready-to-use implementation of the ERC-1155 token standard. It includes additional features from the ERC1155MintBurn, ERC1155Meta, and ERC1155Metadata contracts. These contracts provide minting capabilities, support for meta transactions, and metadata functionality. +This contract is a base implementation of the ERC-1155 token standard. It includes role based access control features from the [OpenZeppelin AccessControl](https://docs.openzeppelin.com/contracts/4.x/access-control) contract, to provide control over added features. Please refer to OpenZeppelin documentation for more information on AccessControl. -Meta transactions are provided by the [0xSequence ERC1155 library](https://github.com/0xsequence/erc-1155/blob/master/SPECIFICATIONS.md#meta-transactions). Please refer to library documentation for more information on meta transactions. +This contracts provide minting capabilities, support for meta transactions, and metadata functionality. It includes additional features from the ERC1155MintBurn, ERC1155Meta, and ERC1155Metadata contracts. Meta transactions are provided by the [0xSequence ERC1155 library](https://github.com/0xsequence/erc-1155/blob/master/SPECIFICATIONS.md#meta-transactions). Please refer to library documentation for more information on meta transactions. -The ERC1155Token contract has a two-step deployment process. First, it's deployed with an empty constructor. After deployment, the `initialize` function must be called to set the owner, name, and base URI. This process is in place to support proxy deployments with the ERC1155TokenFactory. +The contract supports the [ERC2981 token royalty standard](https://eips.ethereum.org/EIPS/eip-2981) via the ERC2981Controlled contract. Please refer to the ERC2981Controlled documentation for more information on token royalty. -### Functions +## Presets -* `initialize(address owner, string memory name_, string memory baseURI_)`: Initializes the token contract, setting the owner, name, and base URI. -* `mint(address to, uint256 tokenId, uint256 amount, bytes memory data)`: Mints the specified amount of tokens of a given ID to the specified address. This function is restricted to addresses with the Minter role. -* `batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data)`: Mints specified amounts of tokens of given IDs to the specified address. This function is restricted to addresses with the Minter role. -* `setBaseMetadataURI(string memory baseURI_)`: Updates the base URI for the token metadata. This function is restricted to addresses with the Metadata Admin role. -* `setContractName(string memory name_)`: Updates the contract's name. This function is restricted to addresses with the Metadata Admin role. +This folder contains contracts that are pre-configured for specific use cases. -## ERC1155TokenFactory +### Minter -This contract deploys ERC1155Token contracts. It uses a proxy pattern to create new token instances to reduce gas costs. +The `ERC1155TokenMinter` contract is a preset that configures the `ERC1155Token` contract to allow minting of tokens. It adds a `MINTER_ROLE` and a `mint(address to, uint256 amount)` function that can only be called by accounts with the `MINTER_ROLE`. -The deployment uses a `salt` which is combined with the caller's address for cross chain consistency and security. +### Sale -### Functions +The `ERC1155TokenSale` contract is a preset that configures the `ERC1155Token` contract to allow for the sale of tokens. It adds a `mint(address to, , uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data, bytes32[] calldata proof)` function allows for the minting of tokens under various conditions. -* `deploy(address owner, string memory name, string memory baseURI, bytes32 salt)`: Deploys a new ERC1155Token proxy contract, initializes it, and emits an ERC1155TokenDeployed event. +Conditions may be set by the contract owner using either the `setTokenSaleDetails(uint256 tokenId, uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime, bytes32 merkleRoot)` function for single token settings or the `setGlobalSaleDetails(uint256 cost, uint256 supplyCap, address paymentTokenAddr, uint64 startTime, uint64 endTime, bytes32 merkleRoot)` function for global settings. These functions can only be called by accounts with the `MINT_ADMIN_ROLE`. + +For information about the function parameters, please refer to the function specification in `presets/sale/IERC1155Sale.sol`. ## Usage -To create a new ERC1155 token: +This section of this repo utilitizes a factory pattern that deploys proxies contracts. This allows for a single deployment of each `Factory` contract, and subsequent deployments of the contracts with minimal gas costs. -1. Deploy the ERC1155TokenFactory contract (or use an existing deployment). +1. Deploy the `[XXX]Factory` contract for the contract you wish to use (or use an existing deployment). 2. Call the `deploy` function on the factory, providing the desired parameters. -3. A new ERC1155Token contract will be created and initialized, ready for use. +3. A new contract will be created and initialized, ready for use. ## Dependencies diff --git a/src/tokens/ERC20/README.md b/src/tokens/ERC20/README.md index 879d44b..2c12642 100644 --- a/src/tokens/ERC20/README.md +++ b/src/tokens/ERC20/README.md @@ -4,33 +4,24 @@ This subsection contains contracts related to the [ERC20 token standard](https:/ ## ERC20Token -This contract is a complete, ready-to-use implementation of the ERC-20 token standard. It includes role based access control features from the [OpenZeppelin AccessControl](https://docs.openzeppelin.com/contracts/4.x/access-control) contract, providing control over minting operations. Please refer to OpenZeppelin documentation for more information on AccessControl. +This contract is a base implementation of the ERC-20 token standard. It includes role based access control features from the [OpenZeppelin AccessControl](https://docs.openzeppelin.com/contracts/4.x/access-control) contract, to provide control over added features. Please refer to OpenZeppelin documentation for more information on AccessControl. -The ERC20Token contract has a two-step deployment process. First, it's deployed with an empty constructor. After deployment, the `initialize` function must be called to set the owner, token name, symbol, and decimals. This process is in place to support proxy deployments with the ERC20TokenFactory. +## Presets -### Functions +This folder contains contracts that are pre-configured for specific use cases. -* `initialize(address owner, string memory tokenName_, string memory tokenSymbol_, uint8 tokenDecimals_)`: Initializes the token contract, setting the owner, name, symbol, and number of decimals. -* `mint(address to, uint256 amount)`: Mints the given amount of tokens toP the specified address. This function is restricted to addresses with the Minter role. +### Minter -## ERC20TokenFactory - -This contract deploys ERC20Token contracts. It uses a proxy pattern to create new token instances to reduce gas costs. - -The deployment uses a `salt` which is combined with the caller's address for cross chain consistency and security. - -### Functions - -* `deploy(address owner, string memory name, string memory symbol, uint8 decimals, bytes32 salt)`: Deploys a new ERC20Token proxy contract, initializes it, and emits an ERC20TokenDeployed event. +The `ERC20TokenMinter` contract is a preset that configures the `ERC20Token` contract to allow minting of tokens. It adds a `MINTER_ROLE` and a `mint(address to, uint256 amount)` function that can only be called by accounts with the `MINTER_ROLE`. ## Usage -To create a new ERC20 token: +This section of this repo utilitizes a factory pattern that deploys proxies contracts. This allows for a single deployment of each `Factory` contract, and subsequent deployments of the contracts with minimal gas costs. -1. Deploy the ERC20TokenFactory contract (or use an existing deployment). +1. Deploy the `[XXX]Factory` contract for the contract you wish to use (or use an existing deployment). 2. Call the `deploy` function on the factory, providing the desired parameters. -3. A new ERC20Token contract will be created and initialized, ready for use. +3. A new contract will be created and initialized, ready for use. ## Dependencies -This repo relies on the OpenZeppelin Contracts library, particularly the ERC20, IERC20, IERC20Metadata, and AccessControl contracts, which provide core ERC-20 functionality and secure access control mechanisms. +These contract relies on the OpenZeppelin Contracts library, particularly the ERC20, IERC20, IERC20Metadata, and AccessControl contracts, which provide core ERC-20 functionality and secure access control mechanisms. diff --git a/src/tokens/ERC721/README.md b/src/tokens/ERC721/README.md index 89fd7d7..e427a57 100644 --- a/src/tokens/ERC721/README.md +++ b/src/tokens/ERC721/README.md @@ -4,35 +4,38 @@ This subsection contains contracts related to the [ERC721 token standard](https: ## ERC721Token -This contract is a complete, ready-to-use implementation of the ERC-721 token standard. It leverages the [Azuki ERC-721A implementation](https://www.erc721a.org/) for gas efficiency. It includes role based access control features from the [OpenZeppelin AccessControl](https://docs.openzeppelin.com/contracts/4.x/access-control) contract, providing control over minting operations and metadata administration. Please refer to OpenZeppelin documentation for more information on AccessControl. - -The ERC721Token contract has a two-step deployment process. First, it's deployed with an empty constructor. After deployment, the `initialize` function must be called to set the owner, token name, symbol, and base URI. This process is in place to support proxy deployments with the ERC721TokenFactory. +This contract is a base implementation of the ERC-721 token standard. It leverages the [Azuki ERC-721A implementation](https://www.erc721a.org/) for gas efficiency. It includes role based access control features from the [OpenZeppelin AccessControl](https://docs.openzeppelin.com/contracts/4.x/access-control) contract, to provide control over added features. Please refer to OpenZeppelin documentation for more information on AccessControl. The contract supports the [ERC2981 token royalty standard](https://eips.ethereum.org/EIPS/eip-2981) via the ERC2981Controlled contract. Please refer to the ERC2981Controlled documentation for more information on token royalty. -### Functions +## Presets + +This folder contains contracts that are pre-configured for specific use cases. -* `initialize(address owner, string memory tokenName_, string memory tokenSymbol_, string memory baseURI_)`: Initializes the token contract, setting the owner, name, symbol, and base URI. -* `mint(address to, uint256 amount)`: Mints the given amount of tokens to the specified address. This function is restricted to addresses with the Minter role. -* `setBaseMetadataURI(string memory baseURI_)`: Updates the base URI for the token metadata. This function is restricted to addresses with the Metadata Admin role. +### Minter -## ERC721TokenFactory +The `ERC721TokenMinter` contract is a preset that configures the `ERC721Token` contract to allow minting of tokens. It adds a `MINTER_ROLE` and a `mint(address to, uint256 amount)` function that can only be called by accounts with the `MINTER_ROLE`. -This contract deploys ERC721Token contracts. It uses a proxy pattern to create new token instances to reduce gas costs. +### Sale -The deployment uses a `salt` which is combined with the caller's address for cross chain consistency and security. +The `ERC721TokenSale` contract is a preset that configures the `ERC721Token` contract to allow for the sale of tokens. It adds a `mint(address to, uint256 amount, bytes32[] memory proof)` function allows for the minting of tokens under various conditions. -### Functions +Conditions may be set by the contract owner using the `setSaleDetails(uint256 supplyCap, uint256 cost, address paymentToken, uint64 startTime, uint64 endTime, bytes32 merkleRoot)` function that can only be called by accounts with the `MINT_ADMIN_ROLE`. The variables function as follows: -* `deploy(address owner, string memory name, string memory symbol, string memory baseURI, bytes32 salt)`: Deploys a new ERC721Token proxy contract, initializes it, and emits an ERC721TokenDeployed event. +* supplyCap: The maximum number of tokens that can be minted. 0 indicates unlimited supply. +* cost: The amount of payment tokens to accept for each token minted. +* paymentToken: The ERC20 token address to accept payment in. address(0) indicates ETH. +* startTime: The start time of the sale. Tokens cannot be minted before this time. +* endTime: The end time of the sale. Tokens cannot be minted after this time. +* merkleRoot: The merkle root for allowlist minting. ## Usage -To create a new ERC721 token: +This section of this repo utilitizes a factory pattern that deploys proxies contracts. This allows for a single deployment of each `Factory` contract, and subsequent deployments of the contracts with minimal gas costs. -1. Deploy the ERC721TokenFactory contract (or use an existing deployment). +1. Deploy the `[XXX]Factory` contract for the contract you wish to use (or use an existing deployment). 2. Call the `deploy` function on the factory, providing the desired parameters. -3. A new ERC721Token contract will be created and initialized, ready for use. +3. A new contract will be created and initialized, ready for use. ## Dependencies diff --git a/src/tokens/common/README.md b/src/tokens/common/README.md index e3277d6..0c739a4 100644 --- a/src/tokens/common/README.md +++ b/src/tokens/common/README.md @@ -4,9 +4,9 @@ This section contains common contracts that can be used for additional functiona ## ERC2981Controlled -The `ERC2981Controlled` contract is an implementation of the [ERC2981 token royalty standard](https://eips.ethereum.org/EIPS/eip-2981), which provides a standardized way to handle royalties in NFTs and SFTs. +The `ERC2981Controlled` contract is an implementation of the [ERC2981 token royalty standard](https://eips.ethereum.org/EIPS/eip-2981), which provides a standardized way to handle royalties in NFTs and SFTs. -This contract allows the royalty information for the contract as a whole, or individual token IDs, to be updated by users with the `ROYALTY_ADMIN_ROLE`. +This contract allows the royalty information for the contract as a whole, or individual token IDs, to be updated by users with the `ROYALTY_ADMIN_ROLE`. ### Functions @@ -30,3 +30,56 @@ Alternatively, use the `ERC721Token` or `ERC1155Token` implementations which alr ### Dependencies The `ERC2981Controlled` contract depends on OpenZeppelin's `ERC2981` and `AccessControl` contracts. `ERC2981` provides the basic royalty-related functionality according to the standard, while `AccessControl` provides a flexible system of access control based on roles. + +## MerkleProofSingleUse + +The `MerkleProofSingleUse` contract provides a way to verify that a given value is included in a Merkle tree, and that it has not been used before. +This is useful for verifying that a given token ID has not been used before, for example in a claim process. + +### Functions + +* `checkMerkleProof(bytes32 root, bytes32[] calldata proof, address addr)`: An internal function that allows a contract to verify that a given value is included in a Merkle tree. The `root` is the Merkle root, the `proof` is the Merkle proof, and the `addr` is the value to verify. If the value is not included in the Merkle tree or if the proof has already been used, the function will return false. +* `requireMerkleProof(bytes32 root, bytes32[] calldata proof, address addr)`: An internal function that does the same as above, and also marks the proof has having been used by the address. + +### Usage + +To use this contract, it should be inherited by the main token contract and functions called as required. For example when minting in an NFT contract: + +```solidity +contract MyNFT is ERC721, MerkleProofSingleUse { + + function mint(address to, bytes32 root, bytes32[] calldata proof) public { + requireMerkleProof(root, proof, to); + _mint(to, tokenId); + } + + //... +} +``` + +### Dependencies + +The `MerkleProofSingleUse` contract depends on OpenZeppelin's `MerkleProof` contract. `MerkleProof` provides the basic Merkle proof verification functionality. + +## WithdrawControlled + +The `WithdrawControlled` contract provides a way to withdraw ETH and ERC20 tokens from a contract. This is useful for contracts that receive ETH or ERC20 tokens, and need to be able to withdraw them. + +### Functions + +* `withdrawETH(address payable to, uint256 value)`: Allows an address with the `WITHDRAW_ROLE` to withdraw ETH from the contract. The `to` parameter is the address to withdraw to, and the `value` is the amount to withdraw. +* `withdrawERC20(address token, address to, uint256 value)`: Allows an address with the `WITHDRAW_ROLE` to withdraw ERC20 tokens from the contract. The `token` parameter is the address of the ERC20 token to withdraw, the `to` parameter is the address to withdraw to, and the `value` is the amount to withdraw. + +### Usage + +To use this contract, it should be inherited by the main token contract. For example: + +```solidity +contract MyNFT is ERC721, WithdrawControlled { + // ... +} +``` + +### Dependencies + +The `WithdrawControlled` contract depends on OpenZeppelin's `AccessControl` contract. `AccessControl` provides a flexible system of access control based on roles. From 7c77d513fb4ffc1e00a64fc470f53014174ed9c6 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Mon, 25 Sep 2023 10:16:16 +1300 Subject: [PATCH 44/49] Update deploy script --- .env.example | 2 +- README.md | 2 -- scripts/constants.ts | 8 +++++--- scripts/deploy.ts | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 81486b9..38f9149 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,3 @@ PRIVATE_KEY= RPC_URL= - +FACTORY_OWNER= diff --git a/README.md b/README.md index 6aa9787..6d469d3 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,6 @@ Then run the deployment script. yarn deploy ``` -**Note:** The Factory contracts in this repository contain no state and are not ownable, as such they only need to be deployed once per network. The Factory contracts are then available to be used by anyone. - ## Dependencies The contracts in this repository are built with Solidity ^0.8.17 and use 0xSequence, OpenZeppelin and Azuki contracts for standards implementation and additional functionalities such as access control. diff --git a/scripts/constants.ts b/scripts/constants.ts index f8bfe34..2851ef0 100644 --- a/scripts/constants.ts +++ b/scripts/constants.ts @@ -1,6 +1,8 @@ export const BUILD_DIR = 'build' export const DEPLOYABLE_CONTRACT_NAMES = [ - 'ERC20TokenFactory', - 'ERC721TokenFactory', - 'ERC1155TokenFactory', + 'ERC20TokenMinterFactory', + 'ERC721TokenMinterFactory', + 'ERC721SaleFactory', + 'ERC1155TokenMinterFactory', + 'ERC1155SaleFactory', ] diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 0bec1d9..01117c0 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -13,7 +13,7 @@ import { JsonRpcProvider } from '@ethersproject/providers' dotenvConfig() -const { PRIVATE_KEY, RPC_URL } = process.env +const { PRIVATE_KEY, RPC_URL, FACTORY_OWNER } = process.env const MAX_GAS_LIMIT = 6000000 @@ -47,7 +47,7 @@ const singletonFactoryFactory = { } const main = async () => { - if (!PRIVATE_KEY || !RPC_URL) { + if (!PRIVATE_KEY || !RPC_URL || !FACTORY_OWNER) { throw new Error('Environment vars not set') } @@ -76,7 +76,7 @@ const main = async () => { } } const contract = new MyContractFactory(wallet) - const contractCode = contract.getDeployTransaction().data + const contractCode = contract.getDeployTransaction(FACTORY_OWNER).data if (!contractCode) { throw new Error(`${solFile} did not return contract code`) } From aca6b92717054c57654dc1768f3f11b420803f3b Mon Sep 17 00:00:00 2001 From: Michael Yu <1619025+acrylix@users.noreply.github.com> Date: Thu, 12 Oct 2023 15:02:57 -0400 Subject: [PATCH 45/49] make sure metadata output is included in forge build --- scripts/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.ts b/scripts/build.ts index ea61033..4add06e 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -15,7 +15,7 @@ const main = async () => { // Build with forge console.log('Building contracts') - await exec('forge build') + await exec('forge build --extra-output-files metadata') console.log('Contracts built') await mkdir(BUILD_DIR, { recursive: true }) From c74c1afcf717dd7553f7e64d255ff7e4db5468c1 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 31 Oct 2023 07:44:39 +1300 Subject: [PATCH 46/49] Fix typo in build script --- scripts/build.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/build.ts b/scripts/build.ts index 4add06e..0b314b9 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -29,10 +29,9 @@ const main = async () => { // Replace source urls with file contents for (const sourceKey of Object.keys(compilerDetails.sources)) { - compilerDetails.sources[sourceKey].contents = await readFile( - join(sourceKey), - 'utf8', - ) + compilerDetails.sources[sourceKey] = { + content: await readFile(join(sourceKey), 'utf8'), + } } // Write the compiler input file From 461112af56fc62cf2be95e09bf135950036c2f8d Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 31 Oct 2023 11:01:34 +1300 Subject: [PATCH 47/49] Add ability to change contract name symbol for erc20 721 --- src/tokens/ERC20/ERC20Token.sol | 4 ++-- .../ERC20/presets/minter/ERC20TokenMinter.sol | 14 ++++++++++++++ .../ERC20/presets/minter/IERC20TokenMinter.sol | 7 +++++++ src/tokens/ERC721/ERC721Token.sol | 4 ++-- .../ERC721/presets/minter/ERC721TokenMinter.sol | 14 ++++++++++++++ .../ERC721/presets/minter/IERC721TokenMinter.sol | 9 +++++++-- src/tokens/ERC721/presets/sale/ERC721Sale.sol | 14 ++++++++++++++ src/tokens/ERC721/presets/sale/IERC721Sale.sol | 7 +++++++ 8 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/tokens/ERC20/ERC20Token.sol b/src/tokens/ERC20/ERC20Token.sol index 11aa363..d17ff66 100644 --- a/src/tokens/ERC20/ERC20Token.sol +++ b/src/tokens/ERC20/ERC20Token.sol @@ -13,8 +13,8 @@ error InvalidInitialization(); */ abstract contract ERC20Token is ERC20, AccessControl { - string private _tokenName; - string private _tokenSymbol; + string internal _tokenName; + string internal _tokenSymbol; uint8 private _tokenDecimals; address private immutable _initializer; diff --git a/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol b/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol index 0d08679..746a8e2 100644 --- a/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol +++ b/src/tokens/ERC20/presets/minter/ERC20TokenMinter.sol @@ -51,6 +51,20 @@ contract ERC20TokenMinter is ERC20Token, IERC20TokenMinter { _mint(to, amount); } + // + // Admin + // + + /** + * Set name and symbol of token. + * @param tokenName Name of token. + * @param tokenSymbol Symbol of token. + */ + function setNameAndSymbol(string memory tokenName, string memory tokenSymbol) external onlyRole(DEFAULT_ADMIN_ROLE) { + _tokenName = tokenName; + _tokenSymbol = tokenSymbol; + } + // // Views // diff --git a/src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol b/src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol index aabbab9..f9e92d9 100644 --- a/src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol +++ b/src/tokens/ERC20/presets/minter/IERC20TokenMinter.sol @@ -9,6 +9,13 @@ interface IERC20TokenMinterFunctions { * @param amount Amount of tokens to mint. */ function mint(address to, uint256 amount) external; + + /** + * Set name and symbol of token. + * @param tokenName Name of token. + * @param tokenSymbol Symbol of token. + */ + function setNameAndSymbol(string memory tokenName, string memory tokenSymbol) external; } interface IERC20TokenMinterSignals { diff --git a/src/tokens/ERC721/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol index 0b16c51..2ffe604 100644 --- a/src/tokens/ERC721/ERC721Token.sol +++ b/src/tokens/ERC721/ERC721Token.sol @@ -15,8 +15,8 @@ abstract contract ERC721Token is ERC721AQueryable, ERC2981Controlled { bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); string private _tokenBaseURI; - string private _tokenName; - string private _tokenSymbol; + string internal _tokenName; + string internal _tokenSymbol; address private immutable _initializer; bool private _initialized; diff --git a/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol index 416f467..e57f8d0 100644 --- a/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol @@ -56,4 +56,18 @@ contract ERC721TokenMinter is ERC721Token, IERC721TokenMinter { function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { _mint(to, amount); } + + // + // Admin + // + + /** + * Set name and symbol of token. + * @param tokenName Name of token. + * @param tokenSymbol Symbol of token. + */ + function setNameAndSymbol(string memory tokenName, string memory tokenSymbol) external onlyRole(METADATA_ADMIN_ROLE) { + _tokenName = tokenName; + _tokenSymbol = tokenSymbol; + } } diff --git a/src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol b/src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol index 3a2364f..053168e 100644 --- a/src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol +++ b/src/tokens/ERC721/presets/minter/IERC721TokenMinter.sol @@ -2,17 +2,22 @@ pragma solidity ^0.8.17; interface IERC721TokenMinterFunctions { - /** * Mint tokens. * @param to Address to mint tokens to. * @param amount Amount of tokens to mint. */ function mint(address to, uint256 amount) external; + + /** + * Set name and symbol of token. + * @param tokenName Name of token. + * @param tokenSymbol Symbol of token. + */ + function setNameAndSymbol(string memory tokenName, string memory tokenSymbol) external; } interface IERC721TokenMinterSignals { - /** * Invalid initialization error. */ diff --git a/src/tokens/ERC721/presets/sale/ERC721Sale.sol b/src/tokens/ERC721/presets/sale/ERC721Sale.sol index ead97a8..e054445 100644 --- a/src/tokens/ERC721/presets/sale/ERC721Sale.sol +++ b/src/tokens/ERC721/presets/sale/ERC721Sale.sol @@ -136,6 +136,20 @@ contract ERC721Sale is IERC721Sale, ERC721Token, WithdrawControlled, MerkleProof emit SaleDetailsUpdated(supplyCap, cost, paymentToken, startTime, endTime, merkleRoot); } + // + // Admin + // + + /** + * Set name and symbol of token. + * @param tokenName Name of token. + * @param tokenSymbol Symbol of token. + */ + function setNameAndSymbol(string memory tokenName, string memory tokenSymbol) external onlyRole(METADATA_ADMIN_ROLE) { + _tokenName = tokenName; + _tokenSymbol = tokenSymbol; + } + // // Views // diff --git a/src/tokens/ERC721/presets/sale/IERC721Sale.sol b/src/tokens/ERC721/presets/sale/IERC721Sale.sol index 949d4bd..f538283 100644 --- a/src/tokens/ERC721/presets/sale/IERC721Sale.sol +++ b/src/tokens/ERC721/presets/sale/IERC721Sale.sol @@ -45,6 +45,13 @@ interface IERC721SaleFunctions { * @return Sale details. */ function saleDetails() external view returns (SaleDetails memory); + + /** + * Set name and symbol of token. + * @param tokenName Name of token. + * @param tokenSymbol Symbol of token. + */ + function setNameAndSymbol(string memory tokenName, string memory tokenSymbol) external; } interface IERC721SaleSignals { From 25093e67a6a91a099e17ccf84633ae08d07d1efd Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 31 Oct 2023 13:53:03 +1300 Subject: [PATCH 48/49] Add royalty to deployment function --- src/tokens/ERC1155/ERC1155Token.sol | 21 ++----- .../presets/minter/ERC1155TokenMinter.sol | 22 +++++-- .../minter/ERC1155TokenMinterFactory.sol | 16 +++++- .../minter/IERC1155TokenMinterFactory.sol | 14 ++++- .../ERC1155/presets/sale/ERC1155Sale.sol | 57 +++++++++++++------ .../presets/sale/ERC1155SaleFactory.sol | 16 +++++- .../presets/sale/IERC1155SaleFactory.sol | 14 ++++- src/tokens/ERC721/ERC721Token.sol | 17 ++---- .../presets/minter/ERC721TokenMinter.sol | 20 +++++-- .../minter/ERC721TokenMinterFactory.sol | 8 ++- .../minter/IERC721TokenMinterFactory.sol | 6 +- src/tokens/ERC721/presets/sale/ERC721Sale.sol | 28 +++++++-- .../ERC721/presets/sale/ERC721SaleFactory.sol | 6 +- .../presets/sale/IERC721SaleFactory.sol | 4 ++ test/tokens/ERC1155/presets/ERC1155Sale.t.sol | 4 +- .../ERC1155/presets/ERC1155TokenMinter.t.sol | 6 +- test/tokens/ERC721/presets/ERC721Sale.t.sol | 4 +- .../ERC721/presets/ERC721TokenMinter.t.sol | 6 +- 18 files changed, 183 insertions(+), 86 deletions(-) diff --git a/src/tokens/ERC1155/ERC1155Token.sol b/src/tokens/ERC1155/ERC1155Token.sol index e409bff..29594a8 100644 --- a/src/tokens/ERC1155/ERC1155Token.sol +++ b/src/tokens/ERC1155/ERC1155Token.sol @@ -14,15 +14,10 @@ error InvalidInitialization(); abstract contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, ERC2981Controlled { bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE"); - address private immutable _initializer; - bool private _initialized; - /** - * Initialize contract. + * Deploy contract. */ - constructor() ERC1155Metadata("", "") { - _initializer = msg.sender; - } + constructor() ERC1155Metadata("", "") {} /** * Initialize the contract. @@ -30,21 +25,14 @@ abstract contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, * @param tokenName Token name. * @param tokenBaseURI Base URI for token metadata. * @dev This should be called immediately after deployment. - */ - function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public virtual { - if (msg.sender != _initializer || _initialized) { - revert InvalidInitialization(); - } - + function _initialize(address owner, string memory tokenName, string memory tokenBaseURI) internal { name = tokenName; baseURI = tokenBaseURI; _setupRole(DEFAULT_ADMIN_ROLE, owner); _setupRole(ROYALTY_ADMIN_ROLE, owner); _setupRole(METADATA_ADMIN_ROLE, owner); - - _initialized = true; } // @@ -84,7 +72,6 @@ abstract contract ERC1155Token is ERC1155MintBurn, ERC1155Meta, ERC1155Metadata, returns (bool) { return ERC1155.supportsInterface(interfaceId) || ERC1155Metadata.supportsInterface(interfaceId) - || ERC2981Controlled.supportsInterface(interfaceId) - || super.supportsInterface(interfaceId); + || ERC2981Controlled.supportsInterface(interfaceId) || super.supportsInterface(interfaceId); } } diff --git a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol index 29a0bb8..4c828d5 100644 --- a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol +++ b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinter.sol @@ -21,17 +21,29 @@ contract ERC1155TokenMinter is ERC1155MintBurn, ERC1155Token, IERC1155TokenMinte /** * Initialize the contract. - * @param owner Owner address. - * @param tokenName Token name. - * @param tokenBaseURI Base URI for token metadata. + * @param owner Owner address + * @param tokenName Token name + * @param tokenBaseURI Base URI for token metadata + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public virtual override { + function initialize( + address owner, + string memory tokenName, + string memory tokenBaseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator + ) + public + virtual + { if (msg.sender != initializer || initialized) { revert InvalidInitialization(); } - ERC1155Token.initialize(owner, tokenName, tokenBaseURI); + ERC1155Token._initialize(owner, tokenName, tokenBaseURI); + _setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); _setupRole(MINTER_ROLE, owner); diff --git a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol index 281e908..ff0b13e 100644 --- a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol +++ b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol @@ -25,17 +25,27 @@ contract ERC1155TokenMinterFactory is IERC1155TokenMinterFactory, SequenceProxyF * @param tokenOwner The owner of the ERC-1155 Token Minter implementation * @param name The name of the ERC-1155 Token Minter proxy * @param baseURI The base URI of the ERC-1155 Token Minter proxy + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Token Minter Proxy * @dev The provided `salt` is hashed with the caller address for security. - * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-1155 Token Minter functions. */ - function deploy(address proxyOwner, address tokenOwner, string memory name, string memory baseURI, bytes32 salt) + function deploy( + address proxyOwner, + address tokenOwner, + string memory name, + string memory baseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator, + bytes32 salt + ) external returns (address proxyAddr) { proxyAddr = _createProxy(salt, proxyOwner, ""); - ERC1155TokenMinter(proxyAddr).initialize(tokenOwner, name, baseURI); + ERC1155TokenMinter(proxyAddr).initialize(tokenOwner, name, baseURI, royaltyReceiver, royaltyFeeNumerator); emit ERC1155TokenMinterDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol index 9dc0ff8..4ba1e3d 100644 --- a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol +++ b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol @@ -8,12 +8,22 @@ interface IERC1155TokenMinterFactoryFunctions { * @param tokenOwner The owner of the ERC-1155 Token Minter implementation * @param name The name of the ERC-1155 Token Minter proxy * @param baseURI The base URI of the ERC-1155 Token Minter proxy + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Token Minter Proxy * @dev The provided `salt` is hashed with the caller address for security. - * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-1155 Token Minter functions. */ - function deploy(address proxyOwner, address tokenOwner, string memory name, string memory baseURI, bytes32 salt) + function deploy( + address proxyOwner, + address tokenOwner, + string memory name, + string memory baseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator, + bytes32 salt + ) external returns (address proxyAddr); } diff --git a/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol b/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol index 60db8f3..baac0a8 100644 --- a/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol +++ b/src/tokens/ERC1155/presets/sale/ERC1155Sale.sol @@ -2,16 +2,19 @@ pragma solidity ^0.8.17; import {IERC1155Sale} from "@0xsequence/contracts-library/tokens/ERC1155/presets/sale/IERC1155Sale.sol"; -import {ERC1155Supply, ERC1155Token} from "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/ERC1155Supply.sol"; -import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; -import {MerkleProofSingleUse} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; - -contract ERC1155Sale is - IERC1155Sale, +import { ERC1155Supply, + ERC1155Token +} from "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/ERC1155Supply.sol"; +import { WithdrawControlled, - MerkleProofSingleUse -{ + AccessControl, + SafeERC20, + IERC20 +} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; +import {MerkleProofSingleUse} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; + +contract ERC1155Sale is IERC1155Sale, ERC1155Supply, WithdrawControlled, MerkleProofSingleUse { bytes32 public constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); bytes4 private constant _ERC20_TRANSFERFROM_SELECTOR = @@ -27,20 +30,30 @@ contract ERC1155Sale is /** * Initialize the contract. - * @param owner Owner address. - * @param tokenName Token name. - * @param tokenBaseURI Base URI for token metadata. + * @param owner Owner address + * @param tokenName Token name + * @param tokenBaseURI Base URI for token metadata + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory tokenName, string memory tokenBaseURI) public virtual override { + function initialize( + address owner, + string memory tokenName, + string memory tokenBaseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator + ) + public + virtual + { if (_initialized) { revert InvalidInitialization(); } - ERC1155Token.initialize(owner, tokenName, tokenBaseURI); - name = tokenName; - baseURI = tokenBaseURI; - _setupRole(DEFAULT_ADMIN_ROLE, owner); + ERC1155Token._initialize(owner, tokenName, tokenBaseURI); + _setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); + _setupRole(MINT_ADMIN_ROLE, owner); _setupRole(WITHDRAW_ROLE, owner); @@ -64,7 +77,9 @@ contract ERC1155Sale is * @param _amounts Amounts of tokens to mint. * @param _proof Merkle proof for allowlist minting. */ - function _payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts, bytes32[] calldata _proof) private { + function _payForActiveMint(uint256[] memory _tokenIds, uint256[] memory _amounts, bytes32[] calldata _proof) + private + { uint256 lastTokenId; uint256 totalCost; uint256 totalAmount; @@ -127,7 +142,13 @@ contract ERC1155Sale is * @dev tokenIds must be sorted ascending without duplicates. * @dev An empty proof is supplied when no proof is required. */ - function mint(address to, uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data, bytes32[] calldata proof) + function mint( + address to, + uint256[] memory tokenIds, + uint256[] memory amounts, + bytes memory data, + bytes32[] calldata proof + ) public payable { diff --git a/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol index 80134b4..f8a99a5 100644 --- a/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol @@ -24,16 +24,26 @@ contract ERC1155SaleFactory is IERC1155SaleFactory, SequenceProxyFactory { * @param tokenOwner The owner of the ERC-1155 Sale implementation * @param name The name of the ERC-1155 Sale token * @param baseURI The base URI of the ERC-1155 Sale token + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Sale Proxy - * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-1155 Sale Minter functions. */ - function deploy(address proxyOwner, address tokenOwner, string memory name, string memory baseURI, bytes32 salt) + function deploy( + address proxyOwner, + address tokenOwner, + string memory name, + string memory baseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator, + bytes32 salt + ) external returns (address proxyAddr) { proxyAddr = _createProxy(salt, proxyOwner, ""); - ERC1155Sale(proxyAddr).initialize(tokenOwner, name, baseURI); + ERC1155Sale(proxyAddr).initialize(tokenOwner, name, baseURI, royaltyReceiver, royaltyFeeNumerator); emit ERC1155SaleDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol index d5b393d..b7e8d93 100644 --- a/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol @@ -8,11 +8,21 @@ interface IERC1155SaleFactoryFunctions { * @param tokenOwner The owner of the ERC-1155 Sale implementation * @param name The name of the ERC-1155 Sale token * @param baseURI The base URI of the ERC-1155 Sale token + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Sale Proxy - * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-1155 Token Sale functions. */ - function deploy(address proxyOwner, address tokenOwner, string memory name, string memory baseURI, bytes32 salt) + function deploy( + address proxyOwner, + address tokenOwner, + string memory name, + string memory baseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator, + bytes32 salt + ) external returns (address proxyAddr); } diff --git a/src/tokens/ERC721/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol index 2ffe604..1812acb 100644 --- a/src/tokens/ERC721/ERC721Token.sol +++ b/src/tokens/ERC721/ERC721Token.sol @@ -18,15 +18,10 @@ abstract contract ERC721Token is ERC721AQueryable, ERC2981Controlled { string internal _tokenName; string internal _tokenSymbol; - address private immutable _initializer; - bool private _initialized; - /** * Deploy contract. */ - constructor() ERC721A("", "") { - _initializer = msg.sender; - } + constructor() ERC721A("", "") {} /** * Initialize contract. @@ -36,11 +31,9 @@ abstract contract ERC721Token is ERC721AQueryable, ERC2981Controlled { * @param tokenBaseURI Base URI of the token * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory tokenName, string memory tokenSymbol, string memory tokenBaseURI) public virtual { - if (msg.sender != _initializer || _initialized) { - revert InvalidInitialization(); - } - + function _initialize(address owner, string memory tokenName, string memory tokenSymbol, string memory tokenBaseURI) + internal + { _tokenName = tokenName; _tokenSymbol = tokenSymbol; _tokenBaseURI = tokenBaseURI; @@ -48,8 +41,6 @@ abstract contract ERC721Token is ERC721AQueryable, ERC2981Controlled { _setupRole(DEFAULT_ADMIN_ROLE, owner); _setupRole(METADATA_ADMIN_ROLE, owner); _setupRole(ROYALTY_ADMIN_ROLE, owner); - - _initialized = true; } // diff --git a/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol index e57f8d0..eac51de 100644 --- a/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinter.sol @@ -26,18 +26,27 @@ contract ERC721TokenMinter is ERC721Token, IERC721TokenMinter { * @param tokenName Name of the token * @param tokenSymbol Symbol of the token * @param tokenBaseURI Base URI of the token + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory tokenName, string memory tokenSymbol, string memory tokenBaseURI) + function initialize( + address owner, + string memory tokenName, + string memory tokenSymbol, + string memory tokenBaseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator + ) public virtual - override { if (msg.sender != _initializer || _initialized) { revert InvalidInitialization(); } - ERC721Token.initialize(owner, tokenName, tokenSymbol, tokenBaseURI); + ERC721Token._initialize(owner, tokenName, tokenSymbol, tokenBaseURI); + _setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); _setupRole(MINTER_ROLE, owner); @@ -66,7 +75,10 @@ contract ERC721TokenMinter is ERC721Token, IERC721TokenMinter { * @param tokenName Name of token. * @param tokenSymbol Symbol of token. */ - function setNameAndSymbol(string memory tokenName, string memory tokenSymbol) external onlyRole(METADATA_ADMIN_ROLE) { + function setNameAndSymbol(string memory tokenName, string memory tokenSymbol) + external + onlyRole(METADATA_ADMIN_ROLE) + { _tokenName = tokenName; _tokenSymbol = tokenSymbol; } diff --git a/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol index 822ac62..029bc58 100644 --- a/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol @@ -27,10 +27,12 @@ contract ERC721TokenMinterFactory is IERC721TokenMinterFactory, SequenceProxyFac * @param name The name of the ERC-721 Token Minter proxy * @param symbol The symbol of the ERC-721 Token Minter proxy * @param baseURI The base URI of the ERC-721 Token Minter proxy + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Token Minter Proxy * @dev The provided `salt` is hashed with the caller address for security. - * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-721 Token Minter functions. */ function deploy( address proxyOwner, @@ -38,13 +40,15 @@ contract ERC721TokenMinterFactory is IERC721TokenMinterFactory, SequenceProxyFac string memory name, string memory symbol, string memory baseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator, bytes32 salt ) external returns (address proxyAddr) { proxyAddr = _createProxy(salt, proxyOwner, ""); - ERC721TokenMinter(proxyAddr).initialize(tokenOwner, name, symbol, baseURI); + ERC721TokenMinter(proxyAddr).initialize(tokenOwner, name, symbol, baseURI, royaltyReceiver, royaltyFeeNumerator); emit ERC721TokenMinterDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol index 8a3da54..41c782b 100644 --- a/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol +++ b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol @@ -10,10 +10,12 @@ interface IERC721TokenMinterFactoryFunctions { * @param name The name of the ERC-721 Token Minter proxy * @param symbol The symbol of the ERC-721 Token Minter proxy * @param baseURI The base URI of the ERC-721 Token Minter proxy + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Token Minter Proxy * @dev The provided `salt` is hashed with the caller address for security. - * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. + * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-721 Token Minter functions. */ function deploy( address proxyOwner, @@ -21,6 +23,8 @@ interface IERC721TokenMinterFactoryFunctions { string memory name, string memory symbol, string memory baseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator, bytes32 salt ) external diff --git a/src/tokens/ERC721/presets/sale/ERC721Sale.sol b/src/tokens/ERC721/presets/sale/ERC721Sale.sol index e054445..65d7ba4 100644 --- a/src/tokens/ERC721/presets/sale/ERC721Sale.sol +++ b/src/tokens/ERC721/presets/sale/ERC721Sale.sol @@ -3,7 +3,12 @@ pragma solidity ^0.8.17; import {IERC721Sale} from "@0xsequence/contracts-library/tokens/ERC721/presets/sale/IERC721Sale.sol"; import {ERC721Token} from "@0xsequence/contracts-library/tokens/ERC721/ERC721Token.sol"; -import {WithdrawControlled, AccessControl, SafeERC20, IERC20} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; +import { + WithdrawControlled, + AccessControl, + SafeERC20, + IERC20 +} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; import {MerkleProofSingleUse} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; /** @@ -25,18 +30,28 @@ contract ERC721Sale is IERC721Sale, ERC721Token, WithdrawControlled, MerkleProof * @param tokenName Name of the token * @param tokenSymbol Symbol of the token * @param tokenBaseURI Base URI of the token + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @dev This should be called immediately after deployment. */ - function initialize(address owner, string memory tokenName, string memory tokenSymbol, string memory tokenBaseURI) + function initialize( + address owner, + string memory tokenName, + string memory tokenSymbol, + string memory tokenBaseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator + ) public virtual - override { if (_initialized) { revert InvalidInitialization(); } - ERC721Token.initialize(owner, tokenName, tokenSymbol, tokenBaseURI); + ERC721Token._initialize(owner, tokenName, tokenSymbol, tokenBaseURI); + _setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); + _setupRole(MINT_ADMIN_ROLE, owner); _setupRole(WITHDRAW_ROLE, owner); @@ -145,7 +160,10 @@ contract ERC721Sale is IERC721Sale, ERC721Token, WithdrawControlled, MerkleProof * @param tokenName Name of token. * @param tokenSymbol Symbol of token. */ - function setNameAndSymbol(string memory tokenName, string memory tokenSymbol) external onlyRole(METADATA_ADMIN_ROLE) { + function setNameAndSymbol(string memory tokenName, string memory tokenSymbol) + external + onlyRole(METADATA_ADMIN_ROLE) + { _tokenName = tokenName; _tokenSymbol = tokenSymbol; } diff --git a/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol index 494b19b..989274d 100644 --- a/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol +++ b/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol @@ -26,6 +26,8 @@ contract ERC721SaleFactory is IERC721SaleFactory, SequenceProxyFactory { * @param name The name of the ERC-721 Sale token * @param symbol The symbol of the ERC-721 Sale token * @param baseURI The base URI of the ERC-721 Sale token + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Sale Proxy * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-721 Sale functions. @@ -36,13 +38,15 @@ contract ERC721SaleFactory is IERC721SaleFactory, SequenceProxyFactory { string memory name, string memory symbol, string memory baseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator, bytes32 salt ) external returns (address proxyAddr) { proxyAddr = _createProxy(salt, proxyOwner, ""); - ERC721Sale(proxyAddr).initialize(tokenOwner, name, symbol, baseURI); + ERC721Sale(proxyAddr).initialize(tokenOwner, name, symbol, baseURI, royaltyReceiver, royaltyFeeNumerator); emit ERC721SaleDeployed(proxyAddr); return proxyAddr; } diff --git a/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol index 8a14e46..aa8a687 100644 --- a/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol +++ b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol @@ -10,6 +10,8 @@ interface IERC721SaleFactoryFunctions { * @param name The name of the ERC-721 Sale token * @param symbol The symbol of the ERC-721 Sale token * @param baseURI The base URI of the ERC-721 Sale token + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Sale Proxy * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-721 Sale functions. @@ -20,6 +22,8 @@ interface IERC721SaleFactoryFunctions { string memory name, string memory symbol, string memory baseURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator, bytes32 salt ) external diff --git a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol index 2ea249c..3016e90 100644 --- a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol @@ -39,14 +39,14 @@ contract ERC1155SaleTest is Test, Merkle, IERC1155SaleSignals, IERC1155SupplySig proxyOwner = makeAddr("proxyOwner"); token = new ERC1155Sale(); - token.initialize(address(this), "test", "ipfs://"); + token.initialize(address(this), "test", "ipfs://", address(this), 0); vm.deal(address(this), 1e6 ether); } function setUpFromFactory() public { ERC1155SaleFactory factory = new ERC1155SaleFactory(address(this)); - token = ERC1155Sale(factory.deploy(proxyOwner, address(this), "test", "ipfs://", "")); + token = ERC1155Sale(factory.deploy(proxyOwner, address(this), "test", "ipfs://", address(this), 0, "")); } function testSupportsInterface() public { diff --git a/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol b/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol index 80e0831..f096ba5 100644 --- a/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol @@ -35,12 +35,12 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { vm.deal(owner, 100 ether); ERC1155TokenMinterFactory factory = new ERC1155TokenMinterFactory(address(this)); - token = ERC1155TokenMinter(factory.deploy(proxyOwner, owner, "name", "baseURI", 0x0)); + token = ERC1155TokenMinter(factory.deploy(proxyOwner, owner, "name", "baseURI", address(this), 0, 0x0)); } function testReinitializeFails() public { vm.expectRevert(InvalidInitialization.selector); - token.initialize(owner, "name", "baseURI"); + token.initialize(owner, "name", "baseURI", address(this), 0); } function testSupportsInterface() public { @@ -240,7 +240,7 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { assertEq(amount, salePrice * feeNumerator / 10000); (receiver_, amount) = token.royaltyInfo(69, salePrice); - assertEq(receiver_, address(0)); + assertEq(receiver_, address(this)); assertEq(amount, 0); } diff --git a/test/tokens/ERC721/presets/ERC721Sale.t.sol b/test/tokens/ERC721/presets/ERC721Sale.t.sol index 91cf73d..815651f 100644 --- a/test/tokens/ERC721/presets/ERC721Sale.t.sol +++ b/test/tokens/ERC721/presets/ERC721Sale.t.sol @@ -35,14 +35,14 @@ contract ERC721SaleTest is Test, Merkle, IERC721SaleSignals, IMerkleProofSingleU proxyOwner = makeAddr("proxyOwner"); token = new ERC721Sale(); - token.initialize(address(this), "test", "test", "ipfs://"); + token.initialize(address(this), "test", "test", "ipfs://", address(this), 0); vm.deal(address(this), 100 ether); } function setUpFromFactory() public { ERC721SaleFactory factory = new ERC721SaleFactory(address(this)); - token = ERC721Sale(factory.deploy(proxyOwner, address(this), "test", "test", "ipfs://", "")); + token = ERC721Sale(factory.deploy(proxyOwner, address(this), "test", "test", "ipfs://", address(this), 0, "")); } function testSupportsInterface() public { diff --git a/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol b/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol index cacbcd2..9ccf231 100644 --- a/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol +++ b/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol @@ -30,12 +30,12 @@ contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { vm.deal(owner, 100 ether); ERC721TokenMinterFactory factory = new ERC721TokenMinterFactory(address(this)); - token = ERC721TokenMinter(factory.deploy(proxyOwner, owner, "name", "symbol", "baseURI", 0x0)); + token = ERC721TokenMinter(factory.deploy(proxyOwner, owner, "name", "symbol", "baseURI", address(this), 0, 0x0)); } function testReinitializeFails() public { vm.expectRevert(InvalidInitialization.selector); - token.initialize(owner, "name", "symbol", "baseURI"); + token.initialize(owner, "name", "symbol", "baseURI", address(this), 0); } function testSupportsInterface() public { @@ -196,7 +196,7 @@ contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { assertEq(amount, salePrice * feeNumerator / 10000); (receiver_, amount) = token.royaltyInfo(69, salePrice); - assertEq(receiver_, address(0)); + assertEq(receiver_, address(this)); assertEq(amount, 0); } From e031790f5a21a4ee402e1fc31c64c9485c26f464 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 31 Oct 2023 14:14:43 +1300 Subject: [PATCH 49/49] Salt is a hash of input params --- .../presets/minter/ERC1155TokenMinterFactory.sol | 6 ++---- .../presets/minter/IERC1155TokenMinterFactory.sol | 5 +---- .../ERC1155/presets/sale/ERC1155SaleFactory.sol | 5 ++--- .../ERC1155/presets/sale/IERC1155SaleFactory.sol | 4 +--- .../ERC20/presets/minter/ERC20TokenMinterFactory.sol | 12 ++---------- .../presets/minter/IERC20TokenMinterFactory.sol | 11 +---------- .../presets/minter/ERC721TokenMinterFactory.sol | 8 +++----- .../presets/minter/IERC721TokenMinterFactory.sol | 6 +----- src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol | 7 +++---- .../ERC721/presets/sale/IERC721SaleFactory.sol | 6 +----- test/tokens/ERC1155/presets/ERC1155Sale.t.sol | 2 +- test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol | 2 +- test/tokens/ERC20/ERC20TokenMinter.t.sol | 2 +- test/tokens/ERC721/presets/ERC721Sale.t.sol | 2 +- test/tokens/ERC721/presets/ERC721TokenMinter.t.sol | 2 +- 15 files changed, 22 insertions(+), 58 deletions(-) diff --git a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol index ff0b13e..b74cd02 100644 --- a/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol +++ b/src/tokens/ERC1155/presets/minter/ERC1155TokenMinterFactory.sol @@ -27,9 +27,7 @@ contract ERC1155TokenMinterFactory is IERC1155TokenMinterFactory, SequenceProxyF * @param baseURI The base URI of the ERC-1155 Token Minter proxy * @param royaltyReceiver Address of who should be sent the royalty payment * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Token Minter Proxy - * @dev The provided `salt` is hashed with the caller address for security. * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-1155 Token Minter functions. */ function deploy( @@ -38,12 +36,12 @@ contract ERC1155TokenMinterFactory is IERC1155TokenMinterFactory, SequenceProxyF string memory name, string memory baseURI, address royaltyReceiver, - uint96 royaltyFeeNumerator, - bytes32 salt + uint96 royaltyFeeNumerator ) external returns (address proxyAddr) { + bytes32 salt = keccak256(abi.encodePacked(tokenOwner, name, baseURI, royaltyReceiver, royaltyFeeNumerator)); proxyAddr = _createProxy(salt, proxyOwner, ""); ERC1155TokenMinter(proxyAddr).initialize(tokenOwner, name, baseURI, royaltyReceiver, royaltyFeeNumerator); emit ERC1155TokenMinterDeployed(proxyAddr); diff --git a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol index 4ba1e3d..b4da515 100644 --- a/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol +++ b/src/tokens/ERC1155/presets/minter/IERC1155TokenMinterFactory.sol @@ -10,9 +10,7 @@ interface IERC1155TokenMinterFactoryFunctions { * @param baseURI The base URI of the ERC-1155 Token Minter proxy * @param royaltyReceiver Address of who should be sent the royalty payment * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Token Minter Proxy - * @dev The provided `salt` is hashed with the caller address for security. * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-1155 Token Minter functions. */ function deploy( @@ -21,8 +19,7 @@ interface IERC1155TokenMinterFactoryFunctions { string memory name, string memory baseURI, address royaltyReceiver, - uint96 royaltyFeeNumerator, - bytes32 salt + uint96 royaltyFeeNumerator ) external returns (address proxyAddr); diff --git a/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol index f8a99a5..74f0263 100644 --- a/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/presets/sale/ERC1155SaleFactory.sol @@ -26,7 +26,6 @@ contract ERC1155SaleFactory is IERC1155SaleFactory, SequenceProxyFactory { * @param baseURI The base URI of the ERC-1155 Sale token * @param royaltyReceiver Address of who should be sent the royalty payment * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Sale Proxy * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-1155 Sale Minter functions. */ @@ -36,12 +35,12 @@ contract ERC1155SaleFactory is IERC1155SaleFactory, SequenceProxyFactory { string memory name, string memory baseURI, address royaltyReceiver, - uint96 royaltyFeeNumerator, - bytes32 salt + uint96 royaltyFeeNumerator ) external returns (address proxyAddr) { + bytes32 salt = keccak256(abi.encodePacked(tokenOwner, name, baseURI, royaltyReceiver, royaltyFeeNumerator)); proxyAddr = _createProxy(salt, proxyOwner, ""); ERC1155Sale(proxyAddr).initialize(tokenOwner, name, baseURI, royaltyReceiver, royaltyFeeNumerator); emit ERC1155SaleDeployed(proxyAddr); diff --git a/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol index b7e8d93..3ebb10e 100644 --- a/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol +++ b/src/tokens/ERC1155/presets/sale/IERC1155SaleFactory.sol @@ -10,7 +10,6 @@ interface IERC1155SaleFactoryFunctions { * @param baseURI The base URI of the ERC-1155 Sale token * @param royaltyReceiver Address of who should be sent the royalty payment * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @param salt The deployment salt * @return proxyAddr The address of the ERC-1155 Sale Proxy * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-1155 Token Sale functions. */ @@ -20,8 +19,7 @@ interface IERC1155SaleFactoryFunctions { string memory name, string memory baseURI, address royaltyReceiver, - uint96 royaltyFeeNumerator, - bytes32 salt + uint96 royaltyFeeNumerator ) external returns (address proxyAddr); diff --git a/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol b/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol index be95da7..fe0fdd0 100644 --- a/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol +++ b/src/tokens/ERC20/presets/minter/ERC20TokenMinterFactory.sol @@ -26,22 +26,14 @@ contract ERC20TokenMinterFactory is IERC20TokenMinterFactory, SequenceProxyFacto * @param name The name of the ERC-20 Token Minter proxy * @param symbol The symbol of the ERC-20 Token Minter proxy * @param decimals The decimals of the ERC-20 Token Minter proxy - * @param salt The deployment salt * @return proxyAddr The address of the ERC-20 Token Minter Proxy - * @dev The provided `salt` is hashed with the caller address for security. * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. */ - function deploy( - address proxyOwner, - address tokenOwner, - string memory name, - string memory symbol, - uint8 decimals, - bytes32 salt - ) + function deploy(address proxyOwner, address tokenOwner, string memory name, string memory symbol, uint8 decimals) external returns (address proxyAddr) { + bytes32 salt = keccak256(abi.encodePacked(tokenOwner, name, symbol, decimals)); proxyAddr = _createProxy(salt, proxyOwner, ""); ERC20TokenMinter(proxyAddr).initialize(tokenOwner, name, symbol, decimals); emit ERC20TokenMinterDeployed(proxyAddr); diff --git a/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol b/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol index 4ce9483..64af83e 100644 --- a/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol +++ b/src/tokens/ERC20/presets/minter/IERC20TokenMinterFactory.sol @@ -9,19 +9,10 @@ interface IERC20TokenMinterFactoryFunctions { * @param name The name of the ERC-20 Token Minter proxy * @param symbol The symbol of the ERC-20 Token Minter proxy * @param decimals The decimals of the ERC-20 Token Minter proxy - * @param salt The deployment salt * @return proxyAddr The address of the ERC-20 Token Minter Proxy - * @dev The provided `salt` is hashed with the caller address for security. * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-20 Token Minter functions. */ - function deploy( - address proxyOwner, - address tokenOwner, - string memory name, - string memory symbol, - uint8 decimals, - bytes32 salt - ) + function deploy(address proxyOwner, address tokenOwner, string memory name, string memory symbol, uint8 decimals) external returns (address proxyAddr); } diff --git a/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol index 029bc58..ef03cde 100644 --- a/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol +++ b/src/tokens/ERC721/presets/minter/ERC721TokenMinterFactory.sol @@ -10,7 +10,6 @@ import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/Sequen * Deployer of ERC-721 Token Minter proxies. */ contract ERC721TokenMinterFactory is IERC721TokenMinterFactory, SequenceProxyFactory { - /** * Creates an ERC-721 Token Minter Factory. * @param factoryOwner The owner of the ERC-721 Token Minter Factory @@ -29,9 +28,7 @@ contract ERC721TokenMinterFactory is IERC721TokenMinterFactory, SequenceProxyFac * @param baseURI The base URI of the ERC-721 Token Minter proxy * @param royaltyReceiver Address of who should be sent the royalty payment * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Token Minter Proxy - * @dev The provided `salt` is hashed with the caller address for security. * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-721 Token Minter functions. */ function deploy( @@ -41,12 +38,13 @@ contract ERC721TokenMinterFactory is IERC721TokenMinterFactory, SequenceProxyFac string memory symbol, string memory baseURI, address royaltyReceiver, - uint96 royaltyFeeNumerator, - bytes32 salt + uint96 royaltyFeeNumerator ) external returns (address proxyAddr) { + bytes32 salt = + keccak256(abi.encodePacked(tokenOwner, name, symbol, baseURI, royaltyReceiver, royaltyFeeNumerator)); proxyAddr = _createProxy(salt, proxyOwner, ""); ERC721TokenMinter(proxyAddr).initialize(tokenOwner, name, symbol, baseURI, royaltyReceiver, royaltyFeeNumerator); emit ERC721TokenMinterDeployed(proxyAddr); diff --git a/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol index 41c782b..486f05a 100644 --- a/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol +++ b/src/tokens/ERC721/presets/minter/IERC721TokenMinterFactory.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.17; interface IERC721TokenMinterFactoryFunctions { - /** * Creates an ERC-721 Token Minter proxy. * @param proxyOwner The owner of the ERC-721 Token Minter proxy @@ -12,9 +11,7 @@ interface IERC721TokenMinterFactoryFunctions { * @param baseURI The base URI of the ERC-721 Token Minter proxy * @param royaltyReceiver Address of who should be sent the royalty payment * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Token Minter Proxy - * @dev The provided `salt` is hashed with the caller address for security. * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-721 Token Minter functions. */ function deploy( @@ -24,8 +21,7 @@ interface IERC721TokenMinterFactoryFunctions { string memory symbol, string memory baseURI, address royaltyReceiver, - uint96 royaltyFeeNumerator, - bytes32 salt + uint96 royaltyFeeNumerator ) external returns (address proxyAddr); diff --git a/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol index 989274d..39c1aa5 100644 --- a/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol +++ b/src/tokens/ERC721/presets/sale/ERC721SaleFactory.sol @@ -9,7 +9,6 @@ import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/Sequen * Deployer of ERC-721 Sale proxies. */ contract ERC721SaleFactory is IERC721SaleFactory, SequenceProxyFactory { - /** * Creates an ERC-721 Sale Factory. * @param factoryOwner The owner of the ERC-721 Sale Factory @@ -28,7 +27,6 @@ contract ERC721SaleFactory is IERC721SaleFactory, SequenceProxyFactory { * @param baseURI The base URI of the ERC-721 Sale token * @param royaltyReceiver Address of who should be sent the royalty payment * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Sale Proxy * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-721 Sale functions. */ @@ -39,12 +37,13 @@ contract ERC721SaleFactory is IERC721SaleFactory, SequenceProxyFactory { string memory symbol, string memory baseURI, address royaltyReceiver, - uint96 royaltyFeeNumerator, - bytes32 salt + uint96 royaltyFeeNumerator ) external returns (address proxyAddr) { + bytes32 salt = + keccak256(abi.encodePacked(tokenOwner, name, symbol, baseURI, royaltyReceiver, royaltyFeeNumerator)); proxyAddr = _createProxy(salt, proxyOwner, ""); ERC721Sale(proxyAddr).initialize(tokenOwner, name, symbol, baseURI, royaltyReceiver, royaltyFeeNumerator); emit ERC721SaleDeployed(proxyAddr); diff --git a/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol index aa8a687..ac2933e 100644 --- a/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol +++ b/src/tokens/ERC721/presets/sale/IERC721SaleFactory.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.17; interface IERC721SaleFactoryFunctions { - /** * Creates an ERC-721 Sale for given token contract * @param proxyOwner The owner of the ERC-721 Sale proxy @@ -12,7 +11,6 @@ interface IERC721SaleFactoryFunctions { * @param baseURI The base URI of the ERC-721 Sale token * @param royaltyReceiver Address of who should be sent the royalty payment * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) - * @param salt The deployment salt * @return proxyAddr The address of the ERC-721 Sale Proxy * @dev As `proxyOwner` owns the proxy, it will be unable to call the ERC-721 Sale functions. */ @@ -23,15 +21,13 @@ interface IERC721SaleFactoryFunctions { string memory symbol, string memory baseURI, address royaltyReceiver, - uint96 royaltyFeeNumerator, - bytes32 salt + uint96 royaltyFeeNumerator ) external returns (address proxyAddr); } interface IERC721SaleFactorySignals { - /** * Event emitted when a new ERC-721 Sale proxy contract is deployed. * @param proxyAddr The address of the deployed proxy. diff --git a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol index 3016e90..38342c3 100644 --- a/test/tokens/ERC1155/presets/ERC1155Sale.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155Sale.t.sol @@ -46,7 +46,7 @@ contract ERC1155SaleTest is Test, Merkle, IERC1155SaleSignals, IERC1155SupplySig function setUpFromFactory() public { ERC1155SaleFactory factory = new ERC1155SaleFactory(address(this)); - token = ERC1155Sale(factory.deploy(proxyOwner, address(this), "test", "ipfs://", address(this), 0, "")); + token = ERC1155Sale(factory.deploy(proxyOwner, address(this), "test", "ipfs://", address(this), 0)); } function testSupportsInterface() public { diff --git a/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol b/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol index f096ba5..c2d72d8 100644 --- a/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol +++ b/test/tokens/ERC1155/presets/ERC1155TokenMinter.t.sol @@ -35,7 +35,7 @@ contract ERC1155TokenMinterTest is Test, IERC1155TokenMinterSignals { vm.deal(owner, 100 ether); ERC1155TokenMinterFactory factory = new ERC1155TokenMinterFactory(address(this)); - token = ERC1155TokenMinter(factory.deploy(proxyOwner, owner, "name", "baseURI", address(this), 0, 0x0)); + token = ERC1155TokenMinter(factory.deploy(proxyOwner, owner, "name", "baseURI", address(this), 0)); } function testReinitializeFails() public { diff --git a/test/tokens/ERC20/ERC20TokenMinter.t.sol b/test/tokens/ERC20/ERC20TokenMinter.t.sol index 792caf2..60344db 100644 --- a/test/tokens/ERC20/ERC20TokenMinter.t.sol +++ b/test/tokens/ERC20/ERC20TokenMinter.t.sol @@ -32,7 +32,7 @@ contract ERC20TokenMinterTest is Test, IERC20TokenMinterSignals { vm.deal(owner, 100 ether); ERC20TokenMinterFactory factory = new ERC20TokenMinterFactory(address(this)); - token = ERC20TokenMinter(factory.deploy(proxyOwner, owner, "name", "symbol", DECIMALS, 0x0)); + token = ERC20TokenMinter(factory.deploy(proxyOwner, owner, "name", "symbol", DECIMALS)); } function testReinitializeFails() public { diff --git a/test/tokens/ERC721/presets/ERC721Sale.t.sol b/test/tokens/ERC721/presets/ERC721Sale.t.sol index 815651f..a86dc2b 100644 --- a/test/tokens/ERC721/presets/ERC721Sale.t.sol +++ b/test/tokens/ERC721/presets/ERC721Sale.t.sol @@ -42,7 +42,7 @@ contract ERC721SaleTest is Test, Merkle, IERC721SaleSignals, IMerkleProofSingleU function setUpFromFactory() public { ERC721SaleFactory factory = new ERC721SaleFactory(address(this)); - token = ERC721Sale(factory.deploy(proxyOwner, address(this), "test", "test", "ipfs://", address(this), 0, "")); + token = ERC721Sale(factory.deploy(proxyOwner, address(this), "test", "test", "ipfs://", address(this), 0)); } function testSupportsInterface() public { diff --git a/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol b/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol index 9ccf231..783ec2c 100644 --- a/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol +++ b/test/tokens/ERC721/presets/ERC721TokenMinter.t.sol @@ -30,7 +30,7 @@ contract ERC721TokenMinterTest is Test, IERC721TokenMinterSignals { vm.deal(owner, 100 ether); ERC721TokenMinterFactory factory = new ERC721TokenMinterFactory(address(this)); - token = ERC721TokenMinter(factory.deploy(proxyOwner, owner, "name", "symbol", "baseURI", address(this), 0, 0x0)); + token = ERC721TokenMinter(factory.deploy(proxyOwner, owner, "name", "symbol", "baseURI", address(this), 0)); } function testReinitializeFails() public {