diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..cacde6e --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +PRIVATE_KEY= +INFURA_KEY= +ETHERSCAN_API_KEY= +POLYGONSCAN_API_KEY= +POLYGONSCAN_ZKEVM_API_KEY= \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..832a92d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# This group is setup to ensure a security review is always required for PRs +/src/ @0xPolygon/internal-security +/script/**/*.sol @0xPolygon/internal-security +/deployments/ @0xPolygon/internal-security diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..28769d5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,48 @@ +# Pull Request + +## Description + +Please include a summary of the change and which feature was implemented or which issue was fixed. Also, include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +### How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. + +# Checklist: + +Before deployment + +- [ ] 100% test and branch coverage +- [ ] check slither for severe issues +- [ ] fuzz and invariant tests (when applicable) +- [ ] formal verification (when applicable) +- [ ] deployment or upgrade scripts ready +- [ ] version management agreed upon and implemented +- [ ] internal team review +- [ ] **Security Team review** + +After deployment + +- [ ] transfer ownership after deployments (when applicable) +- [ ] complete upgrade (when applicable) +- [ ] generate deployment/upgrade log files +- [ ] update [static](https://github.com/maticnetwork/static/tree/master/network) with new contract address and/or version + +--- + +### Considerations + +- I have followed the [contributing guidelines](../CONTRIBUTING.md). +- My code follows the style guidelines of this project and I have run `forge fmt` and prettier to ensure the code style is valid +- I have performed a self-review of my own code +- I have commented my code, particularly in hard-to-understand areas +- I have made corresponding changes to the documentation +- My changes generate no new warnings +- I have added tests that prove my fix is effective or that my feature works +- New and existing unit tests pass locally with my changes + +### Additional context + +Add any other context about the pull request here. diff --git a/.github/workflows/pr-check.yaml b/.github/workflows/pr-check.yaml new file mode 100644 index 0000000..78af9e9 --- /dev/null +++ b/.github/workflows/pr-check.yaml @@ -0,0 +1,24 @@ +name: Source branch check +on: + pull_request: + branches: [main] + types: + - opened + - reopened + - synchronize + - edited +jobs: + check-main: + if: github.base_ref == 'main' + runs-on: ubuntu-latest + steps: + - name: Log + run: | + echo "Base ref: ${{ github.base_ref }}" + echo "Head ref: ${{ github.head_ref }}" + - name: Check branches + run: | + if [ ${{ github.head_ref }} != "staging" ]; then + echo "Merge requests to main branch are only allowed from staging branch." + exit 1 + fi diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..cc46bd4 --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,21 @@ +# checks that pre-commit hooks pass before allowing to merge a PR + +name: pre-commit +on: + pull_request: + branches: [main, master, staging, dev, feat/**, fix/**] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v2 + with: + submodules: recursive + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + - name: Install pre-commit + run: pip install pre-commit + - name: Run pre-commit + run: pre-commit run --all-files diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..42c3cda --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,36 @@ +name: test + +on: + pull_request: + branches: [main, master, staging, dev, feat/**, fix/**] + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run Forge build + run: | + forge --version + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08411c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +/target +/out +/cache +/coverage +lcov.info +.DS_Store +.env +.vscode + +broadcast/*/31337 +deployments/**/31337.* + +script/util/storage_check_cache +script/util/storage_check_report \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..712087f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,18 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/openzeppelin/openzeppelin-contracts +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/solady"] + path = lib/solady + url = https://github.com/vectorized/solady +[submodule "lib/deployment-log-generator"] + path = lib/deployment-log-generator + url = https://github.com/0xPolygon/deployment-log-generator +[submodule "lib/contract-deployer-template"] + path = lib/contract-deployer-template + url = https://github.com/0xPolygon/contract-deployer-template diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..25bf17f --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18 \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..550c23c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: mixed-line-ending + args: ["--fix=lf"] + description: Forces to replace line ending by the UNIX 'lf' character. + exclude: "^docs/autogen" + - repo: local + hooks: + - id: format + name: Format solidity code + description: Format solidity code with `forge fmt` + language: system + entry: forge fmt + exclude: "^lib/" + pass_filenames: true + - id: doc + name: Generate documentation + description: Generate docs with `forge doc` + language: system + # generates docs and unstages files if only the commit hash changed within the file, this way only when the documentation is updated, the documentation needs to be regenerated and only the changed files are pushed + entry: "script/util/doc_gen.sh" + pass_filenames: false + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.0.3" + hooks: + - id: prettier + name: Format non solidity files with prettier + exclude: "^docs/autogen" diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..45f4529 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +foundry.toml +out +lib/ +cache/ +docs/autogenerated +*.sol +deployments/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..29b171c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,26 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": true, + "trailingComma": "all", + "overrides": [ + { + "files": "*.sol", + "options": { + "printWidth": 120, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": false + } + }, + { + "files": "*.json", + "options": { + "tabWidth": 4 + } + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d1eb1dc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,177 @@ +# Contributing + +- [Install](#install) +- [Pre-commit Hooks](#pre-commit-hooks) +- [Branching](#branching) + - [Main](#main) + - [Staging](#staging) + - [Dev](#dev) + - [Feature](#feature) + - [Fix](#fix) +- [Code Practices](#code-practices) + - [Code Style](#code-style) + - [Interfaces](#interfaces) + - [NatSpec \& Comments](#natspec--comments) +- [Versioning](#versioning) +- [Testing](#testing) + - [Deployer Template](#deployer-template) +- [Deployment](#deployment) + - [Deployer Template](#deployer-template-1) + - [Deployment](#deployment-1) + - [Deployment Info Generation](#deployment-info-generation) +- [Deployer Template Script](#deployer-template-script) +- [Releases](#releases) + +## Install + +Follow these steps to set up your local environment for development: + +- [Install foundry](https://book.getfoundry.sh/getting-started/installation) +- Install dependencies: `forge install` +- [Install pre-commit](https://pre-commit.com/#installation) +- Install pre commit hooks: `pre-commit install` + +## Pre-commit Hooks + +Follow the [installation steps](#install) to enable pre-commit hooks. To ensure consistency in our formatting `pre-commit` is used to check whether code was formatted properly and the documentation is up to date. Whenever a commit does not meet the checks implemented by pre-commit, the commit will fail and the pre-commit checks will modify the files to make the commits pass. Include these changes in your commit for the next commit attempt to succeed. On pull requests the CI checks whether all pre-commit hooks were run correctly. +This repo includes the following pre-commit hooks that are defined in the `.pre-commit-config.yaml`: + +- `mixed-line-ending`: This hook ensures that all files have the same line endings (LF). +- `format`: This hook uses `forge fmt` to format all Solidity files. +- `doc`: This hook uses `forge doc` to automatically generate documentation for all Solidity files whenever the NatSpec documentation changes. The `script/util/doc_gen.sh` script is used to generate documentation. Forge updates the commit hash in the documentation automatically. To only generate new documentation when the documentation has actually changed, the script checks whether more than just the hash has changed in the documentation and discard all changes if only the hash has changed. +- `prettier`: All remaining files are formatted using prettier. + +## Branching + +This section outlines the branching strategy of this repo. + +### Main + +The main branch is supposed to reflect the deployed state on all networks. Any pull requests into this branch MUST come from the staging branch. The main branch is protected and requires a separate code review by the security team. Whenever the main branch is updated, a new release is created with the latest version. For more information on versioning, check [here](#versioning). + +### Staging + +The staging branch reflects new code complete deployments or upgrades containing fixes and/or features. Any pull requests into this branch MUST come from the dev branch. The staging branch is used for security audits and deployments. Once the deployment is complete and deployment log files are generated, the branch can be merged into main. For more information on the deployment and log file generation check [here](#deployment--versioning). + +### Dev + +This is the active development branch. All pull requests into this branch MUST come from fix or feature branches. Upon code completion this branch is merged into staging for auditing and deployment. + +### Feature + +Any new feature should be developed on a separate branch. The naming convention for these branches is `feat/*`. Once the feature is complete, a pull request into the dev branch can be created. + +### Fix + +Any bug fixes should be developed on a separate branch. The naming convention for these branches is `fix/*`. Once the fix is complete, a pull request into the dev branch can be created. + +## Code Practices + +### Code Style + +The repo follows the official [Solidity Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html). In addition to that, this repo also borrows the following rules from [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/GUIDELINES.md#solidity-conventions): + +- Internal or private state variables or functions should have an underscore prefix. + + ```solidity + contract TestContract { + uint256 private _privateVar; + uint256 internal _internalVar; + function _testInternal() internal { ... } + function _testPrivate() private { ... } + } + ``` + +- Events should generally be emitted immediately after the state change that they + represent, and should be named in the past tense. Some exceptions may be made for gas + efficiency if the result doesn't affect observable ordering of events. + + ```solidity + function _burn(address who, uint256 value) internal { + super._burn(who, value); + emit TokensBurned(who, value); + } + ``` + +- Interface names should have a capital I prefix. + + ```solidity + interface IERC777 { + ``` + +- Contracts not intended to be used standalone should be marked abstract + so they are required to be inherited to other contracts. + + ```solidity + abstract contract AccessControl is ..., { + ``` + +- Unchecked arithmetic blocks should contain comments explaining why overflow is guaranteed not to happen. If the reason is immediately apparent from the line above the unchecked block, the comment may be omitted. + +### Interfaces + +Every contract MUST implement their corresponding interface that includes all externally callable functions, errors and events. + +### NatSpec & Comments + +Interfaces should be the entrypoint for all contracts. When exploring the a contract within the repository, the interface MUST contain all relevant information to understand the functionality of the contract in the form of NatSpec comments. This includes all externally callable functions, errors and events. The NatSpec documentation MUST be added to the functions, errors and events within the interface. This allows a reader to understand the functionality of a function before moving on to the implementation. The implementing functions MUST point to the NatSpec documentation in the interface using `@inheritdoc`. Internal and private functions shouldn't have NatSpec documentation except for `@dev` comments, whenever more context is needed. Additional comments within a function should only be used to give more context to more complex operations, otherwise the code should be kept readable and self-explanatory. + +## Versioning + +This repo utilizes [semantic versioning](https://semver.org/) for smart contracts. An `IVersioned` interface is included in the [interfaces directory](src/interface/IVersioned.sol) exposing a unified versioning interface for all contracts. This version MUST be included in all contracts, whether they are upgradeable or not, to be able to easily match deployed versions. For example, in the case of a non-upgradeable contract one version could be deployed to a network and later a new version might be deployed to another network. The exposed `version()` function is also used by the [Deployment Log Generator](https://github.com/0xPolygon/deployment-log-generator#readme) to extract information about the version. + +Whenever contracts are modified, only the version of the changed contracts should be updated. Unmodified contracts should remain on the version of their last change. + +## Testing + +### Deployer Template + +This repo provides a deployer template library for consistency between scripts and unit tests. For more information on how to use the template, check [here](https://github.com/0xPolygon/contract-deployer-template#readme). + +## Deployment + +This repo utilizes versioned deployments. Any changes to a contract should update the version of this specific contract. A script is provided that extracts deployment information from the `run-latest.json` file within the `broadcast` directory generated while the forge script runs. From this information a JSON and markdown file is generated containing various information about the deployment itself as well as past deployments. + +### Deployer Template + +This repo provides a deployer template library for consistency between scripts and unit tests. For more information on how to use the template, check [here](https://github.com/0xPolygon/contract-deployer-template#readme). + +### Deployment + +This repo set up the following RPCs in the `foundry.toml` file: + +- mainnet: Ethereum Mainnet +- goerli: Ethereum Goerli +- sepolia: Ethereum Sepolia +- polygon_pos: Polygon PoS +- mumbai: Polygon Mumbai +- polygon_zkevm: Polygon zkEVM +- polygon_zkevm_testnet: Polygon zkEVM Testnet + +To deploy the contracts, provide the `--broadcast` flag to the forge script command. Should the etherscan verification time out, it can be picked up again by replacing the `--broadcast` flag with `--resume`. +Deploy the contracts to one of the predefined networks by providing the according key with the `--rpc-url` flag. Most of the predefined networks require the `INFURA_KEY` environment variable to be set in the `.env` file. +Including the `--verify` flag will verify deployed contracts on Etherscan. Define the appropriate environment variable for the Etherscan api key in the `.env` file. + +```shell +forge script script/Deploy.s.sol --broadcast --rpc-url --verify +``` + +## Releases + +Releases should be created whenever the code on the main branch is updated to reflect a deployment or an upgrade on a network. The release should be named after the version of the contracts deployed or upgraded. +The release should include the following: + +- In case of a MAJOR version + - changelog + - summary of breaking changes + - summary of new features + - summary of fixes +- In case of a MINOR version + - changelog + - summary of new features + - summary of fixes +- In case of a PATCH version + - changelog + - summary of fixes +- Deployment information (can be copied from the generated log files) + - Addresses of the deployed contracts diff --git a/LICENSE-APACHE.md b/LICENSE-APACHE.md new file mode 100644 index 0000000..4f05bc1 --- /dev/null +++ b/LICENSE-APACHE.md @@ -0,0 +1,176 @@ + 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 diff --git a/LICENSE-MIT.md b/LICENSE-MIT.md new file mode 100644 index 0000000..f7f7438 --- /dev/null +++ b/LICENSE-MIT.md @@ -0,0 +1,7 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 497407f..2fa34f9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,66 @@ -# foundry-template -Contracts team, template repo +## Template Repo (Foundry) + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![CI Status](../../actions/workflows/test.yaml/badge.svg)](../../actions) + +This template repo is a quick and easy way to get started with a new Solidity project. It comes with a number of features that are useful for developing and deploying smart contracts. Such as: + +- Pre-commit hooks for formatting, auto generated documentation, and more +- Various libraries with useful contracts (OpenZeppelin, Solady) and libraries (Deployment log generation, storage checks, deployer templates) + +#### Table of Contents + +- [Setup](#setup) +- [Deployment](#deployment) +- [Docs](#docs) +- [Contributing](#contributing) +- [License](#license) + +## Setup + +Follow these steps to set up your local environment: + +- [Install foundry](https://book.getfoundry.sh/getting-started/installation) +- Install dependencies: `forge install` +- Build contracts: `forge build` +- Test contracts: `forge test` + +If you intend to develop on this repo, follow the steps outlined in [CONTRIBUTING.md](CONTRIBUTING.md#install). + +## Deployment + +This repo utilizes versioned deployments. For more information on how to use forge scripts within the repo, check [here](CONTRIBUTING.md#deployment). + +Smart contracts are deployed or upgraded using the following command: + +```shell +forge script script/Deploy.s.sol --broadcast --rpc-url --verify +``` + +## Docs + +The documentation and architecture diagrams for the contracts within this repo can be found [here](docs/). +Detailed documentation generated from the NatSpec documentation of the contracts can be found [here](docs/autogen/src/src/). +When exploring the contracts within this repository, it is recommended to start with the interfaces first and then move on to the implementation as outlined [here](CONTRIBUTING.md#natspec--comments) + +## Contributing + +If you want to contribute to this project, please check [CONTRIBUTING.md](CONTRIBUTING.md) first. + +## License + +​ +Licensed under either of +​ + +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + ​ + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +--- + +© 2023 PT Services DMCC diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..53191ae --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,22 @@ +# Polygon Technology Security Information + +## Link to vulnerability disclosure details (Bug Bounty). + +- Websites and Applications: https://hackerone.com/polygon-technology +- Smart Contracts: https://immunefi.com/bounty/polygon + +## Languages that our team speaks and understands. + +Preferred-Languages: en + +## Security-related job openings at Polygon. + +https://polygon.technology/careers + +## Polygon security contact details. + +security@polygon.technology + +## The URL for accessing the security.txt file. + +Canonical: https://polygon.technology/security.txt diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..14ae96a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +# Documentation + +Documentation about the project can be placed here. This is a good place to put architecture diagrams, or other documentation that is not specific to a single contract. diff --git a/docs/autogen/.gitignore b/docs/autogen/.gitignore new file mode 100644 index 0000000..4e42a1b --- /dev/null +++ b/docs/autogen/.gitignore @@ -0,0 +1 @@ +book/ \ No newline at end of file diff --git a/docs/autogen/book.css b/docs/autogen/book.css new file mode 100644 index 0000000..b5ce903 --- /dev/null +++ b/docs/autogen/book.css @@ -0,0 +1,13 @@ +table { + margin: 0 auto; + border-collapse: collapse; + width: 100%; +} + +table td:first-child { + width: 15%; +} + +table td:nth-child(2) { + width: 25%; +} \ No newline at end of file diff --git a/docs/autogen/book.toml b/docs/autogen/book.toml new file mode 100644 index 0000000..05beb66 --- /dev/null +++ b/docs/autogen/book.toml @@ -0,0 +1,12 @@ +[book] +src = "src" +title = "" + +[output.html] +no-section-label = true +additional-js = ["solidity.min.js"] +additional-css = ["book.css"] +git-repository-url = "https://github.com/0xPolygon/foundry-template" + +[output.html.fold] +enable = true diff --git a/docs/autogen/solidity.min.js b/docs/autogen/solidity.min.js new file mode 100644 index 0000000..1924932 --- /dev/null +++ b/docs/autogen/solidity.min.js @@ -0,0 +1,74 @@ +hljs.registerLanguage("solidity",(()=>{"use strict";function e(){try{return!0 +}catch(e){return!1}} +var a=/-?(\b0[xX]([a-fA-F0-9]_?)*[a-fA-F0-9]|(\b[1-9](_?\d)*(\.((\d_?)*\d)?)?|\.\d(_?\d)*)([eE][-+]?\d(_?\d)*)?|\b0)(?!\w|\$)/ +;e()&&(a=a.source.replace(/\\b/g,"(?{ +var a=r(e),o=l(e),c=/[A-Za-z_$][A-Za-z_$0-9.]*/,d=e.inherit(e.TITLE_MODE,{ +begin:/[A-Za-z$_][0-9A-Za-z$_]*/,lexemes:c,keywords:n}),u={className:"params", +begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,lexemes:c,keywords:n, +contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,o,s]},_={ +className:"operator",begin:/:=|->/};return{keywords:n,lexemes:c, +contains:[a,o,i,t,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,_,{ +className:"function",lexemes:c,beginKeywords:"function",end:"{",excludeEnd:!0, +contains:[d,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,_]}]}}, +solAposStringMode:r,solQuoteStringMode:l,HEX_APOS_STRING_MODE:i, +HEX_QUOTE_STRING_MODE:t,SOL_NUMBER:s,isNegativeLookbehindAvailable:e} +;const{baseAssembly:c,solAposStringMode:d,solQuoteStringMode:u,HEX_APOS_STRING_MODE:_,HEX_QUOTE_STRING_MODE:m,SOL_NUMBER:b,isNegativeLookbehindAvailable:E}=o +;return e=>{for(var a=d(e),s=u(e),n=[],i=0;i<32;i++)n[i]=i+1 +;var t=n.map((e=>8*e)),r=[];for(i=0;i<=80;i++)r[i]=i +;var l=n.map((e=>"bytes"+e)).join(" ")+" ",o=t.map((e=>"uint"+e)).join(" ")+" ",g=t.map((e=>"int"+e)).join(" ")+" ",M=[].concat.apply([],t.map((e=>r.map((a=>e+"x"+a))))),p={ +keyword:"var bool string int uint "+g+o+"byte bytes "+l+"fixed ufixed "+M.map((e=>"fixed"+e)).join(" ")+" "+M.map((e=>"ufixed"+e)).join(" ")+" enum struct mapping address new delete if else for while continue break return throw emit try catch revert unchecked _ function modifier event constructor fallback receive error virtual override constant immutable anonymous indexed storage memory calldata external public internal payable pure view private returns import from as using pragma contract interface library is abstract type assembly", +literal:"true false wei gwei szabo finney ether seconds minutes hours days weeks years", +built_in:"self this super selfdestruct suicide now msg block tx abi blockhash gasleft assert require Error Panic sha3 sha256 keccak256 ripemd160 ecrecover addmod mulmod log0 log1 log2 log3 log4" +},O={className:"operator",begin:/[+\-!~*\/%<>&^|=]/ +},C=/[A-Za-z_$][A-Za-z_$0-9]*/,N={className:"params",begin:/\(/,end:/\)/, +excludeBegin:!0,excludeEnd:!0,lexemes:C,keywords:p, +contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,b,"self"]},f={ +begin:/\.\s*/,end:/[^A-Za-z0-9$_\.]/,excludeBegin:!0,excludeEnd:!0,keywords:{ +built_in:"gas value selector address length push pop send transfer call callcode delegatecall staticcall balance code codehash wrap unwrap name creationCode runtimeCode interfaceId min max" +},relevance:2},y=e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/, +lexemes:C,keywords:p}),w={className:"built_in", +begin:(E()?"(? --verify +``` + +## Docs + +The documentation and architecture diagrams for the contracts within this repo can be found [here](docs/). +Detailed documentation generated from the NatSpec documentation of the contracts can be found [here](docs/autogen/src/src/). +When exploring the contracts within this repository, it is recommended to start with the interfaces first and then move on to the implementation as outlined [here](CONTRIBUTING.md#natspec--comments) + +## Contributing + +If you want to contribute to this project, please check [CONTRIBUTING.md](CONTRIBUTING.md) first. + +## License + +​ +Licensed under either of +​ + +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + ​ + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +--- + +© 2023 PT Services DMCC diff --git a/docs/autogen/src/SUMMARY.md b/docs/autogen/src/SUMMARY.md new file mode 100644 index 0000000..1c37b0f --- /dev/null +++ b/docs/autogen/src/SUMMARY.md @@ -0,0 +1,7 @@ +# Summary +- [Home](README.md) +# src + - [❱ interface](src/interface/README.md) + - [ICounter](src/interface/ICounter.sol/interface.ICounter.md) + - [IVersioned](src/interface/IVersioned.sol/interface.IVersioned.md) + - [Counter](src/Counter.sol/contract.Counter.md) diff --git a/docs/autogen/src/src/Counter.sol/contract.Counter.md b/docs/autogen/src/src/Counter.sol/contract.Counter.md new file mode 100644 index 0000000..c2acdc5 --- /dev/null +++ b/docs/autogen/src/src/Counter.sol/contract.Counter.md @@ -0,0 +1,67 @@ +# Counter +[Git Source](https://github.com/0xPolygon/foundry-template/blob/e3be09e288733981d374eff2b25e6c283a17801f/src/Counter.sol) + +**Inherits:** +[ICounter](/src/interface/ICounter.sol/interface.ICounter.md), Initializable + + +## State Variables +### number + +```solidity +uint256 public number; +``` + + +## Functions +### constructor + + +```solidity +constructor(); +``` + +### initialize + + +```solidity +function initialize(uint256 initialNumber) public initializer; +``` + +### setNumber + +Sets the number + + +```solidity +function setNumber(uint256 newNumber) public; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newNumber`|`uint256`|The new number| + + +### increment + +Increments the number by 1 + + +```solidity +function increment() public; +``` + +### version + + +```solidity +function version() external pure returns (string memory); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`string`|The version of the contract| + + diff --git a/docs/autogen/src/src/README.md b/docs/autogen/src/src/README.md new file mode 100644 index 0000000..1b1cc23 --- /dev/null +++ b/docs/autogen/src/src/README.md @@ -0,0 +1,5 @@ + + +# Contents +- [interface](/src/interface) +- [Counter](Counter.sol/contract.Counter.md) diff --git a/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md b/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md new file mode 100644 index 0000000..b0392a4 --- /dev/null +++ b/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md @@ -0,0 +1,45 @@ +# ICounter +[Git Source](https://github.com/0xPolygon/foundry-template/blob/e3be09e288733981d374eff2b25e6c283a17801f/src/interface/ICounter.sol) + +**Inherits:** +[IVersioned](/src/interface/IVersioned.sol/interface.IVersioned.md) + + +## Functions +### number + + +```solidity +function number() external view returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The current number| + + +### setNumber + +Sets the number + + +```solidity +function setNumber(uint256 newNumber) external; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newNumber`|`uint256`|The new number| + + +### increment + +Increments the number by 1 + + +```solidity +function increment() external; +``` + diff --git a/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md b/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md new file mode 100644 index 0000000..c319a0e --- /dev/null +++ b/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md @@ -0,0 +1,18 @@ +# IVersioned +[Git Source](https://github.com/0xPolygon/foundry-template/blob/55b07186cd4779cbe55cc2f262f992aeabaf34ad/src/interface/IVersioned.sol) + + +## Functions +### version + + +```solidity +function version() external pure returns (string memory); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`string`|The version of the contract| + + diff --git a/docs/autogen/src/src/interface/README.md b/docs/autogen/src/src/interface/README.md new file mode 100644 index 0000000..b060352 --- /dev/null +++ b/docs/autogen/src/src/interface/README.md @@ -0,0 +1,5 @@ + + +# Contents +- [ICounter](ICounter.sol/interface.ICounter.md) +- [IVersioned](IVersioned.sol/interface.IVersioned.md) diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..baea3d3 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,45 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +optimizer = true +optimizer_runs = 999999 +via_ir = true +solc = "0.8.23" +verbosity = 2 +ffi = true + +remappings = [ + "forge-std=lib/forge-std/src", + "@openzeppelin/contracts=lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts" +] + +[profile.intense.fuzz] +runs = 10000 +max_test_rejects = 999999 + +[fmt] +line_length = 160 +number_underscore = "thousands" + +[rpc_endpoints] +anvil = "http://127.0.0.1:8545" +mainnet = "https://mainnet.infura.io/v3/${INFURA_KEY}" +goerli = "https://goerli.infura.io/v3/${INFURA_KEY}" +sepolia = "https://sepolia.infura.io/v3/${INFURA_KEY}" +polygon_pos = "https://polygon-mainnet.infura.io/v3/${INFURA_KEY}" +mumbai = "https://polygon-mumbai.infura.io/v3/${INFURA_KEY}" +polygon_zkevm = "https://zkevm-rpc.com" +polygon_zkevm_testnet = "https://rpc.public.zkevm-test.net" + +[etherscan] +mainnet = { key = "${ETHERSCAN_API_KEY}" } +goerli = { key = "${ETHERSCAN_API_KEY}" } +sepolia = { key = "${ETHERSCAN_API_KEY}" } +polygon_pos = { key = "${POLYGONSCAN_API_KEY}" } +mumbai = { key = "${POLYGONSCAN_API_KEY}" } +polygon_zkevm = { key = "${POLYGONSCAN_ZKEVM_API_KEY}" } +polygon_zkevm_testnet = { key = "${POLYGONSCAN_ZKEVM_API_KEY}" } + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/lib/contract-deployer-template b/lib/contract-deployer-template new file mode 160000 index 0000000..3101afc --- /dev/null +++ b/lib/contract-deployer-template @@ -0,0 +1 @@ +Subproject commit 3101afcefef8a677dec4562d1a1b73c74b4364d8 diff --git a/lib/deployment-log-generator b/lib/deployment-log-generator new file mode 160000 index 0000000..f3fc0bf --- /dev/null +++ b/lib/deployment-log-generator @@ -0,0 +1 @@ +Subproject commit f3fc0bfd4a7caee49cf6eb6ac172bc012a339fc1 diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..2f11269 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 2f112697506eab12d433a65fdc31a639548fe365 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..932fddf --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 932fddf69a699a9a80fd2396fd1a2ab91cdda123 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..625fb3c --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 625fb3c2b2696f1747ba2e72d1e1113066e6c177 diff --git a/lib/solady b/lib/solady new file mode 160000 index 0000000..c565332 --- /dev/null +++ b/lib/solady @@ -0,0 +1 @@ +Subproject commit c565332afef2d4f993fde402541487e4e331fddd diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..a8ee7f8 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import "forge-std/Script.sol"; +import "script/util/ScriptHelpers.sol"; + +import "script/deployers/CounterDeployer.s.sol"; + +contract Deploy is Script, ScriptHelpers, CounterDeployer { + using stdJson for string; + + function run() public { + address proxyAdmin = address(1); + uint256 initialNumber = 5; + deployCounterTransparent(proxyAdmin, initialNumber); + } +} diff --git a/script/deployers/CounterDeployer.s.sol b/script/deployers/CounterDeployer.s.sol new file mode 100644 index 0000000..a99f915 --- /dev/null +++ b/script/deployers/CounterDeployer.s.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "src/Counter.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract CounterDeployer is Script { + Counter internal counter; + ProxyAdmin internal counterProxyAdmin; + address internal counterImplementation; + + function deployCounterTransparent(address proxyAdminOwner, uint256 initialNumber) + internal + returns (address implementation, address proxyAdmin, address proxy) + { + bytes memory initData = abi.encodeCall(Counter.initialize, (initialNumber)); + + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + + counterImplementation = address(new Counter()); + counter = Counter(address(new TransparentUpgradeableProxy(counterImplementation, proxyAdminOwner, initData))); + + vm.stopBroadcast(); + + counterProxyAdmin = + ProxyAdmin(address(uint160(uint256(vm.load(address(counter), hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"))))); + + return (counterImplementation, address(counterProxyAdmin), address(counter)); + } + + function deployCounterImplementation() internal returns (address implementation) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address(new Counter()); + vm.stopBroadcast(); + } +} diff --git a/script/util/ScriptHelpers.sol b/script/util/ScriptHelpers.sol new file mode 100644 index 0000000..18a1247 --- /dev/null +++ b/script/util/ScriptHelpers.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import "forge-std/Script.sol"; + +abstract contract ScriptHelpers is Script {} diff --git a/script/util/doc_gen.sh b/script/util/doc_gen.sh new file mode 100755 index 0000000..97ed58c --- /dev/null +++ b/script/util/doc_gen.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +foundryup +# generate docs +forge doc -b -o docs/autogen + +# Unstage all docs where only the commit hash changed +# Get a list of all unstaged files in the directory +files=$(git diff --name-only -- 'docs/autogen/*') + +# Loop over each file +for file in $files; do + # Get the diff for the file, only lines that start with - or + + diff=$(git diff $file | grep '^[+-][^+-]') + # Check if there are any other changes in the diff besides the commit hash (in that case the file has more than 1 line that changed, one minus one plus) + if [[ $(echo "$diff" | wc -l) -eq 2 ]]; then + # If there are no other changes, discard the changes for the file + git reset HEAD $file + git checkout -- $file + fi +done \ No newline at end of file diff --git a/slither.config.json b/slither.config.json new file mode 100644 index 0000000..e0d7e2a --- /dev/null +++ b/slither.config.json @@ -0,0 +1,3 @@ +{ + "filter_paths": "(lib/|test/|scripts/)" +} diff --git a/src/Counter.sol b/src/Counter.sol new file mode 100644 index 0000000..37782ec --- /dev/null +++ b/src/Counter.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {ICounter, IVersioned} from "./interface/ICounter.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract Counter is ICounter, Initializable { + uint256 public number; + + constructor() { + _disableInitializers(); + } + + function initialize(uint256 initialNumber) public initializer { + number = initialNumber; + } + + /// @inheritdoc ICounter + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + /// @inheritdoc ICounter + function increment() public { + number++; + } + + /// @inheritdoc IVersioned + function version() external pure returns (string memory) { + return "1.0.0"; + } +} diff --git a/src/interface/ICounter.sol b/src/interface/ICounter.sol new file mode 100644 index 0000000..57985b4 --- /dev/null +++ b/src/interface/ICounter.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {IVersioned} from "./IVersioned.sol"; + +interface ICounter is IVersioned { + /// @return The current number + function number() external view returns (uint256); + + /// @notice Sets the number + /// @param newNumber The new number + function setNumber(uint256 newNumber) external; + + /// @notice Increments the number by 1 + function increment() external; +} diff --git a/src/interface/IVersioned.sol b/src/interface/IVersioned.sol new file mode 100644 index 0000000..0897f48 --- /dev/null +++ b/src/interface/IVersioned.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +interface IVersioned { + /// @return The version of the contract + function version() external pure returns (string memory); +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol new file mode 100644 index 0000000..04cfd67 --- /dev/null +++ b/test/Counter.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; +import "test/util/TestHelpers.sol"; + +import "script/deployers/CounterDeployer.s.sol"; + +abstract contract BeforeScript is Test, TestHelpers, CounterDeployer { + function setUp() public virtual { + counter = Counter(deployCounterImplementation()); + } +} + +contract CounterTest_Zero is BeforeScript { + function test_InitialState() public { + assertEq(counter.number(), 0); + } + + function test_RevertsOnInitialization(uint256 number) public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + counter.initialize(number); + } +} + +abstract contract AfterScript is Test, TestHelpers, CounterDeployer { + function setUp() public virtual { + address proxyAdmin = makeAddr("alice"); + uint256 initialNumber = 10; + deployCounterTransparent(proxyAdmin, initialNumber); + } +} + +contract CounterTest_Initialized is AfterScript { + function test_IsInitialized() public { + assertEq(counter.number(), 10); + } + + function test_RevertsIf_InitializedAgain() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + counter.initialize(1); + } + + function test_IncrementsNumber() public { + counter.increment(); + assertEq(counter.number(), 11); + } + + function testFuzz_SetsNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } + + function test_ReturnsVersion() public { + assertEq(counter.version(), "1.0.0"); + } +} diff --git a/test/util/TestHelpers.sol b/test/util/TestHelpers.sol new file mode 100644 index 0000000..b524dc5 --- /dev/null +++ b/test/util/TestHelpers.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import "forge-std/Test.sol"; + +abstract contract TestHelpers is Test { + Account internal DEPLOYER; + + constructor() { + DEPLOYER = makeAccount("DEPLOYER"); + vm.setEnv("PRIVATE_KEY", vm.toString(DEPLOYER.key)); + } +}