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/.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/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..3d021f0 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,103 @@ + +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') }} + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run linting + 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 }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..646835d --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Forge +out/ +cache/ + +# Ignore .DS_Store files on macOS +.DS_Store + +# Yarn +node_modules/ +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/.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/.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/.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/.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/README.md b/README.md index dfce5ec..1e263d1 100644 --- a/README.md +++ b/README.md @@ -1 +1,46 @@ -# 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 + +Copy `.env.example` to `.env` and set your wallet configuration. + +```sh +cp .env.example .env +``` + +Then run the deployment script. + +```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/foundry.toml b/foundry.toml new file mode 100644 index 0000000..c8a7cb1 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,7 @@ +[profile.default] +src = 'src' +out = 'out' +libs = ['lib'] +solc = "0.8.17" +via_ir = true +optimizer-runs = 20_000 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..307f9f7 --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "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": "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\"", + "format:sol": "forge fmt" + }, + "files": [ + "src", + "dist" + ], + "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" + }, + "lint-staged": { + "**/*.sol": "yarn lint:sol && yarn format:sol" + }, + "devDependencies": { + "@types/node": "^20.1.0", + "dotenv": "^16.1.4", + "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/remappings.txt b/remappings.txt new file mode 100644 index 0000000..8504885 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,7 @@ +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/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..ea61033 --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,59 @@ +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 + try { + await rmdir(BUILD_DIR, { recursive: true }) + } catch (err) { + // Dir not found, ignore + } + + // 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..f8bfe34 --- /dev/null +++ b/scripts/constants.ts @@ -0,0 +1,6 @@ +export const BUILD_DIR = 'build' +export const DEPLOYABLE_CONTRACT_NAMES = [ + 'ERC20TokenFactory', + 'ERC721TokenFactory', + 'ERC1155TokenFactory', +] 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/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..0bd22b2 --- /dev/null +++ b/src/proxies/ERC1967/Proxy.sol @@ -0,0 +1,63 @@ +// 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 new file mode 100644 index 0000000..f2aa6e8 --- /dev/null +++ b/src/proxies/ERC1967/ProxyDeployer.sol @@ -0,0 +1,73 @@ +// 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 { // 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/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/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/ERC1155Token.sol b/src/tokens/ERC1155/ERC1155Token.sol new file mode 100644 index 0000000..943ae5f --- /dev/null +++ b/src/tokens/ERC1155/ERC1155Token.sol @@ -0,0 +1,118 @@ +// 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 {ERC2981Controlled} from "../common/ERC2981Controlled.sol"; + +error InvalidInitialization(); + +/** + * A ready made implementation of ERC-1155. + */ +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; + + /** + * 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 (ERC1155, ERC1155Metadata, ERC2981Controlled) + returns (bool) + { + 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 new file mode 100644 index 0000000..564503d --- /dev/null +++ b/src/tokens/ERC1155/ERC1155TokenFactory.sol @@ -0,0 +1,37 @@ +// 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/IERC1155TokenFactory.sol b/src/tokens/ERC1155/IERC1155TokenFactory.sol new file mode 100644 index 0000000..a266742 --- /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..f0ba098 --- /dev/null +++ b/src/tokens/ERC1155/Packed/ERC1155PackedToken.sol @@ -0,0 +1,126 @@ +// 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 new file mode 100644 index 0000000..9581a98 --- /dev/null +++ b/src/tokens/ERC1155/Packed/ERC1155PackedTokenFactory.sol @@ -0,0 +1,37 @@ +// 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 new file mode 100644 index 0000000..27ab133 --- /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/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/ERC20Token.sol b/src/tokens/ERC20/ERC20Token.sol new file mode 100644 index 0000000..9d53711 --- /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 _tokenName; + string private _tokenSymbol; + uint8 private _tokenDecimals; + + address private immutable _initializer; + bool private _initialized; + + /** + * Deploy contract. + */ + constructor() ERC20("", "") { + _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) external { + 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); + } + + // + // 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 _tokenName; + } + + /** + * Override the ERC20 symbol function. + */ + function symbol() public view override returns (string memory) { + return _tokenSymbol; + } + + /** + * Override the ERC20 decimals function. + */ + function decimals() public view override returns (uint8) { + return _tokenDecimals; + } +} diff --git a/src/tokens/ERC20/ERC20TokenFactory.sol b/src/tokens/ERC20/ERC20TokenFactory.sol new file mode 100644 index 0000000..7329f16 --- /dev/null +++ b/src/tokens/ERC20/ERC20TokenFactory.sol @@ -0,0 +1,38 @@ +// 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/IERC20TokenFactory.sol b/src/tokens/ERC20/IERC20TokenFactory.sol new file mode 100644 index 0000000..4594e11 --- /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/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/ERC721Token.sol b/src/tokens/ERC721/ERC721Token.sol new file mode 100644 index 0000000..d1618f0 --- /dev/null +++ b/src/tokens/ERC721/ERC721Token.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import { + ERC721AQueryable, IERC721AQueryable, ERC721A, IERC721A +} from "erc721a/contracts/extensions/ERC721AQueryable.sol"; +import {ERC2981Controlled} from "../common/ERC2981Controlled.sol"; + +error InvalidInitialization(); + +/** + * A ready made implementation of ERC-721. + */ +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; + string private _tokenSymbol; + + address private immutable _initializer; + bool private _initialized; + + /** + * Deploy contract. + */ + constructor() ERC721A("", "") { + _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) external { + if (msg.sender != _initializer || _initialized) { + revert InvalidInitialization(); + } + _initialized = true; + + _tokenName = tokenName; + _tokenSymbol = tokenSymbol; + _tokenBaseURI = tokenBaseURI; + + _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); + } + + // + // 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) { + _tokenBaseURI = tokenBaseURI; + } + + // + // Views + // + + /** + * Check interface support. + * @param interfaceId Interface id + * @return True if supported + */ + 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); + } + + // + // ERC721A Overrides + // + + /** + * Override the ERC721A baseURI function. + */ + function _baseURI() internal view override returns (string memory) { + return _tokenBaseURI; + } + + /** + * Override the ERC721A name function. + */ + function name() public view override (ERC721A, IERC721A) returns (string memory) { + return _tokenName; + } + + /** + * Override the ERC721A symbol function. + */ + function symbol() public view override (ERC721A, IERC721A) returns (string memory) { + return _tokenSymbol; + } +} diff --git a/src/tokens/ERC721/ERC721TokenFactory.sol b/src/tokens/ERC721/ERC721TokenFactory.sol new file mode 100644 index 0000000..1f44491 --- /dev/null +++ b/src/tokens/ERC721/ERC721TokenFactory.sol @@ -0,0 +1,38 @@ +// 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/IERC721TokenFactory.sol b/src/tokens/ERC721/IERC721TokenFactory.sol new file mode 100644 index 0000000..3c44107 --- /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/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/ERC2981Controlled.sol b/src/tokens/common/ERC2981Controlled.sol new file mode 100644 index 0000000..9204258 --- /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); + } +} 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. diff --git a/src/utils/StorageSlot.sol b/src/utils/StorageSlot.sol new file mode 100644 index 0000000..b45b9fb --- /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 { // solhint-disable-line no-inline-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 { // solhint-disable-line no-inline-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 { // solhint-disable-line no-inline-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 { // solhint-disable-line no-inline-assembly + r.slot := _slot + } + } +} 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/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); + } +} 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 new file mode 100644 index 0000000..56213ca --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1503 @@ +# 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/erc-1155@^4.0.3": + 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" + integrity sha512-q3yIR5OwsTK+HnTQVXTDlknMo2kE65rBwZN6ymPeoe1CW+RE9XqUM3QCvxTlLGuAnsIaL6/ABG2ePX4crMvbaw== + 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" + 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" + 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" + +"@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" + +"@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== + +"@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" + 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" + 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" + +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" + 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@^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== + 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== + +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" + 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== + +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" + 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== + +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" + 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@^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" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + 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" + 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== + +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" + 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" + 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" + +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== + +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" + 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" + 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" + +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-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" + 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" + 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" + +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" + +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" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + 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" + 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" + +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== + +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== + +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" + 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== + +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" + 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" + +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" + 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" + +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" + 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" + 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== + +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" + 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== + +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" + 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" + +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" + 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== + +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" + 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== + +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" + 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== + +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" + 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" + +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.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== + 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== + +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" + 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" + +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" + 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== + +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== + +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" + 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" + 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" + +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" + 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== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==