From c2e35e293534e27e33a21992e755d37a77c00712 Mon Sep 17 00:00:00 2001 From: a-t-0 Date: Thu, 28 Mar 2024 10:47:42 +0100 Subject: [PATCH] feat: initial commit --- .editorconfig | 19 ++++ .env.example | 11 +++ .github/workflows/ci.yml | 92 +++++++++++++++++ .gitignore | 20 ++++ .gitpod.yml | 14 +++ .prettierignore | 17 ++++ .prettierrc.yml | 7 ++ .solhint.json | 14 +++ .vscode/settings.json | 9 ++ LICENSE.md | 16 +++ README.md | 206 +++++++++++++++++++++++++++++++++++++++ bun.lockb | Bin 0 -> 28807 bytes foundry.toml | 53 ++++++++++ package.json | 39 ++++++++ remappings.txt | 3 + script/Base.s.sol | 41 ++++++++ script/Deploy.s.sol | 13 +++ src/Foo.sol | 8 ++ test/Foo.t.sol | 57 +++++++++++ 19 files changed, 639 insertions(+) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .gitpod.yml create mode 100644 .prettierignore create mode 100644 .prettierrc.yml create mode 100644 .solhint.json create mode 100644 .vscode/settings.json create mode 100644 LICENSE.md create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 foundry.toml create mode 100644 package.json create mode 100644 remappings.txt create mode 100644 script/Base.s.sol create mode 100644 script/Deploy.s.sol create mode 100644 src/Foo.sol create mode 100644 test/Foo.t.sol diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..746ae31 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# All files +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.sol] +indent_size = 4 + +[*.tree] +indent_size = 1 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..98c1028 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +export API_KEY_ALCHEMY="YOUR_API_KEY_ALCHEMY" +export API_KEY_ARBISCAN="YOUR_API_KEY_ARBISCAN" +export API_KEY_BSCSCAN="YOUR_API_KEY_BSCSCAN" +export API_KEY_ETHERSCAN="YOUR_API_KEY_ETHERSCAN" +export API_KEY_GNOSISSCAN="YOUR_API_KEY_GNOSISSCAN" +export API_KEY_INFURA="YOUR_API_KEY_INFURA" +export API_KEY_OPTIMISTIC_ETHERSCAN="YOUR_API_KEY_OPTIMISTIC_ETHERSCAN" +export API_KEY_POLYGONSCAN="YOUR_API_KEY_POLYGONSCAN" +export API_KEY_SNOWTRACE="YOUR_API_KEY_SNOWTRACE" +export MNEMONIC="YOUR_MNEMONIC" +export FOUNDRY_PROFILE="default" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7550749 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,92 @@ +name: "CI" + +env: + API_KEY_ALCHEMY: ${{ secrets.API_KEY_ALCHEMY }} + FOUNDRY_PROFILE: "ci" + +on: + workflow_dispatch: + pull_request: + push: + branches: + - "main" + +jobs: + lint: + runs-on: "ubuntu-latest" + steps: + - name: "Check out the repo" + uses: "actions/checkout@v4" + + - name: "Install Foundry" + uses: "foundry-rs/foundry-toolchain@v1" + + - name: "Install Bun" + uses: "oven-sh/setup-bun@v1" + + - name: "Install the Node.js dependencies" + run: "bun install" + + - name: "Lint the code" + run: "bun run lint" + + - name: "Add lint summary" + run: | + echo "## Lint result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + build: + runs-on: "ubuntu-latest" + steps: + - name: "Check out the repo" + uses: "actions/checkout@v4" + + - name: "Install Foundry" + uses: "foundry-rs/foundry-toolchain@v1" + + - name: "Install Bun" + uses: "oven-sh/setup-bun@v1" + + - name: "Install the Node.js dependencies" + run: "bun install" + + - name: "Build the contracts and print their size" + run: "forge build --sizes" + + - name: "Add build summary" + run: | + echo "## Build result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + test: + needs: ["lint", "build"] + runs-on: "ubuntu-latest" + steps: + - name: "Check out the repo" + uses: "actions/checkout@v4" + + - name: "Install Foundry" + uses: "foundry-rs/foundry-toolchain@v1" + + - name: "Install Bun" + uses: "oven-sh/setup-bun@v1" + + - name: "Install the Node.js dependencies" + run: "bun install" + + - name: "Show the Foundry config" + run: "forge config" + + - name: "Generate a fuzz seed that changes weekly to avoid burning through RPC allowance" + run: > + echo "FOUNDRY_FUZZ_SEED=$( + echo $(($EPOCHSECONDS - $EPOCHSECONDS % 604800)) + )" >> $GITHUB_ENV + + - name: "Run the tests" + run: "forge test" + + - name: "Add test summary" + run: | + echo "## Tests result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e108b40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# directories +cache +coverage +node_modules +out + +# files +*.env +*.log +.DS_Store +.pnp.* +lcov.info +package-lock.json +pnpm-lock.yaml +yarn.lock + +# broadcasts +!broadcast +broadcast/* +broadcast/*/31337/ diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..b9646d8 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,14 @@ +image: "gitpod/workspace-bun" + +tasks: + - name: "Install dependencies" + before: | + curl -L https://foundry.paradigm.xyz | bash + source ~/.bashrc + foundryup + init: "bun install" + +vscode: + extensions: + - "esbenp.prettier-vscode" + - "NomicFoundation.hardhat-solidity" diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..3996d20 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,17 @@ +# directories +broadcast +cache +coverage +node_modules +out + +# files +*.env +*.log +.DS_Store +.pnp.* +bun.lockb +lcov.info +package-lock.json +pnpm-lock.yaml +yarn.lock diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..a1ecdbb --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,7 @@ +bracketSpacing: true +printWidth: 120 +proseWrap: "always" +singleQuote: false +tabWidth: 2 +trailingComma: "all" +useTabs: false diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..7a15ca0 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,14 @@ +{ + "extends": "solhint:recommended", + "rules": { + "code-complexity": ["error", 8], + "compiler-version": ["error", ">=0.8.23"], + "func-name-mixedcase": "off", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "named-parameters-mapping": "warn", + "no-console": "off", + "not-rely-on-time": "off", + "one-contract-per-file": "off" + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..241108b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "[solidity]": { + "editor.defaultFormatter": "NomicFoundation.hardhat-solidity" + }, + "[toml]": { + "editor.defaultFormatter": "tamasfe.even-better-toml" + }, + "solidity.formatter": "forge" +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0424fa0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,16 @@ +MIT License + +Copyright (c) 2024 Paul Razvan Berg + +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 new file mode 100644 index 0000000..5f2f708 --- /dev/null +++ b/README.md @@ -0,0 +1,206 @@ +# Foundry Template [![Open in Gitpod][gitpod-badge]][gitpod] [![Github Actions][gha-badge]][gha] [![Foundry][foundry-badge]][foundry] [![License: MIT][license-badge]][license] + +[gitpod]: https://gitpod.io/#https://github.com/TruCol/foundry-template +[gitpod-badge]: https://img.shields.io/badge/Gitpod-Open%20in%20Gitpod-FFB45B?logo=gitpod +[gha]: https://github.com/TruCol/foundry-template/actions +[gha-badge]: https://github.com/TruCol/foundry-template/actions/workflows/ci.yml/badge.svg +[foundry]: https://getfoundry.sh/ +[foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg +[license]: https://opensource.org/licenses/MIT +[license-badge]: https://img.shields.io/badge/License-MIT-blue.svg + +A Foundry-based template for developing Solidity smart contracts, with sensible defaults. + +## What's Inside + +- [Forge](https://github.com/foundry-rs/foundry/blob/master/forge): compile, test, fuzz, format, and deploy smart + contracts +- [Forge Std](https://github.com/foundry-rs/forge-std): collection of helpful contracts and cheatcodes for testing +- [PRBTest](https://github.com/PaulRBerg/prb-test): modern collection of testing assertions and logging utilities +- [Prettier](https://github.com/prettier/prettier): code formatter for non-Solidity files +- [Solhint](https://github.com/protofire/solhint): linter for Solidity code + +## Getting Started + +Click the [`Use this template`](https://github.com/PaulRBerg/foundry-template/generate) button at the top of the page to +create a new repository with this repo as the initial state. + +Or, if you prefer to install the template manually: + +```sh +$ mkdir my-project +$ cd my-project +$ forge init --template PaulRBerg/foundry-template +$ bun install # install Solhint, Prettier, and other Node.js deps +``` + +If this is your first time with Foundry, check out the +[installation](https://github.com/foundry-rs/foundry#installation) instructions. + +## Features + +This template builds upon the frameworks and libraries mentioned above, so please consult their respective documentation +for details about their specific features. + +For example, if you're interested in exploring Foundry in more detail, you should look at the +[Foundry Book](https://book.getfoundry.sh/). In particular, you may be interested in reading the +[Writing Tests](https://book.getfoundry.sh/forge/writing-tests.html) tutorial. + +### Sensible Defaults + +This template comes with a set of sensible default configurations for you to use. These defaults can be found in the +following files: + +```text +├── .editorconfig +├── .gitignore +├── .prettierignore +├── .prettierrc.yml +├── .solhint.json +├── foundry.toml +└── remappings.txt +``` + +### VSCode Integration + +This template is IDE agnostic, but for the best user experience, you may want to use it in VSCode alongside Nomic +Foundation's [Solidity extension](https://marketplace.visualstudio.com/items?itemName=NomicFoundation.hardhat-solidity). + +For guidance on how to integrate a Foundry project in VSCode, please refer to this +[guide](https://book.getfoundry.sh/config/vscode). + +### GitHub Actions + +This template comes with GitHub Actions pre-configured. Your contracts will be linted and tested on every push and pull +request made to the `main` branch. + +You can edit the CI script in [.github/workflows/ci.yml](./.github/workflows/ci.yml). + +## Installing Dependencies + +Foundry typically uses git submodules to manage dependencies, but this template uses Node.js packages because +[submodules don't scale](https://twitter.com/PaulRBerg/status/1736695487057531328). + +This is how to install dependencies: + +1. Install the dependency using your preferred package manager, e.g. `bun install dependency-name` + - Use this syntax to install from GitHub: `bun install github:username/repo-name` +2. Add a remapping for the dependency in [remappings.txt](./remappings.txt), e.g. + `dependency-name=node_modules/dependency-name` + +Note that OpenZeppelin Contracts is pre-installed, so you can follow that as an example. + +## Writing Tests + +To write a new test contract, you start by importing [PRBTest](https://github.com/PaulRBerg/prb-test) and inherit from +it in your test contract. PRBTest comes with a pre-instantiated [cheatcodes](https://book.getfoundry.sh/cheatcodes/) +environment accessible via the `vm` property. If you would like to view the logs in the terminal output you can add the +`-vvv` flag and use [console.log](https://book.getfoundry.sh/faq?highlight=console.log#how-do-i-use-consolelog). + +This template comes with an example test contract [Foo.t.sol](./test/Foo.t.sol) + +## Usage + +This is a list of the most frequently needed commands. + +### Build + +Build the contracts: + +```sh +$ forge build +``` + +### Clean + +Delete the build artifacts and cache directories: + +```sh +$ forge clean +``` + +### Compile + +Compile the contracts: + +```sh +$ forge build +``` + +### Coverage + +Get a test coverage report: + +```sh +$ forge coverage +``` + +### Deploy + +Deploy to Anvil: + +```sh +$ forge script script/Deploy.s.sol --broadcast --fork-url http://localhost:8545 +``` + +For this script to work, you need to have a `MNEMONIC` environment variable set to a valid +[BIP39 mnemonic](https://iancoleman.io/bip39/). + +For instructions on how to deploy to a testnet or mainnet, check out the +[Solidity Scripting](https://book.getfoundry.sh/tutorials/solidity-scripting.html) tutorial. + +### Format + +Format the contracts: + +```sh +$ forge fmt +``` + +### Gas Usage + +Get a gas report: + +```sh +$ forge test --gas-report +``` + +### Lint + +Lint the contracts: + +```sh +$ bun run lint +``` + +### Test + +Run the tests: + +```sh +$ forge test +``` + +Generate test coverage and output result to the terminal: + +```sh +$ bun run test:coverage +``` + +Generate test coverage with lcov report (you'll have to open the `./coverage/index.html` file in your browser, to do so +simply copy paste the path): + +```sh +$ bun run test:coverage:report +``` + +## Related Efforts + +- [abigger87/femplate](https://github.com/abigger87/femplate) +- [cleanunicorn/ethereum-smartcontract-template](https://github.com/cleanunicorn/ethereum-smartcontract-template) +- [foundry-rs/forge-template](https://github.com/foundry-rs/forge-template) +- [FrankieIsLost/forge-template](https://github.com/FrankieIsLost/forge-template) + +## License + +This project is licensed under MIT. diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..204be629ba773796c7f5e6039f60fe58079ca484 GIT binary patch literal 28807 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.8.23 <0.9.0; + +import { Script } from "forge-std/src/Script.sol"; + +abstract contract BaseScript is Script { + /// @dev Included to enable compilation of the script without a $MNEMONIC environment variable. + string internal constant TEST_MNEMONIC = "test test test test test test test test test test test junk"; + + /// @dev Needed for the deterministic deployments. + bytes32 internal constant ZERO_SALT = bytes32(0); + + /// @dev The address of the transaction broadcaster. + address internal broadcaster; + + /// @dev Used to derive the broadcaster's address if $ETH_FROM is not defined. + string internal mnemonic; + + /// @dev Initializes the transaction broadcaster like this: + /// + /// - If $ETH_FROM is defined, use it. + /// - Otherwise, derive the broadcaster address from $MNEMONIC. + /// - If $MNEMONIC is not defined, default to a test mnemonic. + /// + /// The use case for $ETH_FROM is to specify the broadcaster key and its address via the command line. + constructor() { + address from = vm.envOr({ name: "ETH_FROM", defaultValue: address(0) }); + if (from != address(0)) { + broadcaster = from; + } else { + mnemonic = vm.envOr({ name: "MNEMONIC", defaultValue: TEST_MNEMONIC }); + (broadcaster,) = deriveRememberKey({ mnemonic: mnemonic, index: 0 }); + } + } + + modifier broadcast() { + vm.startBroadcast(broadcaster); + _; + vm.stopBroadcast(); + } +} diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..4d977c4 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.23 <0.9.0; + +import { Foo } from "../src/Foo.sol"; + +import { BaseScript } from "./Base.s.sol"; + +/// @dev See the Solidity Scripting tutorial: https://book.getfoundry.sh/tutorials/solidity-scripting +contract Deploy is BaseScript { + function run() public broadcast returns (Foo foo) { + foo = new Foo(); + } +} diff --git a/src/Foo.sol b/src/Foo.sol new file mode 100644 index 0000000..2c0986d --- /dev/null +++ b/src/Foo.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.23; + +contract Foo { + function id(uint256 value) external pure returns (uint256) { + return value; + } +} diff --git a/test/Foo.t.sol b/test/Foo.t.sol new file mode 100644 index 0000000..ca8cc5e --- /dev/null +++ b/test/Foo.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.23 <0.9.0; + +import { PRBTest } from "@prb/test/src/PRBTest.sol"; +import { console2 } from "forge-std/src/console2.sol"; +import { StdCheats } from "forge-std/src/StdCheats.sol"; + +import { Foo } from "../src/Foo.sol"; + +interface IERC20 { + function balanceOf(address account) external view returns (uint256); +} + +/// @dev If this is your first time with Forge, read this tutorial in the Foundry Book: +/// https://book.getfoundry.sh/forge/writing-tests +contract FooTest is PRBTest, StdCheats { + Foo internal foo; + + /// @dev A function invoked before each test case is run. + function setUp() public virtual { + // Instantiate the contract-under-test. + foo = new Foo(); + } + + /// @dev Basic test. Run it with `forge test -vvv` to see the console log. + function test_Example() external { + console2.log("Hello World"); + uint256 x = 42; + assertEq(foo.id(x), x, "value mismatch"); + } + + /// @dev Fuzz test that provides random values for an unsigned integer, but which rejects zero as an input. + /// If you need more sophisticated input validation, you should use the `bound` utility instead. + /// See https://twitter.com/PaulRBerg/status/1622558791685242880 + function testFuzz_Example(uint256 x) external { + vm.assume(x != 0); // or x = bound(x, 1, 100) + assertEq(foo.id(x), x, "value mismatch"); + } + + /// @dev Fork test that runs against an Ethereum Mainnet fork. For this to work, you need to set `API_KEY_ALCHEMY` + /// in your environment You can get an API key for free at https://alchemy.com. + function testFork_Example() external { + // Silently pass this test if there is no API key. + string memory alchemyApiKey = vm.envOr("API_KEY_ALCHEMY", string("")); + if (bytes(alchemyApiKey).length == 0) { + return; + } + + // Otherwise, run the test against the mainnet fork. + vm.createSelectFork({ urlOrAlias: "mainnet", blockNumber: 16_428_000 }); + address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address holder = 0x7713974908Be4BEd47172370115e8b1219F4A5f0; + uint256 actualBalance = IERC20(usdc).balanceOf(holder); + uint256 expectedBalance = 196_307_713.810457e6; + assertEq(actualBalance, expectedBalance); + } +}