From 9127bf4751d57b92ad9267596854336c6cb91592 Mon Sep 17 00:00:00 2001 From: gretzke Date: Mon, 23 Oct 2023 22:58:50 +0200 Subject: [PATCH 01/86] chore: forge init --- .github/workflows/test.yml | 34 +++++++++++++++++++ .gitignore | 14 ++++++++ README.md | 68 ++++++++++++++++++++++++++++++++++++-- foundry.toml | 6 ++++ script/Counter.s.sol | 12 +++++++ src/Counter.sol | 14 ++++++++ test/Counter.t.sol | 24 ++++++++++++++ 7 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 foundry.toml create mode 100644 script/Counter.s.sol create mode 100644 src/Counter.sol create mode 100644 test/Counter.t.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..09880b1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: test + +on: workflow_dispatch + +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..85198aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/README.md b/README.md index 497407f..9265b45 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,66 @@ -# foundry-template -Contracts team, template repo +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..25b918f --- /dev/null +++ b/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/script/Counter.s.sol b/script/Counter.s.sol new file mode 100644 index 0000000..1a47b40 --- /dev/null +++ b/script/Counter.s.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console2} from "forge-std/Script.sol"; + +contract CounterScript is Script { + function setUp() public {} + + function run() public { + vm.broadcast(); + } +} diff --git a/src/Counter.sol b/src/Counter.sol new file mode 100644 index 0000000..aded799 --- /dev/null +++ b/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol new file mode 100644 index 0000000..e9b9e6a --- /dev/null +++ b/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} From 31acb277f2f7dee5f59a2a2c47aa07b521bba301 Mon Sep 17 00:00:00 2001 From: gretzke Date: Mon, 23 Oct 2023 22:58:51 +0200 Subject: [PATCH 02/86] forge install: forge-std v1.7.1 --- .gitmodules | 3 +++ lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 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/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..f73c73d --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 From c7d88c5ccfcfd0254fb3463a03064eb613167cbb Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 01:37:35 +0200 Subject: [PATCH 03/86] Update foundry.toml --- foundry.toml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/foundry.toml b/foundry.toml index 25b918f..edcf7a8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,6 +1,18 @@ [profile.default] -src = "src" -out = "out" -libs = ["lib"] +src = 'src' +out = 'out' +libs = ['lib'] +optimizer = true +optimize_runs = 999999 +via_ir = true +solc = '0.8.21' +ffi = true +fs_permissions = [ + { access = "read", path = "scripts/config.json" }, +] -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +[profile.intense.fuzz] +runs = 10000 +max_test_rejects = 999999 + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file From db657893aca67d9972033c6a17ffa46e95ccceae Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:01:30 +0200 Subject: [PATCH 04/86] forge install: openzeppelin-contracts v5.0.0 --- .gitmodules | 3 +++ lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index 888d42d..e80ffd8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/openzeppelin/openzeppelin-contracts 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 From af3db4f80d2fc24c66e1e01f2e771111b9e1ba38 Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:03:12 +0200 Subject: [PATCH 05/86] forge install: openzeppelin-contracts-upgradeable v5.0.0 --- .gitmodules | 3 +++ lib/openzeppelin-contracts-upgradeable | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index e80ffd8..c90cdcb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable diff --git a/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 From fa788fd6d3466661f130f693bc1045e969b3f735 Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:04:22 +0200 Subject: [PATCH 06/86] Add remappings --- foundry.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/foundry.toml b/foundry.toml index edcf7a8..57a5170 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,6 +10,10 @@ ffi = true fs_permissions = [ { access = "read", path = "scripts/config.json" }, ] +remappings = [ + "@openzeppelin/contracts=lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts" +] [profile.intense.fuzz] runs = 10000 From b2445eb3f145ee25285871a79fdd383a1ce879ff Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:05:23 +0200 Subject: [PATCH 07/86] Create CODEOWNERS --- .github/CODEOWNERS | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/CODEOWNERS 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 From 1141ec937d74198826ce3fd94ebc642c54ed4e32 Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:13:28 +0200 Subject: [PATCH 08/86] Update .gitignore --- .gitignore | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 85198aa..4964cb1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,11 @@ -# Compiler files -cache/ -out/ - -# Ignores development broadcast logs -!/broadcast -/broadcast/*/31337/ -/broadcast/**/dry-run/ - -# Docs -docs/ - -# Dotenv file +/target +/out +/cache +/coverage +lcov.info +.DS_Store .env +.vscode + +broadcast/*/31337 +deployments/31337.* \ No newline at end of file From c936b8333853e3a57046614e6405f18943649af2 Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:13:51 +0200 Subject: [PATCH 09/86] Create .nvmrc --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..3c79f30 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18.16.0 \ No newline at end of file From 8d9e3472b0d43c48234d47c8aac0d4c0ebc3637b Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:16:00 +0200 Subject: [PATCH 10/86] Create .prettierrc --- .prettierrc | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0c4d091 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,25 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": true, + "overrides": [ + { + "files": "*.sol", + "options": { + "printWidth": 120, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": false + } + }, + { + "files": "*.json", + "options": { + "tabWidth": 4 + } + } + ] +} From b3fb07bf22e062a99256f7c02ca8af5822d9e45d Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:16:04 +0200 Subject: [PATCH 11/86] Create .prettierignore --- .prettierignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e89ded0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +.prettierrc +foundry.toml +out +lib/ +cache/ +docs/ \ No newline at end of file From b4d6c1cefad394277b91b1bba1823edd9ffb1442 Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:16:07 +0200 Subject: [PATCH 12/86] Create .env.example --- .env.example | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2f991ec --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +RPC_URL= +PRIVATE_KEY= +ETHERSCAN_API_KEY= \ No newline at end of file From 7d9fd4357adae2b73f15be3162e727a0fcbdaaa1 Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:16:30 +0200 Subject: [PATCH 13/86] Create SECURITY.md --- SECURITY.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 SECURITY.md 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 From 80c8e794f8fc902760ae9ac1a0f5d51958092d50 Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:16:57 +0200 Subject: [PATCH 14/86] Create slither.config.json --- slither.config.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 slither.config.json 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/)" +} From 4a3239639e6a09ac9034d89ebd104791420cae1c Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:21:23 +0200 Subject: [PATCH 15/86] Add MIT License --- LICENSE.md | 9 +++++++++ README.md | 4 ++++ src/Counter.sol | 2 +- test/Counter.t.sol | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..95817e3 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2023 PT Services DMCC + +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 9265b45..1aea96b 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,7 @@ $ forge --help $ anvil --help $ cast --help ``` + +--- + +Copyright (C) 2023 PT Services DMCC diff --git a/src/Counter.sol b/src/Counter.sol index aded799..274bb39 100644 --- a/src/Counter.sol +++ b/src/Counter.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract Counter { diff --git a/test/Counter.t.sol b/test/Counter.t.sol index e9b9e6a..82a78c1 100644 --- a/test/Counter.t.sol +++ b/test/Counter.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {Test, console2} from "forge-std/Test.sol"; From 9aa8163d730db8ddcf4eadd7e7e6e1848a25e3ee Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:25:06 +0200 Subject: [PATCH 16/86] Implement best practices --- src/Counter.sol | 13 +++++++++++-- src/interface/ICounter.sol | 16 ++++++++++++++++ src/interface/IVersioned.sol | 7 +++++++ test/Counter.t.sol | 2 +- 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/interface/ICounter.sol create mode 100644 src/interface/IVersioned.sol diff --git a/src/Counter.sol b/src/Counter.sol index 274bb39..9dbe4ce 100644 --- a/src/Counter.sol +++ b/src/Counter.sol @@ -1,14 +1,23 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity 0.8.21; -contract Counter { +import {ICounter, IVersioned} from "./interface/ICounter.sol"; + +contract Counter is ICounter { uint256 public number; + /// @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..4b7acf7 --- /dev/null +++ b/src/interface/ICounter.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {IVersioned} from "./IVersioned.sol"; + +interface ICounter is IVersioned { + /// @return the current number + function number() external view returns (uint256); + + /// @notice set the number + /// @param newNumber the new number + function setNumber(uint256 newNumber) external; + + /// @notice increment 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..98b238c --- /dev/null +++ b/src/interface/IVersioned.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +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 index 82a78c1..633f8b7 100644 --- a/test/Counter.t.sol +++ b/test/Counter.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity 0.8.21; import {Test, console2} from "forge-std/Test.sol"; import {Counter} from "../src/Counter.sol"; From 8733bac54bd9be835d614ad4c7c76632d78ceaa0 Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 24 Oct 2023 02:49:23 +0200 Subject: [PATCH 17/86] Add deployment scripts --- .gitignore | 2 +- foundry.toml | 2 + script/1.0.0/Deploy.s.sol | 23 ++ script/1.0.0/input.json | 11 + script/config.json | 7 + script/util/extract.js | 502 ++++++++++++++++++++++++++++++++++++++ src/Counter.sol | 4 + test/Counter.t.sol | 2 +- 8 files changed, 551 insertions(+), 2 deletions(-) create mode 100644 script/1.0.0/Deploy.s.sol create mode 100644 script/1.0.0/input.json create mode 100644 script/config.json create mode 100644 script/util/extract.js diff --git a/.gitignore b/.gitignore index 4964cb1..c2a7344 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ lcov.info .vscode broadcast/*/31337 -deployments/31337.* \ No newline at end of file +deployments/**/31337.* \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 57a5170..4a2ca8e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,7 +9,9 @@ solc = '0.8.21' ffi = true fs_permissions = [ { access = "read", path = "scripts/config.json" }, + { access = "read", path = "script/1.0.0/input.json" } ] + remappings = [ "@openzeppelin/contracts=lib/openzeppelin-contracts/contracts", "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts" diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol new file mode 100644 index 0000000..629c48b --- /dev/null +++ b/script/1.0.0/Deploy.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {Script, stdJson, console2 as console} from "forge-std/Script.sol"; + +import {Counter} from "../../src/Counter.sol"; + +contract Deploy is Script { + using stdJson for string; + + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + string memory input = vm.readFile("script/1.0.0/input.json"); + string memory chainIdSlug = string(abi.encodePacked('["', vm.toString(block.chainid), '"]')); + uint256 num = input.readUint(string.concat(chainIdSlug, ".num")); + + vm.startBroadcast(deployerPrivateKey); + + new Counter(num); + + vm.stopBroadcast(); + } +} diff --git a/script/1.0.0/input.json b/script/1.0.0/input.json new file mode 100644 index 0000000..40a551a --- /dev/null +++ b/script/1.0.0/input.json @@ -0,0 +1,11 @@ +{ + "1": { + "num": 1 + }, + "5": { + "num": 2 + }, + "31337": { + "num": 3 + } +} diff --git a/script/config.json b/script/config.json new file mode 100644 index 0000000..77253a4 --- /dev/null +++ b/script/config.json @@ -0,0 +1,7 @@ +{ + "defaultRpc": { + "31337": "http://127.0.0.1:8545", + "1": "https://eth.llamarpc.com", + "5": "https://ethereum-goerli.publicnode.com" + } +} diff --git a/script/util/extract.js b/script/util/extract.js new file mode 100644 index 0000000..cf800fb --- /dev/null +++ b/script/util/extract.js @@ -0,0 +1,502 @@ +const { readFileSync, existsSync, writeFileSync, mkdirSync } = require("fs"); +const { execSync } = require("child_process"); +const { join } = require("path"); + +/** + * @description Extracts contract deployment data from run-latest.json (foundry broadcast output) and writes to deployments/{chainId}.json + * @usage node script/utils/extract.js {chainId} [version = "1.0.0"] [scriptName = "Deploy.s.sol"] + * @dev + * currently only supports TransparentUpgradeableProxy pattern + */ +async function main() { + validateInputs(); + let [chainId, version, scriptName] = process.argv.slice(2); + if (!version?.length) version = "1.0.0"; + if (!scriptName?.length) scriptName = "Deploy.s.sol"; + const commitHash = getCommitHash(); + const data = JSON.parse( + readFileSync(join(__dirname, `../../broadcast/${scriptName}/${chainId}/run-latest.json`), "utf-8") + ); + const config = JSON.parse(readFileSync(join(__dirname, "../config.json"), "utf-8")); + const input = JSON.parse(readFileSync(join(__dirname, `../${version}/input.json`), "utf-8")); + const rpcUrl = config.defaultRpc[chainId] || process.env.RPC_URL || "http://127.0.0.1:8545"; + const deployments = data.transactions.filter(({ transactionType }) => transactionType === "CREATE"); + + const outPath = join(__dirname, `../../deployments/json/${chainId}.json`); + if (!existsSync(join(__dirname, "../../deployments/"))) mkdirSync(join(__dirname, "../../deployments/")); + if (!existsSync(join(__dirname, "../../deployments/json/"))) mkdirSync(join(__dirname, "../../deployments/json/")); + const out = JSON.parse( + (existsSync(outPath) && readFileSync(outPath, "utf-8")) || JSON.stringify({ chainId, latest: {}, history: [] }) + ); + + const timestamp = data.timestamp; + let latestContracts = {}; + if (Object.keys(out.latest).length === 0) { + const deployedContractsMap = new Map( + [...deployments].map(({ contractAddress, contractName }) => [contractAddress, contractName]) + ); + + // first deployment + // todo(future): add support for other proxy patterns + const proxies = await Promise.all( + deployments + .filter(({ contractName }) => contractName === "TransparentUpgradeableProxy") + .map(async ({ arguments, contractAddress, hash }) => ({ + implementation: arguments[0], + proxyAdmin: arguments[1], + address: contractAddress, + contractName: deployedContractsMap.get(arguments[0]), + proxy: true, + ...(await getVersion(contractAddress, rpcUrl)), + proxyType: "TransparentUpgradeableProxy", + timestamp, + deploymentTxn: hash, + commitHash, + })) + ); + const nonProxies = await Promise.all( + deployments + .filter( + ({ contractName }) => + contractName !== "TransparentUpgradeableProxy" && !proxies.find((p) => p.contractName === contractName) + ) + .map(async ({ contractName, contractAddress, hash }) => ({ + address: contractAddress, + contractName, + proxy: false, + ...(await getVersion(contractAddress, rpcUrl)), + timestamp, + deploymentTxn: hash, + commitHash, + })) + ); + const contracts = [...proxies, ...nonProxies].reduce((obj, { contractName, ...rest }) => { + obj[contractName] = rest; + return obj; + }, {}); + latestContracts = contracts; + out.history.push({ + contracts: Object.entries(contracts).reduce((obj, [key, { timestamp, commitHash, ...rest }]) => { + obj[key] = rest; + return obj; + }, {}), + input: input[chainId], + timestamp, + commitHash, + }); + } else { + if (out.history.find((h) => h.commitHash === commitHash)) return console.log("warn: commitHash already deployed"); // if commitHash already exists in history, return + + for (const { contractName, contractAddress } of deployments) { + if (Object.keys(out.latest).includes(contractName) && out.latest[contractName].proxy) { + // new deployment, check if implementation changed on chain + if (out.latest[contractName].proxyType !== "TransparentUpgradeableProxy") continue; // only support TransparentUpgradeableProxy pattern + const currentImplementation = getImplementationAddress( + out.latest[contractName].address, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + rpcUrl + ); + if (currentImplementation === out.latest[contractName].implementation) + throw new Error( + `Implementation for ${contractName}(${out.latest[contractName].address}) did not change - ${currentImplementation}, deployed - ${contractAddress}` + ); + if (currentImplementation !== contractAddress) + throw new Error( + `Implementation mismatch for ${contractName}(${out.latest[contractName].address}), onchain - ${currentImplementation}, deployed - ${contractAddress}` + ); + + // currentImplementation === contractAddress + // implementation changed, update latestContracts + latestContracts[contractName] = { + ...out.latest[contractName], + implementation: toChecksumAddress(currentImplementation), + version: (await getVersion(currentImplementation, rpcUrl))?.version || version, + timestamp, + commitHash, + }; + out.history.unshift({ + contracts: Object.entries(latestContracts).reduce((obj, [key, { timestamp, commitHash, ...rest }]) => { + obj[key] = rest; + return obj; + }, {}), + input: input[chainId], + timestamp, + commitHash, + }); + } + } + + const deployedContractsMap = new Map( + Object.entries(out.latest).map(([contractName, { address }]) => [address.toLowerCase(), contractName]) + ); + + for (const { transaction, transactionType } of data.transactions) { + if ( + transactionType === "CALL" && + deployedContractsMap.get(transaction.to.toLowerCase()) === "ProxyAdmin" && + transaction.data.startsWith("0x99a88ec4") // upgrade(address, address) + ) { + const proxyAddress = "0x" + transaction.data.slice(34, 74); + const newImplementationAddress = "0x" + transaction.data.slice(98, 138); + const contractName = deployedContractsMap.get(proxyAddress.toLowerCase()); + + latestContracts[contractName] = { + ...out.latest[contractName], + implementation: toChecksumAddress(newImplementationAddress), + version: (await getVersion(newImplementationAddress, rpcUrl))?.version || version, + timestamp, + commitHash, + }; + out.history.unshift({ + contracts: Object.entries(latestContracts).reduce((obj, [key, { timestamp, commitHash, ...rest }]) => { + obj[key] = rest; + return obj; + }, {}), + input: input[chainId], + timestamp, + commitHash, + }); + } + } + } + + // overwrite latest with changed contracts + out.latest = { + ...out.latest, + ...latestContracts, + }; + + writeFileSync(outPath, JSON.stringify(out, null, 2)); + generateMarkdown(out); +} + +function getCommitHash() { + return execSync("git rev-parse HEAD").toString().trim(); // note: update if not using git +} + +function toChecksumAddress(address) { + try { + return execSync(`cast to-check-sum-address ${address}`).toString().trim(); // note: update if not using cast + } catch (e) { + console.log("ERROR", e); + return address; + } +} + +function getImplementationAddress(proxyAddress, implementationSlot, rpcUrl) { + try { + const implementationAddress = execSync(`cast storage ${proxyAddress} ${implementationSlot} --rpc-url ${rpcUrl}`) + .toString() + .trim(); // note: update if not using cast + if (implementationAddress === "0x0000000000000000000000000000000000000000000000000000000000000000") + throw new Error(`empty implementation address for ${proxyAddress} at slot ${implementationSlot}`); + const trimmedAddress = "0x" + implementationAddress.substring(66 - 40, 66); + return toChecksumAddress(trimmedAddress); + } catch (e) { + console.log("ERROR", e); + return "0x0000000000000000000000000000000000000000"; + } +} + +async function getVersion(contractAddress, rpcUrl) { + try { + const res = await ( + await fetch(rpcUrl, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: Date.now(), + method: "eth_call", + params: [{ to: contractAddress, data: "0x54fd4d50" }, "latest"], // version()(string) + }), + }) + ).json(); + if (res.error) throw new Error(res.error.message); + return { version: hexToAscii(res.result)?.trim() || res.result }; + } catch (e) { + if (e.message === "execution reverted") return { version: undefined }; // contract does not implement getVersion() + if (e.message.includes("fetch is not defined")) { + console.warn("use node 18+"); + } + throw e; + } +} + +function generateMarkdown(input) { + let out = `# Polygon Ecosystem Token\n\n`; + // read name from foundry.toml + + out += `\n### Table of Contents\n- [Summary](#summary)\n- [Contracts](#contracts)\n\t- `; + out += Object.keys(input.latest) + .map( + (c) => + `[${c.replace(/([A-Z])/g, " $1").trim()}](#${c + .replace(/([A-Z])/g, "-$1") + .trim() + .slice(1) + .toLowerCase()})` + ) + .join("\n\t- "); + out += `\n- [Deployment History](#deployment-history)`; + const { deploymentHistoryMd, allVersions } = generateDeploymentHistory(input.history, input.latest, input.chainId); + out += Object.keys(allVersions) + .map((v) => `\n\t- [${v}](#${v.replace(/\./g, "")})`) + .join(""); + + out += `\n\n## Summary + + + + + + `; + out += Object.entries(input.latest) + .map( + ([contractName, { address, version }]) => + ` + + + + ` + ) + .join("\n"); + out += `
ContractAddressVersion
${contractName}${address}${version || `N/A`}
\n`; + + out += `\n## Contracts\n\n`; + + out += Object.entries(input.latest) + .map( + ([ + contractName, + { address, deploymentTxn, version, commitHash, timestamp, proxyType, implementation, proxyAdmin }, + ]) => `### ${contractName.replace(/([A-Z])/g, " $1").trim()} + +Address: ${getEtherscanLinkMd(input.chainId, address)} + +Deployment Txn: ${getEtherscanLinkMd(input.chainId, deploymentTxn, "tx")} + +${ + typeof version === "undefined" + ? "" + : `Version: [${version}](https://github.com/0xPolygon/pol-token/releases/tag/${version})` +} + +Commit Hash: [${commitHash.slice(0, 7)}](https://github.com/0xPolygon/pol-token/commit/${commitHash}) + +${prettifyTimestamp(timestamp)} +${generateProxyInformationIfProxy({ + address, + contractName, + proxyType, + implementation, + proxyAdmin, + history: input.history, + chainId: input.chainId, +})}` + ) + .join("\n\n --- \n\n"); + + out += ` + +---- + + +### Deployment History + +${deploymentHistoryMd}`; + + writeFileSync(join(__dirname, `../../deployments/${input.chainId}.md`), out, "utf-8"); +} + +function getEtherscanLink(chainId, address, slug = "address") { + chainId = parseInt(chainId); + switch (chainId) { + case 1: + return `https://etherscan.io/${slug}/${address}`; + case 5: + return `https://goerli.etherscan.io/${slug}/${address}`; + default: + return ``; + // return `https://blockscan.com/${slug}/${address}`; + } +} +function getEtherscanLinkMd(chainId, address, slug = "address") { + const etherscanLink = getEtherscanLink(chainId, address, slug); + return etherscanLink.length ? `[${address}](${etherscanLink})` : address; +} + +function generateProxyInformationIfProxy({ + address, + contractName, + proxyType, + implementation, + proxyAdmin, + history, + chainId, +}) { + let out = ``; + if (typeof proxyType === "undefined") return out; + out += `\n\n_Proxy Information_\n\n`; + out += `\n\nProxy Type: ${proxyType}\n\n`; + out += `\n\nImplementation: ${getEtherscanLinkMd(chainId, implementation)}\n\n`; + out += `\n\nProxy Admin: ${getEtherscanLinkMd(chainId, proxyAdmin)}\n\n`; + + const historyOfProxy = history.filter((h) => h?.contracts[contractName]?.address === address); + if (historyOfProxy.length === 0) return out; + out += `\n`; + out += ` +
+Implementation History + + + + + + ${historyOfProxy + .map( + ({ + contracts: { + [contractName]: { implementation, version }, + }, + commitHash, + }) => ` + + + + + ` + ) + .join("")} +
VersionAddressCommit Hash
${version}${implementation}${commitHash.slice( + 0, + 7 + )}
+
+ `; + return out; +} + +function generateDeploymentHistory(history, latest, chainId) { + let allVersions = {}; + if (history.length === 0) { + const inputPath = join(__dirname, "../1.0.0/input.json"); + const input = JSON.parse((existsSync(inputPath) && readFileSync(inputPath, "utf-8")) || `{"${chainId}":{}}`)[ + chainId + ]; + allVersions = Object.entries(latest).reduce((obj, [contractName, contract]) => { + if (typeof contract.version === "undefined") return obj; + if (!obj[contract.version]) obj[contract.version] = []; + obj[contract.version].push({ contract, contractName, input }); + return obj; + }, {}); + } else { + allVersions = history.reduce((obj, { contracts, input, timestamp, commitHash }) => { + Object.entries(contracts).forEach(([contractName, contract]) => { + if (typeof contract.version === "undefined") return; + if (!obj[contract.version]) obj[contract.version] = []; + obj[contract.version].push({ contract: { ...contract, timestamp, commitHash }, contractName, input }); + }); + return obj; + }, {}); + } + + let out = ``; + out += Object.entries(allVersions) + .map( + ([version, contractInfos]) => ` +### [${version}](https://github.com/0xPolygon/pol-token/releases/tag/${version}) + +${prettifyTimestamp(contractInfos[0].contract.timestamp)} + +Commit Hash: [${contractInfos[0].contract.commitHash.slice(0, 7)}](https://github.com/0xPolygon/pol-token/commit/${ + contractInfos[0].contract.commitHash + }) + +Deployed contracts: + +- ${contractInfos + .map( + ({ contract, contractName }) => + `[${contractName.replace(/([A-Z])/g, " $1").trim()}](${ + getEtherscanLink(chainId, contract.address) || contract.address + })${ + contract.proxyType + ? ` ([Implementation](${ + getEtherscanLink(chainId, contract.implementation) || contract.implementation + }))` + : `` + }` + ) + .join("\n- ")} + +
+Inputs + + + + + + ${Object.entries(contractInfos[0].input) + .map( + ([key, value]) => ` + + + +` + ) + .join("\n")} +
ParameterValue
${key}${value}
+
+ ` + ) + .join("\n\n"); + + return { deploymentHistoryMd: out, allVersions }; +} + +function prettifyTimestamp(timestamp) { + return new Date(timestamp * 1000).toUTCString().replace("GMT", "UTC"); +} + +const hexToAscii = (str) => hexToUtf8(str).replace(/[\u0000-\u0008,\u000A-\u001F,\u007F-\u00A0]+/g, ""); // remove non-ascii chars +const hexToUtf8 = (str) => new TextDecoder().decode(hexToUint8Array(str)); // note: TextDecoder present in node, update if not using nodejs +function hexToUint8Array(hex) { + const value = hex.toLowerCase().startsWith("0x") ? hex.slice(2) : hex; + return new Uint8Array(Math.ceil(value.length / 2)).map((_, i) => parseInt(value.substring(i * 2, i * 2 + 2), 16)); +} + +function validateInputs() { + let [chainId, version, scriptName] = process.argv.slice(2); + let printUsageAndExit = false; + if ( + !( + typeof chainId === "string" && + ["string", "undefined"].includes(typeof version) && + ["string", "undefined"].includes(typeof scriptName) + ) || + chainId === "help" + ) { + if (chainId !== "help") + console.log(`error: invalid inputs: ${JSON.stringify({ chainId, version, scriptName }, null, 0)}\n`); + printUsageAndExit = true; + } + if ( + version && + !( + existsSync(join(__dirname, `../${version}/input.json`)) && + existsSync(join(__dirname, `../${version}/${scriptName}`)) + ) + ) { + console.log( + `error: script/${version}/input.json or script/${version}/${scriptName || ""} does not exist\n` + ); + printUsageAndExit = true; + } + if (printUsageAndExit) { + console.log(`usage: node script/utils/extract.js {chainId} [version = "1.0.0"] [scriptName = "Deploy.s.sol"]`); + process.exit(1); + } +} + +main(); diff --git a/src/Counter.sol b/src/Counter.sol index 9dbe4ce..a691c7a 100644 --- a/src/Counter.sol +++ b/src/Counter.sol @@ -6,6 +6,10 @@ import {ICounter, IVersioned} from "./interface/ICounter.sol"; contract Counter is ICounter { uint256 public number; + constructor(uint256 initialNumber) { + number = initialNumber; + } + /// @inheritdoc ICounter function setNumber(uint256 newNumber) public { number = newNumber; diff --git a/test/Counter.t.sol b/test/Counter.t.sol index 633f8b7..4e17e56 100644 --- a/test/Counter.t.sol +++ b/test/Counter.t.sol @@ -8,7 +8,7 @@ contract CounterTest is Test { Counter public counter; function setUp() public { - counter = new Counter(); + counter = new Counter(1); counter.setNumber(0); } From 97cb8755d1a8e651d8f253298cc6030d208a475c Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 3 Nov 2023 05:15:31 +0100 Subject: [PATCH 18/86] add pre commit hooks for documentation and formatting --- .github/workflows/pre-commit.yaml | 21 ++++++ .github/workflows/{test.yml => test.yaml} | 0 .pre-commit-config.yaml | 29 ++++++++ .prettierignore | 4 +- CONTRIBUTING.md | 8 ++ README.md | 12 ++- docs/html/.gitignore | 1 + docs/html/book.css | 13 ++++ docs/html/book.toml | 12 +++ docs/html/solidity.min.js | 74 +++++++++++++++++++ docs/html/src/README.md | 74 +++++++++++++++++++ docs/html/src/SUMMARY.md | 7 ++ .../src/src/Counter.sol/contract.Counter.md | 60 +++++++++++++++ docs/html/src/src/README.md | 5 ++ .../ICounter.sol/interface.ICounter.md | 45 +++++++++++ .../IVersioned.sol/interface.IVersioned.md | 18 +++++ docs/html/src/src/interface/README.md | 5 ++ script/util/extract.js | 38 +++++----- 18 files changed, 401 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/pre-commit.yaml rename .github/workflows/{test.yml => test.yaml} (100%) create mode 100644 .pre-commit-config.yaml create mode 100644 CONTRIBUTING.md create mode 100644 docs/html/.gitignore create mode 100644 docs/html/book.css create mode 100644 docs/html/book.toml create mode 100644 docs/html/solidity.min.js create mode 100644 docs/html/src/README.md create mode 100644 docs/html/src/SUMMARY.md create mode 100644 docs/html/src/src/Counter.sol/contract.Counter.md create mode 100644 docs/html/src/src/README.md create mode 100644 docs/html/src/src/interface/ICounter.sol/interface.ICounter.md create mode 100644 docs/html/src/src/interface/IVersioned.sol/interface.IVersioned.md create mode 100644 docs/html/src/src/interface/README.md diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..1ded477 --- /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: [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.yml b/.github/workflows/test.yaml similarity index 100% rename from .github/workflows/test.yml rename to .github/workflows/test.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..43394ee --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +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/html" + - 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 + entry: forge doc -b -o docs/html + 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/html" diff --git a/.prettierignore b/.prettierignore index e89ded0..e92cb50 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,6 @@ -.prettierrc foundry.toml out lib/ cache/ -docs/ \ No newline at end of file +docs/autogenerated +*.sol diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..84f0914 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,8 @@ +# Contributing + +## Install + +To ensure consistency in our formatting we use `pre-commit` to check whether code was formatted properly and the documentation is up to date. On pull requests the CI checks whether all pre-commit hooks were run correctly. + +1. Install [pre-commit](https://pre-commit.com/#post-commit) +2. Run `pre-commit install` diff --git a/README.md b/README.md index 1aea96b..b14ab12 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ ## 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) + **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** Foundry consists of: -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. ## Documentation diff --git a/docs/html/.gitignore b/docs/html/.gitignore new file mode 100644 index 0000000..4e42a1b --- /dev/null +++ b/docs/html/.gitignore @@ -0,0 +1 @@ +book/ \ No newline at end of file diff --git a/docs/html/book.css b/docs/html/book.css new file mode 100644 index 0000000..b5ce903 --- /dev/null +++ b/docs/html/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/html/book.toml b/docs/html/book.toml new file mode 100644 index 0000000..05beb66 --- /dev/null +++ b/docs/html/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/html/solidity.min.js b/docs/html/solidity.min.js new file mode 100644 index 0000000..1924932 --- /dev/null +++ b/docs/html/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()?"(? --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` + +--- + +Copyright (C) 2023 PT Services DMCC diff --git a/docs/html/src/SUMMARY.md b/docs/html/src/SUMMARY.md new file mode 100644 index 0000000..1c37b0f --- /dev/null +++ b/docs/html/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/html/src/src/Counter.sol/contract.Counter.md b/docs/html/src/src/Counter.sol/contract.Counter.md new file mode 100644 index 0000000..48b3a10 --- /dev/null +++ b/docs/html/src/src/Counter.sol/contract.Counter.md @@ -0,0 +1,60 @@ +# Counter +[Git Source](https://github.com/0xPolygon/foundry-template/blob/8733bac54bd9be835d614ad4c7c76632d78ceaa0/src/Counter.sol) + +**Inherits:** +[ICounter](/docs/html/src/src/interface/ICounter.sol/interface.ICounter.md) + + +## State Variables +### number + +```solidity +uint256 public number; +``` + + +## Functions +### constructor + + +```solidity +constructor(uint256 initialNumber); +``` + +### setNumber + +set the number + + +```solidity +function setNumber(uint256 newNumber) public; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newNumber`|`uint256`|the new number| + + +### increment + +increment 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/html/src/src/README.md b/docs/html/src/src/README.md new file mode 100644 index 0000000..1b1cc23 --- /dev/null +++ b/docs/html/src/src/README.md @@ -0,0 +1,5 @@ + + +# Contents +- [interface](/src/interface) +- [Counter](Counter.sol/contract.Counter.md) diff --git a/docs/html/src/src/interface/ICounter.sol/interface.ICounter.md b/docs/html/src/src/interface/ICounter.sol/interface.ICounter.md new file mode 100644 index 0000000..cc824ca --- /dev/null +++ b/docs/html/src/src/interface/ICounter.sol/interface.ICounter.md @@ -0,0 +1,45 @@ +# ICounter +[Git Source](https://github.com/0xPolygon/foundry-template/blob/8733bac54bd9be835d614ad4c7c76632d78ceaa0/src/interface/ICounter.sol) + +**Inherits:** +[IVersioned](/docs/html/src/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 + +set the number + + +```solidity +function setNumber(uint256 newNumber) external; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newNumber`|`uint256`|the new number| + + +### increment + +increment the number by 1 + + +```solidity +function increment() external; +``` + diff --git a/docs/html/src/src/interface/IVersioned.sol/interface.IVersioned.md b/docs/html/src/src/interface/IVersioned.sol/interface.IVersioned.md new file mode 100644 index 0000000..8cd147b --- /dev/null +++ b/docs/html/src/src/interface/IVersioned.sol/interface.IVersioned.md @@ -0,0 +1,18 @@ +# IVersioned +[Git Source](https://github.com/0xPolygon/foundry-template/blob/8733bac54bd9be835d614ad4c7c76632d78ceaa0/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/html/src/src/interface/README.md b/docs/html/src/src/interface/README.md new file mode 100644 index 0000000..b060352 --- /dev/null +++ b/docs/html/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/script/util/extract.js b/script/util/extract.js index cf800fb..f1f8ffa 100644 --- a/script/util/extract.js +++ b/script/util/extract.js @@ -15,7 +15,7 @@ async function main() { if (!scriptName?.length) scriptName = "Deploy.s.sol"; const commitHash = getCommitHash(); const data = JSON.parse( - readFileSync(join(__dirname, `../../broadcast/${scriptName}/${chainId}/run-latest.json`), "utf-8") + readFileSync(join(__dirname, `../../broadcast/${scriptName}/${chainId}/run-latest.json`), "utf-8"), ); const config = JSON.parse(readFileSync(join(__dirname, "../config.json"), "utf-8")); const input = JSON.parse(readFileSync(join(__dirname, `../${version}/input.json`), "utf-8")); @@ -26,14 +26,14 @@ async function main() { if (!existsSync(join(__dirname, "../../deployments/"))) mkdirSync(join(__dirname, "../../deployments/")); if (!existsSync(join(__dirname, "../../deployments/json/"))) mkdirSync(join(__dirname, "../../deployments/json/")); const out = JSON.parse( - (existsSync(outPath) && readFileSync(outPath, "utf-8")) || JSON.stringify({ chainId, latest: {}, history: [] }) + (existsSync(outPath) && readFileSync(outPath, "utf-8")) || JSON.stringify({ chainId, latest: {}, history: [] }), ); const timestamp = data.timestamp; let latestContracts = {}; if (Object.keys(out.latest).length === 0) { const deployedContractsMap = new Map( - [...deployments].map(({ contractAddress, contractName }) => [contractAddress, contractName]) + [...deployments].map(({ contractAddress, contractName }) => [contractAddress, contractName]), ); // first deployment @@ -52,13 +52,13 @@ async function main() { timestamp, deploymentTxn: hash, commitHash, - })) + })), ); const nonProxies = await Promise.all( deployments .filter( ({ contractName }) => - contractName !== "TransparentUpgradeableProxy" && !proxies.find((p) => p.contractName === contractName) + contractName !== "TransparentUpgradeableProxy" && !proxies.find((p) => p.contractName === contractName), ) .map(async ({ contractName, contractAddress, hash }) => ({ address: contractAddress, @@ -68,7 +68,7 @@ async function main() { timestamp, deploymentTxn: hash, commitHash, - })) + })), ); const contracts = [...proxies, ...nonProxies].reduce((obj, { contractName, ...rest }) => { obj[contractName] = rest; @@ -94,15 +94,15 @@ async function main() { const currentImplementation = getImplementationAddress( out.latest[contractName].address, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - rpcUrl + rpcUrl, ); if (currentImplementation === out.latest[contractName].implementation) throw new Error( - `Implementation for ${contractName}(${out.latest[contractName].address}) did not change - ${currentImplementation}, deployed - ${contractAddress}` + `Implementation for ${contractName}(${out.latest[contractName].address}) did not change - ${currentImplementation}, deployed - ${contractAddress}`, ); if (currentImplementation !== contractAddress) throw new Error( - `Implementation mismatch for ${contractName}(${out.latest[contractName].address}), onchain - ${currentImplementation}, deployed - ${contractAddress}` + `Implementation mismatch for ${contractName}(${out.latest[contractName].address}), onchain - ${currentImplementation}, deployed - ${contractAddress}`, ); // currentImplementation === contractAddress @@ -127,7 +127,7 @@ async function main() { } const deployedContractsMap = new Map( - Object.entries(out.latest).map(([contractName, { address }]) => [address.toLowerCase(), contractName]) + Object.entries(out.latest).map(([contractName, { address }]) => [address.toLowerCase(), contractName]), ); for (const { transaction, transactionType } of data.transactions) { @@ -235,7 +235,7 @@ function generateMarkdown(input) { .replace(/([A-Z])/g, "-$1") .trim() .slice(1) - .toLowerCase()})` + .toLowerCase()})`, ) .join("\n\t- "); out += `\n- [Deployment History](#deployment-history)`; @@ -258,7 +258,7 @@ function generateMarkdown(input) { ${contractName} ${address} ${version || `N/A`} - ` + `, ) .join("\n"); out += `\n`; @@ -293,7 +293,7 @@ ${generateProxyInformationIfProxy({ proxyAdmin, history: input.history, chainId: input.chainId, -})}` +})}`, ) .join("\n\n --- \n\n"); @@ -366,9 +366,9 @@ function generateProxyInformationIfProxy({ ${implementation} ${commitHash.slice( 0, - 7 + 7, )} - ` + `, ) .join("")} @@ -426,7 +426,7 @@ Deployed contracts: getEtherscanLink(chainId, contract.implementation) || contract.implementation }))` : `` - }` + }`, ) .join("\n- ")} @@ -443,12 +443,12 @@ Deployed contracts: ${key} ${value} -` +`, ) .join("\n")} - ` + `, ) .join("\n\n"); @@ -489,7 +489,7 @@ function validateInputs() { ) ) { console.log( - `error: script/${version}/input.json or script/${version}/${scriptName || ""} does not exist\n` + `error: script/${version}/input.json or script/${version}/${scriptName || ""} does not exist\n`, ); printUsageAndExit = true; } From b77f93ba18a8d11ff20cbfd89b3a04e53d76664f Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 3 Nov 2023 06:11:39 +0100 Subject: [PATCH 19/86] Run tests on PRs, only trigger docs when content changed --- .github/workflows/test.yaml | 4 +++- .pre-commit-config.yaml | 3 ++- script/util/doc_gen.sh | 22 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100755 script/util/doc_gen.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 09880b1..78a2ed5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,6 +1,8 @@ name: test -on: workflow_dispatch +on: + pull_request: + branches: [master, staging, dev, feat/**, fix/**] env: FOUNDRY_PROFILE: ci diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43394ee..75613ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,8 @@ repos: name: Generate documentation description: Generate docs with `forge doc` language: system - entry: forge doc -b -o docs/html + # 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" diff --git a/script/util/doc_gen.sh b/script/util/doc_gen.sh new file mode 100755 index 0000000..1010390 --- /dev/null +++ b/script/util/doc_gen.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# generate docs +forge doc -b -o docs/html + +# 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/html/*') + +# 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 '^[+-][^+-]') + echo $file + echo "$diff" | wc -l + # 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 From cdf98d85610f303ec8d109b1d833b6bcc0c50388 Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 3 Nov 2023 06:12:27 +0100 Subject: [PATCH 20/86] rename directory for autogenerated docs --- .pre-commit-config.yaml | 4 ++-- docs/{html => autogen}/.gitignore | 0 docs/{html => autogen}/book.css | 0 docs/{html => autogen}/book.toml | 0 docs/{html => autogen}/solidity.min.js | 0 docs/{html => autogen}/src/README.md | 0 docs/{html => autogen}/src/SUMMARY.md | 0 .../{html => autogen}/src/src/Counter.sol/contract.Counter.md | 4 ++-- docs/{html => autogen}/src/src/README.md | 0 .../src/src/interface/ICounter.sol/interface.ICounter.md | 4 ++-- .../src/src/interface/IVersioned.sol/interface.IVersioned.md | 2 +- docs/{html => autogen}/src/src/interface/README.md | 0 script/util/doc_gen.sh | 4 ++-- 13 files changed, 9 insertions(+), 9 deletions(-) rename docs/{html => autogen}/.gitignore (100%) rename docs/{html => autogen}/book.css (100%) rename docs/{html => autogen}/book.toml (100%) rename docs/{html => autogen}/solidity.min.js (100%) rename docs/{html => autogen}/src/README.md (100%) rename docs/{html => autogen}/src/SUMMARY.md (100%) rename docs/{html => autogen}/src/src/Counter.sol/contract.Counter.md (84%) rename docs/{html => autogen}/src/src/README.md (100%) rename docs/{html => autogen}/src/src/interface/ICounter.sol/interface.ICounter.md (79%) rename docs/{html => autogen}/src/src/interface/IVersioned.sol/interface.IVersioned.md (82%) rename docs/{html => autogen}/src/src/interface/README.md (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75613ae..550c23c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: mixed-line-ending args: ["--fix=lf"] description: Forces to replace line ending by the UNIX 'lf' character. - exclude: "^docs/html" + exclude: "^docs/autogen" - repo: local hooks: - id: format @@ -27,4 +27,4 @@ repos: hooks: - id: prettier name: Format non solidity files with prettier - exclude: "^docs/html" + exclude: "^docs/autogen" diff --git a/docs/html/.gitignore b/docs/autogen/.gitignore similarity index 100% rename from docs/html/.gitignore rename to docs/autogen/.gitignore diff --git a/docs/html/book.css b/docs/autogen/book.css similarity index 100% rename from docs/html/book.css rename to docs/autogen/book.css diff --git a/docs/html/book.toml b/docs/autogen/book.toml similarity index 100% rename from docs/html/book.toml rename to docs/autogen/book.toml diff --git a/docs/html/solidity.min.js b/docs/autogen/solidity.min.js similarity index 100% rename from docs/html/solidity.min.js rename to docs/autogen/solidity.min.js diff --git a/docs/html/src/README.md b/docs/autogen/src/README.md similarity index 100% rename from docs/html/src/README.md rename to docs/autogen/src/README.md diff --git a/docs/html/src/SUMMARY.md b/docs/autogen/src/SUMMARY.md similarity index 100% rename from docs/html/src/SUMMARY.md rename to docs/autogen/src/SUMMARY.md diff --git a/docs/html/src/src/Counter.sol/contract.Counter.md b/docs/autogen/src/src/Counter.sol/contract.Counter.md similarity index 84% rename from docs/html/src/src/Counter.sol/contract.Counter.md rename to docs/autogen/src/src/Counter.sol/contract.Counter.md index 48b3a10..34f3ccc 100644 --- a/docs/html/src/src/Counter.sol/contract.Counter.md +++ b/docs/autogen/src/src/Counter.sol/contract.Counter.md @@ -1,8 +1,8 @@ # Counter -[Git Source](https://github.com/0xPolygon/foundry-template/blob/8733bac54bd9be835d614ad4c7c76632d78ceaa0/src/Counter.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/8d1e780e7b907a30a740d7d96eeb5db9fb0b1450/src/Counter.sol) **Inherits:** -[ICounter](/docs/html/src/src/interface/ICounter.sol/interface.ICounter.md) +[ICounter](/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md) ## State Variables diff --git a/docs/html/src/src/README.md b/docs/autogen/src/src/README.md similarity index 100% rename from docs/html/src/src/README.md rename to docs/autogen/src/src/README.md diff --git a/docs/html/src/src/interface/ICounter.sol/interface.ICounter.md b/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md similarity index 79% rename from docs/html/src/src/interface/ICounter.sol/interface.ICounter.md rename to docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md index cc824ca..bc6802e 100644 --- a/docs/html/src/src/interface/ICounter.sol/interface.ICounter.md +++ b/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md @@ -1,8 +1,8 @@ # ICounter -[Git Source](https://github.com/0xPolygon/foundry-template/blob/8733bac54bd9be835d614ad4c7c76632d78ceaa0/src/interface/ICounter.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/8d1e780e7b907a30a740d7d96eeb5db9fb0b1450/src/interface/ICounter.sol) **Inherits:** -[IVersioned](/docs/html/src/src/interface/IVersioned.sol/interface.IVersioned.md) +[IVersioned](/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md) ## Functions diff --git a/docs/html/src/src/interface/IVersioned.sol/interface.IVersioned.md b/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md similarity index 82% rename from docs/html/src/src/interface/IVersioned.sol/interface.IVersioned.md rename to docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md index 8cd147b..f7cf1bb 100644 --- a/docs/html/src/src/interface/IVersioned.sol/interface.IVersioned.md +++ b/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md @@ -1,5 +1,5 @@ # IVersioned -[Git Source](https://github.com/0xPolygon/foundry-template/blob/8733bac54bd9be835d614ad4c7c76632d78ceaa0/src/interface/IVersioned.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/8d1e780e7b907a30a740d7d96eeb5db9fb0b1450/src/interface/IVersioned.sol) ## Functions diff --git a/docs/html/src/src/interface/README.md b/docs/autogen/src/src/interface/README.md similarity index 100% rename from docs/html/src/src/interface/README.md rename to docs/autogen/src/src/interface/README.md diff --git a/script/util/doc_gen.sh b/script/util/doc_gen.sh index 1010390..5098b44 100755 --- a/script/util/doc_gen.sh +++ b/script/util/doc_gen.sh @@ -1,11 +1,11 @@ #!/bin/bash # generate docs -forge doc -b -o docs/html +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/html/*') +files=$(git diff --name-only -- 'docs/autogen/*') # Loop over each file for file in $files; do From 0cb7b9adcf5782d2f788c03c0e96a86b449e1ef3 Mon Sep 17 00:00:00 2001 From: Simon Dosch Date: Fri, 3 Nov 2023 13:42:30 +0100 Subject: [PATCH 21/86] readme draft --- README.md | 70 +++++++++++++++---------------------------------------- 1 file changed, 19 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index b14ab12..741b3a3 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,42 @@ -## Foundry +## 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) - -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +This is a brief summary of the project. +What are its core features and what is its role in the bigger ecosystem -Foundry consists of: +Want to contribute to this project? +Check [CONTRIBUTING.md](CONTRIBUTING.md) -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. -## Documentation +#### Table of Contents +[Setup](#setup) +[Compilation](#compilation) +[Testing](#testing) +[Deployment](#deployment) +[Architecture](#architecture) +[License](#license) -https://book.getfoundry.sh/ - -## Usage - -### Build +## Compilation ```shell $ forge build ``` -### Test +### Testing ```shell $ forge test ``` -### Format - -```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Deploy +## Deployment ```shell $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key ``` -### Cast - -```shell -$ cast -``` - -### Help +## Architecture -```shell -$ forge --help -$ anvil --help -$ cast --help -``` +Add explanations and graphs to help poeple understand how the contracts of this repo work together. ---- +## License +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -Copyright (C) 2023 PT Services DMCC From f7d1865f99892a2ee59a34610980cc6b63229e65 Mon Sep 17 00:00:00 2001 From: Simon Dosch Date: Fri, 3 Nov 2023 15:56:14 +0100 Subject: [PATCH 22/86] create PR template --- .github/PULL_REQUEST_TEMPLATE.md | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..1ceb769 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,45 @@ +# Pull Request + +## Description + +Please include a summary of the change and which issue is 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 +- [ ] fuzz and invariant tests when applicable +- [ ] deployment or update scripts ready +- [ ] version management agreed upon and implemented +- [ ] documentation of work in Jira or Notion +- [ ] internal team review +- [ ] **Security Team review** + + +After deployment + +- [ ] Update [static](https://github.com/maticnetwork/static/tree/master/network) with new contract address and/or version +- [ ] create release if necessary +--- +### Considerations + +- I have followed the [contributing guidelines](../CONTRIBUTING.md). +- My code follows the style guidelines of this project and I have run `lint` 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. From 9427497e0b533dcfe14c6191a7682c2212114a28 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:20:30 +0100 Subject: [PATCH 23/86] chore: update foundry.toml --- foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/foundry.toml b/foundry.toml index 4a2ca8e..fa09e08 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,6 +6,7 @@ optimizer = true optimize_runs = 999999 via_ir = true solc = '0.8.21' +verbosity = 2 ffi = true fs_permissions = [ { access = "read", path = "scripts/config.json" }, From feee8bf54e3ce2c87293055ac47e50e046c9c8bd Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:21:17 +0100 Subject: [PATCH 24/86] feat: change Counter --- src/Counter.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Counter.sol b/src/Counter.sol index a691c7a..079575c 100644 --- a/src/Counter.sol +++ b/src/Counter.sol @@ -2,11 +2,12 @@ pragma solidity 0.8.21; import {ICounter, IVersioned} from "./interface/ICounter.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -contract Counter is ICounter { +contract Counter is ICounter, Initializable { uint256 public number; - constructor(uint256 initialNumber) { + function initialize(uint256 initialNumber) public initializer { number = initialNumber; } From 60aa7f7727ce18c0f968325372b2689f8cb2ef08 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:23:07 +0100 Subject: [PATCH 25/86] script: add deployer generator --- script/deployers/DeployCounter.s.sol | 38 +++++++++++ script/util/README.md | 5 ++ script/util/deployer_template | 38 +++++++++++ script/util/generateDeployer.js | 97 ++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 script/deployers/DeployCounter.s.sol create mode 100644 script/util/README.md create mode 100644 script/util/deployer_template create mode 100644 script/util/generateDeployer.js diff --git a/script/deployers/DeployCounter.s.sol b/script/deployers/DeployCounter.s.sol new file mode 100644 index 0000000..e5c6d89 --- /dev/null +++ b/script/deployers/DeployCounter.s.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "forge-std/Script.sol"; + +import "src/Counter.sol"; +import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract CounterDeployer is Script { + function deployCounter( + address proxyAdmin, + uint256 number + ) internal returns (Counter proxyAsCounter, address proxy, address logic) { + bytes memory initData = abi.encodeCall(Counter.initialize, (number)); + + return _deployCounter(proxyAdmin, initData); + } + + function deployCounter_NoInit( + address proxyAdmin + ) internal returns (Counter proxyAsCounter, address proxy, address logic) { + return _deployCounter(proxyAdmin, ""); + } + + function _deployCounter( + address proxyAdmin, + bytes memory initData + ) private returns (Counter proxyAsCounter, address proxy, address logic) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + + logic = address(new Counter()); + proxy = address(new TransparentUpgradeableProxy(logic, proxyAdmin, initData)); + + vm.stopBroadcast(); + + proxyAsCounter = Counter(proxy); + } +} diff --git a/script/util/README.md b/script/util/README.md new file mode 100644 index 0000000..757e522 --- /dev/null +++ b/script/util/README.md @@ -0,0 +1,5 @@ +### Generate deployer + +``` +node script/util/generateDeployer.js [type arg, ...] +``` \ No newline at end of file diff --git a/script/util/deployer_template b/script/util/deployer_template new file mode 100644 index 0000000..25f3050 --- /dev/null +++ b/script/util/deployer_template @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "forge-std/Script.sol"; + +import ""; +import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract Deployer is Script { + function deploy( + address proxyAdmin, + + ) internal returns ( proxyAs, address proxy, address logic) { + bytes memory initData = abi.encodeCall(.initialize, ()); + + return _deploy(proxyAdmin, initData); + } + + function deploy_NoInit( + address proxyAdmin + ) internal returns ( proxyAs, address proxy, address logic) { + return _deploy(proxyAdmin, ""); + } + + function _deploy( + address proxyAdmin, + bytes memory initData + ) private returns ( proxyAs, address proxy, address logic) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + + logic = address(new ()); + proxy = address(new TransparentUpgradeableProxy(logic, proxyAdmin, initData)); + + vm.stopBroadcast(); + + proxyAs = (proxy); + } +} diff --git a/script/util/generateDeployer.js b/script/util/generateDeployer.js new file mode 100644 index 0000000..3d4b613 --- /dev/null +++ b/script/util/generateDeployer.js @@ -0,0 +1,97 @@ +const fs = require("fs"); +const path = require("path"); + +// Function to replace occurrences of 'Example', 'uint256 arg', and 'arg' in a file +const replaceInFile = (filePath, newFilePath, replacementExample, replacementArgs, replacementPathToExample) => { + fs.readFile(filePath, "utf8", (err, data) => { + if (err) { + return console.error(err); + } + + let regexExample = new RegExp("", "g"); + let regexArgs = new RegExp("", "g"); + let regexArgsNames = new RegExp("", "g"); + let regexPathToExample = new RegExp("", "g"); + + let updatedData = data.replace(regexExample, replacementExample); + updatedData = updatedData.replace(regexArgs, replacementArgs); + updatedData = updatedData.replace(regexArgsNames, processString(replacementArgs)); + updatedData = updatedData.replace(regexPathToExample, replacementPathToExample); + + fs.writeFile(newFilePath, updatedData, "utf8", (err) => { + if (err) { + console.error(err); + } else { + console.log("Deployer generated."); + + if (replacementArgs.length === 0) { + fs.readFile(newFilePath, "utf8", (err, data) => { + if (err) { + return console.error(err); + } + + // Find the index of the first occurrence of "address proxyAdmin" and replace it + const index = data.indexOf("address proxyAdmin,\n"); + if (index !== -1) { + const newData = + data.slice(0, index) + "address proxyAdmin" + data.slice(index + "address proxyAdmin,\n".length); + // Write the updated content back to the file + fs.writeFile(newFilePath, newData, "utf8", (err) => { + if (err) return console.error(err); + }); + } else { + console.log('No match found for "address proxyAdmin," in the file.'); + } + }); + } + } + }); + }); +}; + +function processString(inputString) { + if (inputString.includes(",")) { + const words = inputString.split(","); + const lastWords = words.map((word) => word.trim().split(" ").pop()); + return lastWords.join(", "); + } else { + return inputString.trim().split(" ").pop(); + } +} + +// Replace occurrences in the specified file with the provided arguments +const filePath = "script/util/deployer_template"; +const replacementPathToExample = process.argv[2]; +const replacementArgs = process.argv[3]; +const newFilePath = process.argv[4]; +let replacementExample; + +// Extract the file name from the path by splitting the string based on the '/' delimiter +const parts = replacementPathToExample.split("/"); +// Get the last part of the path, which is the file name with the extension +const fileNameWithExtension = parts[parts.length - 1]; +// Split the file name by the dot('.') to get the name and the extension separately +const fileNameParts = fileNameWithExtension.split("."); +// Check if there is more than one element in the fileNameParts array +if (fileNameParts.length > 1) { + // Join the parts of the file name excluding the last element (the extension) + replacementExample = fileNameParts.slice(0, -1).join("."); +} else { + // The file name as it is if no extension is found + replacementExample = fileNameParts[0]; +} + +if (!replacementPathToExample || !newFilePath) { + console.error( + "Usage: node script/util/generateDeployer.js [type arg, ...] " + ); + process.exit(1); +} + +let filePathPrefix = newFilePath; + +const formattedPath = path.join(filePathPrefix, "Deploy" + replacementExample + ".s.sol"); + +replaceInFile(filePath, formattedPath, replacementExample, replacementArgs, replacementPathToExample); + +// TODO: Format the new file From 2679d55e7e4ae54fe5ff19a2e3ddc73afbc5f5b7 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:24:11 +0100 Subject: [PATCH 26/86] script: add Deploy script --- script/1.0.0/Deploy.s.sol | 22 +++++++++++++--------- script/1.0.0/input.json | 17 ++++++++++------- script/util/Helpers.sol | 13 +++++++++++++ 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 script/util/Helpers.sol diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol index 629c48b..77ea53f 100644 --- a/script/1.0.0/Deploy.s.sol +++ b/script/1.0.0/Deploy.s.sol @@ -1,23 +1,27 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.21; -import {Script, stdJson, console2 as console} from "forge-std/Script.sol"; +import "forge-std/Script.sol"; +import "script/util/Helpers.sol"; -import {Counter} from "../../src/Counter.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -contract Deploy is Script { +import "script/deployers/DeployCounter.s.sol"; + +contract Deploy is Script, Helpers, CounterDeployer { using stdJson for string; + address internal proxyAdmin; + + Counter internal counter; + function run() public { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); string memory input = vm.readFile("script/1.0.0/input.json"); - string memory chainIdSlug = string(abi.encodePacked('["', vm.toString(block.chainid), '"]')); - uint256 num = input.readUint(string.concat(chainIdSlug, ".num")); - - vm.startBroadcast(deployerPrivateKey); - new Counter(num); + vm.broadcast(deployerPrivateKey); + proxyAdmin = address(new ProxyAdmin(input.readAddress($("ProxyAdmin.initialOwner")))); - vm.stopBroadcast(); + (counter, , ) = deployCounter(address(proxyAdmin), input.readUint($("Counter.number"))); } } diff --git a/script/1.0.0/input.json b/script/1.0.0/input.json index 40a551a..0e24f90 100644 --- a/script/1.0.0/input.json +++ b/script/1.0.0/input.json @@ -1,11 +1,14 @@ { - "1": { - "num": 1 - }, - "5": { - "num": 2 - }, + "1": {}, + + "5": {}, + "31337": { - "num": 3 + "ProxyAdmin": { + "initialOwner": "0x48789ECd4317eCc8dBdB1d06EF39F01c5862a941" + }, + "Counter": { + "number": 3 + } } } diff --git a/script/util/Helpers.sol b/script/util/Helpers.sol new file mode 100644 index 0000000..fa04700 --- /dev/null +++ b/script/util/Helpers.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "forge-std/Script.sol"; + +abstract contract Helpers is Script { + using stdJson for string; + + function $(string memory field) internal view returns (string memory) { + string memory chainIdSlug = string.concat('["', vm.toString(block.chainid), '"]'); + return string.concat(chainIdSlug, ".", field); + } +} From a9f2dec0535500e07491e0f2e99dcf48319d8022 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:24:23 +0100 Subject: [PATCH 27/86] test: add basic tests --- test/Counter.t.sol | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/test/Counter.t.sol b/test/Counter.t.sol index 4e17e56..d0c792c 100644 --- a/test/Counter.t.sol +++ b/test/Counter.t.sol @@ -1,23 +1,30 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.21; -import {Test, console2} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; +import "forge-std/Test.sol"; -contract CounterTest is Test { - Counter public counter; +import "script/1.0.0/Deploy.s.sol"; +contract CounterTest is Test, Deploy { function setUp() public { - counter = new Counter(1); - counter.setNumber(0); + run(); } - function test_Increment() public { + function test_IsInitialized() public { + assertEq(counter.number(), 3); + } + + function test_RevertsIf_AlreadyInitialized() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + counter.initialize(1); + } + + function test_IncrementsNumber() public { counter.increment(); - assertEq(counter.number(), 1); + assertEq(counter.number(), 4); } - function testFuzz_SetNumber(uint256 x) public { + function testFuzz_SetsNumber(uint256 x) public { counter.setNumber(x); assertEq(counter.number(), x); } From 06cb74318647b9cf0509ed8878fc124c2ba6ecba Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 3 Nov 2023 19:41:01 +0100 Subject: [PATCH 28/86] Run pre-commit --- .github/PULL_REQUEST_TEMPLATE.md | 22 +++---- README.md | 10 ++- docs/autogen/src/README.md | 66 ++++++------------- .../src/src/Counter.sol/contract.Counter.md | 8 +-- script/1.0.0/Deploy.s.sol | 2 +- script/deployers/DeployCounter.s.sol | 25 +++---- script/util/README.md | 2 +- script/util/generateDeployer.js | 2 +- 8 files changed, 57 insertions(+), 80 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1ceb769..49d17e9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,20 +14,21 @@ Please describe the tests that you ran to verify your changes. Provide instructi Before deployment -- [ ] 100% test and branch coverage -- [ ] fuzz and invariant tests when applicable -- [ ] deployment or update scripts ready -- [ ] version management agreed upon and implemented -- [ ] documentation of work in Jira or Notion -- [ ] internal team review -- [ ] **Security Team review** - +- [ ] 100% test and branch coverage +- [ ] fuzz and invariant tests when applicable +- [ ] deployment or update scripts ready +- [ ] version management agreed upon and implemented +- [ ] documentation of work in Jira or Notion +- [ ] internal team review +- [ ] **Security Team review** After deployment -- [ ] Update [static](https://github.com/maticnetwork/static/tree/master/network) with new contract address and/or version -- [ ] create release if necessary +- [ ] Update [static](https://github.com/maticnetwork/static/tree/master/network) with new contract address and/or version +- [ ] create release if necessary + --- + ### Considerations - I have followed the [contributing guidelines](../CONTRIBUTING.md). @@ -39,7 +40,6 @@ After deployment - 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/README.md b/README.md index 741b3a3..7420dd6 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,16 @@ What are its core features and what is its role in the bigger ecosystem Want to contribute to this project? Check [CONTRIBUTING.md](CONTRIBUTING.md) +#### Table of Contents -#### Table of Contents [Setup](#setup) [Compilation](#compilation) [Testing](#testing) [Deployment](#deployment) [Architecture](#architecture) -[License](#license) +[License](#license) -## Compilation +## Compilation ```shell $ forge build @@ -38,5 +38,9 @@ $ forge script script/Counter.s.sol:CounterScript --rpc-url --pri Add explanations and graphs to help poeple understand how the contracts of this repo work together. ## License + [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +--- + +Copyright (C) 2023 PT Services DMCC diff --git a/docs/autogen/src/README.md b/docs/autogen/src/README.md index b14ab12..7420dd6 100644 --- a/docs/autogen/src/README.md +++ b/docs/autogen/src/README.md @@ -1,73 +1,45 @@ -## Foundry +## 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) - -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +This is a brief summary of the project. +What are its core features and what is its role in the bigger ecosystem -Foundry consists of: +Want to contribute to this project? +Check [CONTRIBUTING.md](CONTRIBUTING.md) -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +#### Table of Contents -## Documentation +[Setup](#setup) +[Compilation](#compilation) +[Testing](#testing) +[Deployment](#deployment) +[Architecture](#architecture) +[License](#license) -https://book.getfoundry.sh/ - -## Usage - -### Build +## Compilation ```shell $ forge build ``` -### Test +### Testing ```shell $ forge test ``` -### Format - -```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Deploy +## Deployment ```shell $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key ``` -### Cast +## Architecture -```shell -$ cast -``` +Add explanations and graphs to help poeple understand how the contracts of this repo work together. -### Help +## License -```shell -$ forge --help -$ anvil --help -$ cast --help -``` +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) --- diff --git a/docs/autogen/src/src/Counter.sol/contract.Counter.md b/docs/autogen/src/src/Counter.sol/contract.Counter.md index 34f3ccc..b8849d0 100644 --- a/docs/autogen/src/src/Counter.sol/contract.Counter.md +++ b/docs/autogen/src/src/Counter.sol/contract.Counter.md @@ -1,8 +1,8 @@ # Counter -[Git Source](https://github.com/0xPolygon/foundry-template/blob/8d1e780e7b907a30a740d7d96eeb5db9fb0b1450/src/Counter.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/a9f2dec0535500e07491e0f2e99dcf48319d8022/src/Counter.sol) **Inherits:** -[ICounter](/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md) +[ICounter](/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md), Initializable ## State Variables @@ -14,11 +14,11 @@ uint256 public number; ## Functions -### constructor +### initialize ```solidity -constructor(uint256 initialNumber); +function initialize(uint256 initialNumber) public initializer; ``` ### setNumber diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol index 77ea53f..a9bc0cd 100644 --- a/script/1.0.0/Deploy.s.sol +++ b/script/1.0.0/Deploy.s.sol @@ -22,6 +22,6 @@ contract Deploy is Script, Helpers, CounterDeployer { vm.broadcast(deployerPrivateKey); proxyAdmin = address(new ProxyAdmin(input.readAddress($("ProxyAdmin.initialOwner")))); - (counter, , ) = deployCounter(address(proxyAdmin), input.readUint($("Counter.number"))); + (counter,,) = deployCounter(address(proxyAdmin), input.readUint($("Counter.number"))); } } diff --git a/script/deployers/DeployCounter.s.sol b/script/deployers/DeployCounter.s.sol index e5c6d89..7282112 100644 --- a/script/deployers/DeployCounter.s.sol +++ b/script/deployers/DeployCounter.s.sol @@ -4,28 +4,29 @@ pragma solidity ^0.8.19; import "forge-std/Script.sol"; import "src/Counter.sol"; -import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; abstract contract CounterDeployer is Script { - function deployCounter( - address proxyAdmin, - uint256 number - ) internal returns (Counter proxyAsCounter, address proxy, address logic) { + function deployCounter(address proxyAdmin, uint256 number) + internal + returns (Counter proxyAsCounter, address proxy, address logic) + { bytes memory initData = abi.encodeCall(Counter.initialize, (number)); return _deployCounter(proxyAdmin, initData); } - function deployCounter_NoInit( - address proxyAdmin - ) internal returns (Counter proxyAsCounter, address proxy, address logic) { + function deployCounter_NoInit(address proxyAdmin) + internal + returns (Counter proxyAsCounter, address proxy, address logic) + { return _deployCounter(proxyAdmin, ""); } - function _deployCounter( - address proxyAdmin, - bytes memory initData - ) private returns (Counter proxyAsCounter, address proxy, address logic) { + function _deployCounter(address proxyAdmin, bytes memory initData) + private + returns (Counter proxyAsCounter, address proxy, address logic) + { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); logic = address(new Counter()); diff --git a/script/util/README.md b/script/util/README.md index 757e522..da3716e 100644 --- a/script/util/README.md +++ b/script/util/README.md @@ -2,4 +2,4 @@ ``` node script/util/generateDeployer.js [type arg, ...] -``` \ No newline at end of file +``` diff --git a/script/util/generateDeployer.js b/script/util/generateDeployer.js index 3d4b613..45a71c8 100644 --- a/script/util/generateDeployer.js +++ b/script/util/generateDeployer.js @@ -83,7 +83,7 @@ if (fileNameParts.length > 1) { if (!replacementPathToExample || !newFilePath) { console.error( - "Usage: node script/util/generateDeployer.js [type arg, ...] " + "Usage: node script/util/generateDeployer.js [type arg, ...] ", ); process.exit(1); } From 04498da2d81b2426906f4e6b3e540e5b02fec54b Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:32:50 +0100 Subject: [PATCH 29/86] script: add ScriptHelpers --- script/1.0.0/Deploy.s.sol | 8 ++++---- script/util/{Helpers.sol => ScriptHelpers.sol} | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) rename script/util/{Helpers.sol => ScriptHelpers.sol} (71%) diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol index a9bc0cd..c7c2067 100644 --- a/script/1.0.0/Deploy.s.sol +++ b/script/1.0.0/Deploy.s.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.21; +pragma solidity 0.8.22; import "forge-std/Script.sol"; -import "script/util/Helpers.sol"; +import "script/util/ScriptHelpers.sol"; import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "script/deployers/DeployCounter.s.sol"; -contract Deploy is Script, Helpers, CounterDeployer { +contract Deploy is Script, ScriptHelpers, CounterDeployer { using stdJson for string; address internal proxyAdmin; @@ -22,6 +22,6 @@ contract Deploy is Script, Helpers, CounterDeployer { vm.broadcast(deployerPrivateKey); proxyAdmin = address(new ProxyAdmin(input.readAddress($("ProxyAdmin.initialOwner")))); - (counter,,) = deployCounter(address(proxyAdmin), input.readUint($("Counter.number"))); + (counter, , ) = deployCounter(address(proxyAdmin), input.readUint($("Counter.number"))); } } diff --git a/script/util/Helpers.sol b/script/util/ScriptHelpers.sol similarity index 71% rename from script/util/Helpers.sol rename to script/util/ScriptHelpers.sol index fa04700..6eb218e 100644 --- a/script/util/Helpers.sol +++ b/script/util/ScriptHelpers.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.21; +pragma solidity 0.8.22; import "forge-std/Script.sol"; -abstract contract Helpers is Script { +abstract contract ScriptHelpers is Script { using stdJson for string; + ///@notice Returns the JSON field for the current chain ID. function $(string memory field) internal view returns (string memory) { string memory chainIdSlug = string.concat('["', vm.toString(block.chainid), '"]'); return string.concat(chainIdSlug, ".", field); From 6309130617a2cafc205ec6a150b71354829c0518 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:34:00 +0100 Subject: [PATCH 30/86] chore: remove Counter script --- script/Counter.s.sol | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 script/Counter.s.sol diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index 1a47b40..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console2} from "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} From 204ad2b9a29264ca770bfc5842bc44b36905a848 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:36:03 +0100 Subject: [PATCH 31/86] test: add complete tests --- script/1.0.0/input.json | 2 +- test/1.0.0/Counter.t.sol | 53 +++++++++++++++++++++++++++++++++++++++ test/Counter.t.sol | 31 ----------------------- test/util/TestHelpers.sol | 13 ++++++++++ 4 files changed, 67 insertions(+), 32 deletions(-) create mode 100644 test/1.0.0/Counter.t.sol delete mode 100644 test/Counter.t.sol create mode 100644 test/util/TestHelpers.sol diff --git a/script/1.0.0/input.json b/script/1.0.0/input.json index 0e24f90..5e2102b 100644 --- a/script/1.0.0/input.json +++ b/script/1.0.0/input.json @@ -8,7 +8,7 @@ "initialOwner": "0x48789ECd4317eCc8dBdB1d06EF39F01c5862a941" }, "Counter": { - "number": 3 + "number": 10 } } } diff --git a/test/1.0.0/Counter.t.sol b/test/1.0.0/Counter.t.sol new file mode 100644 index 0000000..9262bda --- /dev/null +++ b/test/1.0.0/Counter.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.22; + +import "forge-std/Test.sol"; +import "test/util/TestHelpers.sol"; + +import "script/1.0.0/Deploy.s.sol"; + +abstract contract BeforeScript is Test, TestHelpers, CounterDeployer { + Counter internal counter; + + function setUp() public { + (counter, , ) = deployCounter_NoInit(makeAddr("")); + } +} + +contract CounterTest_Zero is BeforeScript { + function test_Initializes(uint256 number) public { + counter.initialize(number); + assertEq(counter.number(), number); + } +} + +abstract contract AfterScript is Test, TestHelpers, Deploy { + function setUp() public virtual { + run(); + } +} + +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/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index d0c792c..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.21; - -import "forge-std/Test.sol"; - -import "script/1.0.0/Deploy.s.sol"; - -contract CounterTest is Test, Deploy { - function setUp() public { - run(); - } - - function test_IsInitialized() public { - assertEq(counter.number(), 3); - } - - function test_RevertsIf_AlreadyInitialized() public { - vm.expectRevert(Initializable.InvalidInitialization.selector); - counter.initialize(1); - } - - function test_IncrementsNumber() public { - counter.increment(); - assertEq(counter.number(), 4); - } - - function testFuzz_SetsNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/util/TestHelpers.sol b/test/util/TestHelpers.sol new file mode 100644 index 0000000..1d03de4 --- /dev/null +++ b/test/util/TestHelpers.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.22; + +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)); + } +} From 1982945ef9a5f6e6ec018759f8fcd1f51129ed46 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:36:41 +0100 Subject: [PATCH 32/86] feat: use solc 0.8.22 --- foundry.toml | 2 +- script/deployers/DeployCounter.s.sol | 25 ++++++++++++------------- script/util/deployer_template | 4 ++-- src/Counter.sol | 2 +- src/interface/ICounter.sol | 10 +++++----- src/interface/IVersioned.sol | 4 ++-- 6 files changed, 23 insertions(+), 24 deletions(-) diff --git a/foundry.toml b/foundry.toml index fa09e08..674be44 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ libs = ['lib'] optimizer = true optimize_runs = 999999 via_ir = true -solc = '0.8.21' +solc = '0.8.22' verbosity = 2 ffi = true fs_permissions = [ diff --git a/script/deployers/DeployCounter.s.sol b/script/deployers/DeployCounter.s.sol index 7282112..015db87 100644 --- a/script/deployers/DeployCounter.s.sol +++ b/script/deployers/DeployCounter.s.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.22; import "forge-std/Script.sol"; @@ -7,26 +7,25 @@ import "src/Counter.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; abstract contract CounterDeployer is Script { - function deployCounter(address proxyAdmin, uint256 number) - internal - returns (Counter proxyAsCounter, address proxy, address logic) - { + function deployCounter( + address proxyAdmin, + uint256 number + ) internal returns (Counter proxyAsCounter, address proxy, address logic) { bytes memory initData = abi.encodeCall(Counter.initialize, (number)); return _deployCounter(proxyAdmin, initData); } - function deployCounter_NoInit(address proxyAdmin) - internal - returns (Counter proxyAsCounter, address proxy, address logic) - { + function deployCounter_NoInit( + address proxyAdmin + ) internal returns (Counter proxyAsCounter, address proxy, address logic) { return _deployCounter(proxyAdmin, ""); } - function _deployCounter(address proxyAdmin, bytes memory initData) - private - returns (Counter proxyAsCounter, address proxy, address logic) - { + function _deployCounter( + address proxyAdmin, + bytes memory initData + ) private returns (Counter proxyAsCounter, address proxy, address logic) { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); logic = address(new Counter()); diff --git a/script/util/deployer_template b/script/util/deployer_template index 25f3050..3f69a66 100644 --- a/script/util/deployer_template +++ b/script/util/deployer_template @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity 0.8.21; import "forge-std/Script.sol"; import ""; -import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; abstract contract Deployer is Script { function deploy( diff --git a/src/Counter.sol b/src/Counter.sol index 079575c..23c6eb1 100644 --- a/src/Counter.sol +++ b/src/Counter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.21; +pragma solidity 0.8.22; import {ICounter, IVersioned} from "./interface/ICounter.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; diff --git a/src/interface/ICounter.sol b/src/interface/ICounter.sol index 4b7acf7..4eb5c3b 100644 --- a/src/interface/ICounter.sol +++ b/src/interface/ICounter.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.21; +pragma solidity 0.8.22; import {IVersioned} from "./IVersioned.sol"; interface ICounter is IVersioned { - /// @return the current number + /// @return The current number. function number() external view returns (uint256); - /// @notice set the number - /// @param newNumber the new number + /// @notice Sets the number. + /// @param newNumber The new number. function setNumber(uint256 newNumber) external; - /// @notice increment the number by 1 + /// @notice Increments the number by 1. function increment() external; } diff --git a/src/interface/IVersioned.sol b/src/interface/IVersioned.sol index 98b238c..badd969 100644 --- a/src/interface/IVersioned.sol +++ b/src/interface/IVersioned.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.21; +pragma solidity 0.8.22; interface IVersioned { - /// @return the version of the contract + /// @return The version of the contract. function version() external pure returns (string memory); } From 68d1a29704a7d298a24a12d604c6014b3444bc12 Mon Sep 17 00:00:00 2001 From: gretzke Date: Sat, 4 Nov 2023 01:44:31 +0100 Subject: [PATCH 33/86] Add formatter options, run pre-compile --- .../src/src/Counter.sol/contract.Counter.md | 10 +++++----- .../interface/ICounter.sol/interface.ICounter.md | 10 +++++----- .../IVersioned.sol/interface.IVersioned.md | 4 ++-- foundry.toml | 4 ++++ script/1.0.0/Deploy.s.sol | 2 +- script/deployers/DeployCounter.s.sol | 14 +++----------- test/1.0.0/Counter.t.sol | 2 +- 7 files changed, 21 insertions(+), 25 deletions(-) diff --git a/docs/autogen/src/src/Counter.sol/contract.Counter.md b/docs/autogen/src/src/Counter.sol/contract.Counter.md index b8849d0..4c3583e 100644 --- a/docs/autogen/src/src/Counter.sol/contract.Counter.md +++ b/docs/autogen/src/src/Counter.sol/contract.Counter.md @@ -1,5 +1,5 @@ # Counter -[Git Source](https://github.com/0xPolygon/foundry-template/blob/a9f2dec0535500e07491e0f2e99dcf48319d8022/src/Counter.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/1982945ef9a5f6e6ec018759f8fcd1f51129ed46/src/Counter.sol) **Inherits:** [ICounter](/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md), Initializable @@ -23,7 +23,7 @@ function initialize(uint256 initialNumber) public initializer; ### setNumber -set the number +Sets the number. ```solidity @@ -33,12 +33,12 @@ function setNumber(uint256 newNumber) public; |Name|Type|Description| |----|----|-----------| -|`newNumber`|`uint256`|the new number| +|`newNumber`|`uint256`|The new number.| ### increment -increment the number by 1 +Increments the number by 1. ```solidity @@ -55,6 +55,6 @@ function version() external pure returns (string memory); |Name|Type|Description| |----|----|-----------| -|``|`string`|the version of the contract| +|``|`string`|The version of the contract.| diff --git a/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md b/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md index bc6802e..83f228a 100644 --- a/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md +++ b/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md @@ -1,5 +1,5 @@ # ICounter -[Git Source](https://github.com/0xPolygon/foundry-template/blob/8d1e780e7b907a30a740d7d96eeb5db9fb0b1450/src/interface/ICounter.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/1982945ef9a5f6e6ec018759f8fcd1f51129ed46/src/interface/ICounter.sol) **Inherits:** [IVersioned](/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md) @@ -16,12 +16,12 @@ function number() external view returns (uint256); |Name|Type|Description| |----|----|-----------| -|``|`uint256`|the current number| +|``|`uint256`|The current number.| ### setNumber -set the number +Sets the number. ```solidity @@ -31,12 +31,12 @@ function setNumber(uint256 newNumber) external; |Name|Type|Description| |----|----|-----------| -|`newNumber`|`uint256`|the new number| +|`newNumber`|`uint256`|The new number.| ### increment -increment the number by 1 +Increments the number by 1. ```solidity diff --git a/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md b/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md index f7cf1bb..7ee30ec 100644 --- a/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md +++ b/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md @@ -1,5 +1,5 @@ # IVersioned -[Git Source](https://github.com/0xPolygon/foundry-template/blob/8d1e780e7b907a30a740d7d96eeb5db9fb0b1450/src/interface/IVersioned.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/1982945ef9a5f6e6ec018759f8fcd1f51129ed46/src/interface/IVersioned.sol) ## Functions @@ -13,6 +13,6 @@ function version() external pure returns (string memory); |Name|Type|Description| |----|----|-----------| -|``|`string`|the version of the contract| +|``|`string`|The version of the contract.| diff --git a/foundry.toml b/foundry.toml index 674be44..125c1af 100644 --- a/foundry.toml +++ b/foundry.toml @@ -22,4 +22,8 @@ remappings = [ runs = 10000 max_test_rejects = 999999 +[fmt] +line_length = 160 +number_underscore = "thousands" + # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol index c7c2067..f51b61b 100644 --- a/script/1.0.0/Deploy.s.sol +++ b/script/1.0.0/Deploy.s.sol @@ -22,6 +22,6 @@ contract Deploy is Script, ScriptHelpers, CounterDeployer { vm.broadcast(deployerPrivateKey); proxyAdmin = address(new ProxyAdmin(input.readAddress($("ProxyAdmin.initialOwner")))); - (counter, , ) = deployCounter(address(proxyAdmin), input.readUint($("Counter.number"))); + (counter,,) = deployCounter(address(proxyAdmin), input.readUint($("Counter.number"))); } } diff --git a/script/deployers/DeployCounter.s.sol b/script/deployers/DeployCounter.s.sol index 015db87..e2ff2ec 100644 --- a/script/deployers/DeployCounter.s.sol +++ b/script/deployers/DeployCounter.s.sol @@ -7,25 +7,17 @@ import "src/Counter.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; abstract contract CounterDeployer is Script { - function deployCounter( - address proxyAdmin, - uint256 number - ) internal returns (Counter proxyAsCounter, address proxy, address logic) { + function deployCounter(address proxyAdmin, uint256 number) internal returns (Counter proxyAsCounter, address proxy, address logic) { bytes memory initData = abi.encodeCall(Counter.initialize, (number)); return _deployCounter(proxyAdmin, initData); } - function deployCounter_NoInit( - address proxyAdmin - ) internal returns (Counter proxyAsCounter, address proxy, address logic) { + function deployCounter_NoInit(address proxyAdmin) internal returns (Counter proxyAsCounter, address proxy, address logic) { return _deployCounter(proxyAdmin, ""); } - function _deployCounter( - address proxyAdmin, - bytes memory initData - ) private returns (Counter proxyAsCounter, address proxy, address logic) { + function _deployCounter(address proxyAdmin, bytes memory initData) private returns (Counter proxyAsCounter, address proxy, address logic) { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); logic = address(new Counter()); diff --git a/test/1.0.0/Counter.t.sol b/test/1.0.0/Counter.t.sol index 9262bda..a58d536 100644 --- a/test/1.0.0/Counter.t.sol +++ b/test/1.0.0/Counter.t.sol @@ -10,7 +10,7 @@ abstract contract BeforeScript is Test, TestHelpers, CounterDeployer { Counter internal counter; function setUp() public { - (counter, , ) = deployCounter_NoInit(makeAddr("")); + (counter,,) = deployCounter_NoInit(makeAddr("")); } } From cb433f26db54ffad614c59ab12d458a2a8fc6996 Mon Sep 17 00:00:00 2001 From: gretzke Date: Sat, 4 Nov 2023 01:59:15 +0100 Subject: [PATCH 34/86] small fixes --- .github/PULL_REQUEST_TEMPLATE.md | 16 +++++++++------- .../src/src/Counter.sol/contract.Counter.md | 10 +++++----- .../interface/ICounter.sol/interface.ICounter.md | 10 +++++----- .../IVersioned.sol/interface.IVersioned.md | 4 ++-- foundry.toml | 13 +++++++------ src/interface/ICounter.sol | 8 ++++---- src/interface/IVersioned.sol | 2 +- test/1.0.0/Counter.t.sol | 2 +- 8 files changed, 34 insertions(+), 31 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 49d17e9..3113159 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ ## Description -Please include a summary of the change and which issue is fixed. Also, include relevant motivation and context. List any dependencies that are required for this change. +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) @@ -15,24 +15,26 @@ Please describe the tests that you ran to verify your changes. Provide instructi Before deployment - [ ] 100% test and branch coverage -- [ ] fuzz and invariant tests when applicable -- [ ] deployment or update scripts ready +- [ ] fuzz and invariant tests (when applicable) +- [ ] formal verification (when applicable) +- [ ] deployment or upgrade scripts ready - [ ] version management agreed upon and implemented -- [ ] documentation of work in Jira or Notion - [ ] internal team review - [ ] **Security Team review** After deployment -- [ ] Update [static](https://github.com/maticnetwork/static/tree/master/network) with new contract address and/or version -- [ ] create release if necessary +- [ ] 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 `lint` to ensure the code style is valid +- 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 diff --git a/docs/autogen/src/src/Counter.sol/contract.Counter.md b/docs/autogen/src/src/Counter.sol/contract.Counter.md index 4c3583e..27b0485 100644 --- a/docs/autogen/src/src/Counter.sol/contract.Counter.md +++ b/docs/autogen/src/src/Counter.sol/contract.Counter.md @@ -1,5 +1,5 @@ # Counter -[Git Source](https://github.com/0xPolygon/foundry-template/blob/1982945ef9a5f6e6ec018759f8fcd1f51129ed46/src/Counter.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/55b07186cd4779cbe55cc2f262f992aeabaf34ad/src/Counter.sol) **Inherits:** [ICounter](/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md), Initializable @@ -23,7 +23,7 @@ function initialize(uint256 initialNumber) public initializer; ### setNumber -Sets the number. +Sets the number ```solidity @@ -33,12 +33,12 @@ function setNumber(uint256 newNumber) public; |Name|Type|Description| |----|----|-----------| -|`newNumber`|`uint256`|The new number.| +|`newNumber`|`uint256`|The new number| ### increment -Increments the number by 1. +Increments the number by 1 ```solidity @@ -55,6 +55,6 @@ function version() external pure returns (string memory); |Name|Type|Description| |----|----|-----------| -|``|`string`|The version of the contract.| +|``|`string`|The version of the contract| diff --git a/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md b/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md index 83f228a..d207307 100644 --- a/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md +++ b/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md @@ -1,5 +1,5 @@ # ICounter -[Git Source](https://github.com/0xPolygon/foundry-template/blob/1982945ef9a5f6e6ec018759f8fcd1f51129ed46/src/interface/ICounter.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/55b07186cd4779cbe55cc2f262f992aeabaf34ad/src/interface/ICounter.sol) **Inherits:** [IVersioned](/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md) @@ -16,12 +16,12 @@ function number() external view returns (uint256); |Name|Type|Description| |----|----|-----------| -|``|`uint256`|The current number.| +|``|`uint256`|The current number| ### setNumber -Sets the number. +Sets the number ```solidity @@ -31,12 +31,12 @@ function setNumber(uint256 newNumber) external; |Name|Type|Description| |----|----|-----------| -|`newNumber`|`uint256`|The new number.| +|`newNumber`|`uint256`|The new number| ### increment -Increments the number by 1. +Increments the number by 1 ```solidity diff --git a/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md b/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md index 7ee30ec..c319a0e 100644 --- a/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md +++ b/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md @@ -1,5 +1,5 @@ # IVersioned -[Git Source](https://github.com/0xPolygon/foundry-template/blob/1982945ef9a5f6e6ec018759f8fcd1f51129ed46/src/interface/IVersioned.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/55b07186cd4779cbe55cc2f262f992aeabaf34ad/src/interface/IVersioned.sol) ## Functions @@ -13,6 +13,6 @@ function version() external pure returns (string memory); |Name|Type|Description| |----|----|-----------| -|``|`string`|The version of the contract.| +|``|`string`|The version of the contract| diff --git a/foundry.toml b/foundry.toml index 125c1af..e0878bd 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,11 +1,11 @@ [profile.default] -src = 'src' -out = 'out' -libs = ['lib'] +src = "src" +out = "out" +libs = ["lib"] optimizer = true optimize_runs = 999999 via_ir = true -solc = '0.8.22' +solc = "0.8.22" verbosity = 2 ffi = true fs_permissions = [ @@ -14,8 +14,9 @@ fs_permissions = [ ] remappings = [ - "@openzeppelin/contracts=lib/openzeppelin-contracts/contracts", - "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts" + "forge-std=lib/forge-std/src", + "@openzeppelin/contracts=lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts" ] [profile.intense.fuzz] diff --git a/src/interface/ICounter.sol b/src/interface/ICounter.sol index 4eb5c3b..d499905 100644 --- a/src/interface/ICounter.sol +++ b/src/interface/ICounter.sol @@ -4,13 +4,13 @@ pragma solidity 0.8.22; import {IVersioned} from "./IVersioned.sol"; interface ICounter is IVersioned { - /// @return The current number. + /// @return The current number function number() external view returns (uint256); - /// @notice Sets the number. - /// @param newNumber The new number. + /// @notice Sets the number + /// @param newNumber The new number function setNumber(uint256 newNumber) external; - /// @notice Increments the number by 1. + /// @notice Increments the number by 1 function increment() external; } diff --git a/src/interface/IVersioned.sol b/src/interface/IVersioned.sol index badd969..dc6caae 100644 --- a/src/interface/IVersioned.sol +++ b/src/interface/IVersioned.sol @@ -2,6 +2,6 @@ pragma solidity 0.8.22; interface IVersioned { - /// @return The version of the contract. + /// @return The version of the contract function version() external pure returns (string memory); } diff --git a/test/1.0.0/Counter.t.sol b/test/1.0.0/Counter.t.sol index a58d536..2c9b2e1 100644 --- a/test/1.0.0/Counter.t.sol +++ b/test/1.0.0/Counter.t.sol @@ -6,7 +6,7 @@ import "test/util/TestHelpers.sol"; import "script/1.0.0/Deploy.s.sol"; -abstract contract BeforeScript is Test, TestHelpers, CounterDeployer { +abstract contract BeforeScript is TestHelpers, CounterDeployer { Counter internal counter; function setUp() public { From 284d7cf735988b224fe62df22cb94f322b8b492e Mon Sep 17 00:00:00 2001 From: gretzke Date: Sat, 4 Nov 2023 05:14:36 +0100 Subject: [PATCH 35/86] Update Readme & Contributing --- .env.example | 6 +- CONTRIBUTING.md | 140 ++++++++++++++++++++++++++++++++++++- README.md | 48 +++++++------ docs/README.md | 3 + docs/autogen/src/README.md | 48 +++++++------ foundry.toml | 19 +++++ 6 files changed, 213 insertions(+), 51 deletions(-) create mode 100644 docs/README.md diff --git a/.env.example b/.env.example index 2f991ec..cacde6e 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ -RPC_URL= PRIVATE_KEY= -ETHERSCAN_API_KEY= \ No newline at end of file +INFURA_KEY= +ETHERSCAN_API_KEY= +POLYGONSCAN_API_KEY= +POLYGONSCAN_ZKEVM_API_KEY= \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 84f0914..24d70a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,142 @@ # Contributing +- [Install](#install) +- [Pre-commit Hooks](#pre-commit-hooks) +- [Branching](#branching) +- [Versioning](#versioning) +- [Code Practices](#code-practices) +- [Testing](#testing) +- [Deployment](#deployment) +- [Deployment Info Generation](#deployment-info-generation) +- [Deployment Template Script](#deployment-template-script) +- [Releases](#releases) + ## Install -To ensure consistency in our formatting we use `pre-commit` to check whether code was formatted properly and the documentation is up to date. On pull requests the CI checks whether all pre-commit hooks were run correctly. +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/#post-commit) +- 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 we use `pre-commit` 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 + +### 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 Info Generator](#deployment-info-generation) 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 + +### Deployment Template + +This repo provides a deployment script template for consistency between scripts and unit tests. For more information on how to use the template, check [here](#deployment-script-template). + +## Deployment + +This repo utilizes versioned deployments. Any changes to a contract should update the version of this specific contract. To deploy a new version of a contract, create a new deployment script in a directory named after the new version of the modified contracts (e.g., `1.0.0`). 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. + +### Deployment Template + +This repo provides a deployment script template for consistency between scripts and unit tests. For more information on how to use the template, check [here](#deployment-script-template). + +### 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/1.0.0/Deploy.s.sol --broadcast --rpc-url --verify +``` + +### Deployment Info Generation + +A JSON and Markdown file can be generated in the `deployments` directory containing various information about the deployment itself as well as past deployments using the following command. To find out more about versioning of contracts within this repo, check [here](CONTRIBUTING.md#versioning). + +```shell +node script/util/extract.js +``` + +As the `chainId`, provide the chainId of the network the contracts were deployed to as a number. The supplied `version` should be the version of the modified contracts and the sub directory the deployment script is located in (e.g., `1.0.0`). The `scriptName` should be the file name of the script used in the deployment (e.g., `Deploy.s.sol`). + +When upgrading a contract, most of the times just the new implementation is deployed and the actual upgrade is triggered by a governance process or a multisig. The script will check whether the implementation of the upgraded contract was updated to the deployed version and if not, it will fail and not generate any files. + +## Deployment Template Script + +This repo provides a deployment script template for consistency between scripts and unit tests. + +TODO more documentation + +## 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: -1. Install [pre-commit](https://pre-commit.com/#post-commit) -2. Run `pre-commit install` +- In case of a MAJOR version + - changelog + - summary of new features + - summary of breaking changes +- 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 +- TODO diff --git a/README.md b/README.md index 7420dd6..2f79c48 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,47 @@ ## Template Repo (Foundry) -This is a brief summary of the project. -What are its core features and what is its role in the bigger ecosystem +[![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) -Want to contribute to this project? -Check [CONTRIBUTING.md](CONTRIBUTING.md) +TODO: summary of the features of the template repo #### Table of Contents -[Setup](#setup) -[Compilation](#compilation) -[Testing](#testing) -[Deployment](#deployment) -[Architecture](#architecture) -[License](#license) +- [Setup](#setup) +- [Deployment](#deployment) +- [Docs](#docs) +- [Contributing](#contributing) -## Compilation +## Setup -```shell -$ forge build -``` +Follow these steps to set up your local environment: -### Testing +- [Install foundry](https://book.getfoundry.sh/getting-started/installation) +- Install dependencies: `forge install` +- Build contracts: `forge build` +- Test contracts: `forge test` -```shell -$ 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/Counter.s.sol:CounterScript --rpc-url --private-key +forge script script/1.0.0/Deploy.s.sol --broadcast --rpc-url --verify ``` -## Architecture +## Docs -Add explanations and graphs to help poeple understand how the contracts of this repo work together. +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) -## License +## Contributing -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +If you want to contribute to this project, please check [CONTRIBUTING.md](CONTRIBUTING.md) first. --- diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..c6b7910 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +# Documentation + +TODO diff --git a/docs/autogen/src/README.md b/docs/autogen/src/README.md index 7420dd6..2f79c48 100644 --- a/docs/autogen/src/README.md +++ b/docs/autogen/src/README.md @@ -1,45 +1,47 @@ ## Template Repo (Foundry) -This is a brief summary of the project. -What are its core features and what is its role in the bigger ecosystem +[![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) -Want to contribute to this project? -Check [CONTRIBUTING.md](CONTRIBUTING.md) +TODO: summary of the features of the template repo #### Table of Contents -[Setup](#setup) -[Compilation](#compilation) -[Testing](#testing) -[Deployment](#deployment) -[Architecture](#architecture) -[License](#license) +- [Setup](#setup) +- [Deployment](#deployment) +- [Docs](#docs) +- [Contributing](#contributing) -## Compilation +## Setup -```shell -$ forge build -``` +Follow these steps to set up your local environment: -### Testing +- [Install foundry](https://book.getfoundry.sh/getting-started/installation) +- Install dependencies: `forge install` +- Build contracts: `forge build` +- Test contracts: `forge test` -```shell -$ 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/Counter.s.sol:CounterScript --rpc-url --private-key +forge script script/1.0.0/Deploy.s.sol --broadcast --rpc-url --verify ``` -## Architecture +## Docs -Add explanations and graphs to help poeple understand how the contracts of this repo work together. +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) -## License +## Contributing -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +If you want to contribute to this project, please check [CONTRIBUTING.md](CONTRIBUTING.md) first. --- diff --git a/foundry.toml b/foundry.toml index e0878bd..21490fe 100644 --- a/foundry.toml +++ b/foundry.toml @@ -27,4 +27,23 @@ max_test_rejects = 999999 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 From 281cb55982700f61643bab202306dcd03ba88262 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Sun, 5 Nov 2023 16:42:09 +0100 Subject: [PATCH 36/86] chore: fix misc --- script/1.0.0/Deploy.s.sol | 6 +++--- script/deployers/DeployCounter.s.sol | 18 ++++++++++++------ script/util/deployer_template | 12 +++++------- test/1.0.0/Counter.t.sol | 4 ++-- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol index f51b61b..3eea3c1 100644 --- a/script/1.0.0/Deploy.s.sol +++ b/script/1.0.0/Deploy.s.sol @@ -11,7 +11,7 @@ import "script/deployers/DeployCounter.s.sol"; contract Deploy is Script, ScriptHelpers, CounterDeployer { using stdJson for string; - address internal proxyAdmin; + ProxyAdmin internal proxyAdmin; Counter internal counter; @@ -20,8 +20,8 @@ contract Deploy is Script, ScriptHelpers, CounterDeployer { string memory input = vm.readFile("script/1.0.0/input.json"); vm.broadcast(deployerPrivateKey); - proxyAdmin = address(new ProxyAdmin(input.readAddress($("ProxyAdmin.initialOwner")))); + proxyAdmin = new ProxyAdmin(input.readAddress($("ProxyAdmin.initialOwner"))); - (counter,,) = deployCounter(address(proxyAdmin), input.readUint($("Counter.number"))); + (counter,) = deployCounter(address(proxyAdmin), input.readUint($("Counter.number"))); } } diff --git a/script/deployers/DeployCounter.s.sol b/script/deployers/DeployCounter.s.sol index e2ff2ec..3bf68b0 100644 --- a/script/deployers/DeployCounter.s.sol +++ b/script/deployers/DeployCounter.s.sol @@ -7,24 +7,30 @@ import "src/Counter.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; abstract contract CounterDeployer is Script { - function deployCounter(address proxyAdmin, uint256 number) internal returns (Counter proxyAsCounter, address proxy, address logic) { + function deployCounter( + address proxyAdmin, + uint256 number + ) internal returns (Counter proxyAsCounter, address logic) { bytes memory initData = abi.encodeCall(Counter.initialize, (number)); return _deployCounter(proxyAdmin, initData); } - function deployCounter_NoInit(address proxyAdmin) internal returns (Counter proxyAsCounter, address proxy, address logic) { + function deployCounter_NoInit( + address proxyAdmin + ) internal returns (Counter proxyAsCounter, address logic) { return _deployCounter(proxyAdmin, ""); } - function _deployCounter(address proxyAdmin, bytes memory initData) private returns (Counter proxyAsCounter, address proxy, address logic) { + function _deployCounter( + address proxyAdmin, + bytes memory initData + ) private returns (Counter proxyAsCounter, address logic) { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); logic = address(new Counter()); - proxy = address(new TransparentUpgradeableProxy(logic, proxyAdmin, initData)); + proxyAsCounter = Counter(address(new TransparentUpgradeableProxy(logic, proxyAdmin, initData))); vm.stopBroadcast(); - - proxyAsCounter = Counter(proxy); } } diff --git a/script/util/deployer_template b/script/util/deployer_template index 3f69a66..5401406 100644 --- a/script/util/deployer_template +++ b/script/util/deployer_template @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.21; +pragma solidity 0.8.22; import "forge-std/Script.sol"; @@ -10,7 +10,7 @@ abstract contract Deployer is Script { function deploy( address proxyAdmin, - ) internal returns ( proxyAs, address proxy, address logic) { + ) internal returns ( proxyAs, address logic) { bytes memory initData = abi.encodeCall(.initialize, ()); return _deploy(proxyAdmin, initData); @@ -18,21 +18,19 @@ abstract contract Deployer is Script { function deploy_NoInit( address proxyAdmin - ) internal returns ( proxyAs, address proxy, address logic) { + ) internal returns ( proxyAs, address logic) { return _deploy(proxyAdmin, ""); } function _deploy( address proxyAdmin, bytes memory initData - ) private returns ( proxyAs, address proxy, address logic) { + ) private returns ( proxyAs, address logic) { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); logic = address(new ()); - proxy = address(new TransparentUpgradeableProxy(logic, proxyAdmin, initData)); + proxyAs = (address(new TransparentUpgradeableProxy(logic, proxyAdmin, initData))); vm.stopBroadcast(); - - proxyAs = (proxy); } } diff --git a/test/1.0.0/Counter.t.sol b/test/1.0.0/Counter.t.sol index 2c9b2e1..0c2615d 100644 --- a/test/1.0.0/Counter.t.sol +++ b/test/1.0.0/Counter.t.sol @@ -6,11 +6,11 @@ import "test/util/TestHelpers.sol"; import "script/1.0.0/Deploy.s.sol"; -abstract contract BeforeScript is TestHelpers, CounterDeployer { +abstract contract BeforeScript is Test, TestHelpers, CounterDeployer { Counter internal counter; function setUp() public { - (counter,,) = deployCounter_NoInit(makeAddr("")); + (counter,) = deployCounter_NoInit(makeAddr("")); } } From a6e4ff859f72f39ace262067c907f5805beebb5d Mon Sep 17 00:00:00 2001 From: Daniel Gretzke Date: Mon, 6 Nov 2023 04:24:33 +0100 Subject: [PATCH 37/86] Update README.md Signed-off-by: Daniel Gretzke --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f79c48..0e07554 100644 --- a/README.md +++ b/README.md @@ -45,4 +45,4 @@ If you want to contribute to this project, please check [CONTRIBUTING.md](CONTRI --- -Copyright (C) 2023 PT Services DMCC +Copyright © 2023 PT Services DMCC From 6f92d1cf90a6016a954f74ad5cecfefc6d6e089d Mon Sep 17 00:00:00 2001 From: Daniel Gretzke Date: Mon, 6 Nov 2023 04:24:50 +0100 Subject: [PATCH 38/86] Update README.md Signed-off-by: Daniel Gretzke --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e07554..2859bb4 100644 --- a/README.md +++ b/README.md @@ -45,4 +45,4 @@ If you want to contribute to this project, please check [CONTRIBUTING.md](CONTRI --- -Copyright © 2023 PT Services DMCC +© 2023 PT Services DMCC From 8ebe47a3fae58b94a9fd0282f33a5423dbba7c16 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:51:49 +0100 Subject: [PATCH 39/86] docs: update `README` --- CONTRIBUTING.md | 36 ++++++++++++++++++++-------- script/deployers/DeployCounter.s.sol | 14 +++-------- script/util/README.md | 5 ---- 3 files changed, 29 insertions(+), 26 deletions(-) delete mode 100644 script/util/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24d70a7..41c6825 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,12 +3,22 @@ - [Install](#install) - [Pre-commit Hooks](#pre-commit-hooks) - [Branching](#branching) -- [Versioning](#versioning) + - [Main](#main) + - [Staging](#staging) + - [Dev](#dev) + - [Feature](#feature) + - [Fix](#fix) - [Code Practices](#code-practices) + - [Interfaces](#interfaces) + - [NatSpec \& Comments](#natspec--comments) +- [Versioning](#versioning) - [Testing](#testing) + - [Deployer Template](#deployer-template) - [Deployment](#deployment) -- [Deployment Info Generation](#deployment-info-generation) -- [Deployment Template Script](#deployment-template-script) + - [Deployer Template](#deployer-template-1) + - [Deployment](#deployment-1) + - [Deployment Info Generation](#deployment-info-generation) +- [Deployer Template Script](#deployer-template-script) - [Releases](#releases) ## Install @@ -72,17 +82,17 @@ Whenever contracts are modified, only the version of the changed contracts shoul ## Testing -### Deployment Template +### Deployer Template -This repo provides a deployment script template for consistency between scripts and unit tests. For more information on how to use the template, check [here](#deployment-script-template). +This repo provides a deployer template for consistency between scripts and unit tests. For more information on how to use the template, check [here](#depler-template-script). ## Deployment This repo utilizes versioned deployments. Any changes to a contract should update the version of this specific contract. To deploy a new version of a contract, create a new deployment script in a directory named after the new version of the modified contracts (e.g., `1.0.0`). 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. -### Deployment Template +### Deployer Template -This repo provides a deployment script template for consistency between scripts and unit tests. For more information on how to use the template, check [here](#deployment-script-template). +This repo provides a deployer template for consistency between scripts and unit tests. For more information on how to use the template, check [here](#deployer-template-script). ### Deployment @@ -116,11 +126,17 @@ As the `chainId`, provide the chainId of the network the contracts were deployed When upgrading a contract, most of the times just the new implementation is deployed and the actual upgrade is triggered by a governance process or a multisig. The script will check whether the implementation of the upgraded contract was updated to the deployed version and if not, it will fail and not generate any files. -## Deployment Template Script +## Deployer Template Script + +This repo provides a deployer template for consistency between scripts and unit tests. -This repo provides a deployment script template for consistency between scripts and unit tests. +A deployer is an `abstract` contract, meant to be inherited in scripts and tests. The deployer consists of two functions: `deploy` and `deploy_NoInit`. It handels the creation, proxification, and initialization of the contract. -TODO more documentation +To generate a new deployer: + +``` +node script/util/generateDeployer.js [params] +``` ## Releases diff --git a/script/deployers/DeployCounter.s.sol b/script/deployers/DeployCounter.s.sol index 3bf68b0..2b9b6e0 100644 --- a/script/deployers/DeployCounter.s.sol +++ b/script/deployers/DeployCounter.s.sol @@ -7,25 +7,17 @@ import "src/Counter.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; abstract contract CounterDeployer is Script { - function deployCounter( - address proxyAdmin, - uint256 number - ) internal returns (Counter proxyAsCounter, address logic) { + function deployCounter(address proxyAdmin, uint256 number) internal returns (Counter proxyAsCounter, address logic) { bytes memory initData = abi.encodeCall(Counter.initialize, (number)); return _deployCounter(proxyAdmin, initData); } - function deployCounter_NoInit( - address proxyAdmin - ) internal returns (Counter proxyAsCounter, address logic) { + function deployCounter_NoInit(address proxyAdmin) internal returns (Counter proxyAsCounter, address logic) { return _deployCounter(proxyAdmin, ""); } - function _deployCounter( - address proxyAdmin, - bytes memory initData - ) private returns (Counter proxyAsCounter, address logic) { + function _deployCounter(address proxyAdmin, bytes memory initData) private returns (Counter proxyAsCounter, address logic) { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); logic = address(new Counter()); diff --git a/script/util/README.md b/script/util/README.md deleted file mode 100644 index da3716e..0000000 --- a/script/util/README.md +++ /dev/null @@ -1,5 +0,0 @@ -### Generate deployer - -``` -node script/util/generateDeployer.js [type arg, ...] -``` From 980577440f44bdc923b9d81fe48ee24852365b53 Mon Sep 17 00:00:00 2001 From: gretzke Date: Mon, 6 Nov 2023 13:13:44 +0100 Subject: [PATCH 40/86] small fixes --- .github/PULL_REQUEST_TEMPLATE.md | 1 + foundry.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3113159..28769d5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,6 +15,7 @@ Please describe the tests that you ran to verify your changes. Provide instructi 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 diff --git a/foundry.toml b/foundry.toml index 21490fe..46e9911 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,7 +3,7 @@ src = "src" out = "out" libs = ["lib"] optimizer = true -optimize_runs = 999999 +optimizer_runs = 999999 via_ir = true solc = "0.8.22" verbosity = 2 From 35e8151af224f3379113640f2076b5e88338e584 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:55:55 +0100 Subject: [PATCH 41/86] feat: storage checker V1 --- .gitignore | 5 +- CONTRIBUTING.md | 2 +- script/util/_storageCheckReporter.js | 133 +++++++++++++++++++++++++++ script/util/check_storage.sh | 118 ++++++++++++++++++++++++ 4 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 script/util/_storageCheckReporter.js create mode 100644 script/util/check_storage.sh diff --git a/.gitignore b/.gitignore index c2a7344..08411c1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,7 @@ lcov.info .vscode broadcast/*/31337 -deployments/**/31337.* \ No newline at end of file +deployments/**/31337.* + +script/util/storage_check_cache +script/util/storage_check_report \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41c6825..8a1804e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,7 +84,7 @@ Whenever contracts are modified, only the version of the changed contracts shoul ### Deployer Template -This repo provides a deployer template for consistency between scripts and unit tests. For more information on how to use the template, check [here](#depler-template-script). +This repo provides a deployer template for consistency between scripts and unit tests. For more information on how to use the template, check [here](#deployer-template-script). ## Deployment diff --git a/script/util/_storageCheckReporter.js b/script/util/_storageCheckReporter.js new file mode 100644 index 0000000..6d27434 --- /dev/null +++ b/script/util/_storageCheckReporter.js @@ -0,0 +1,133 @@ +// PREPARE JSONS + +const oldObject = JSON.parse(process.argv[2]); +const newObject = JSON.parse(process.argv[3]); + +function removeField(jsonArray, fieldToRemove) { + jsonArray.forEach((json) => { + if (json.hasOwnProperty("astId")) { + delete json[fieldToRemove]; + } + }); +} + +removeField(oldObject.storage, "astId"); +removeField(newObject.storage, "astId"); + +function jsonsSame(json1, json2) { + return JSON.stringify(json1) === JSON.stringify(json2); +} + +if (jsonsSame(oldObject, newObject)) { + process.exit(0); +} + +let oldStorage = oldObject.storage; +let newStorage = newObject.storage; + +// COMPARE + +let reportOld = ""; +let reportNew = ""; + +function calcStart(item) { + let slot = parseInt(item.slot); + let offset = item.offset; + let result = slot * 256 + 1 + offset * 8; + if (slot + offset == 0) result = 0; + return result; +} + +function startsSame(item1, item2) { + return calcStart(item1) === calcStart(item2); +} + +function startsBefore(item1, item2) { + return calcStart(item1) < calcStart(item2); +} + +function isSame(item1, item2) { + return ( + item1.label === item2.label && + item1.type === item2.type && + jsonsSame(oldObject.types[item1.type], newObject.types[item2.type]) + ); +} + +let o = 0; +let n = 0; + +while (true) { + const item1 = oldStorage[o]; + const item2 = newStorage[n]; + + if (item1 && item2 && startsSame(item1, item2)) { + printOld(true); + printNew(true, isSame(item1, item2) ? " " : "❗️"); + o++; + n++; + } else if (item1 && (!item2 || startsBefore(item1, item2))) { + printOld(true); + printNew(false, "🗑️"); + o++; + } else if (item2 && (!item1 || startsBefore(item2, item1))) { + printOld(false); + printNew(true, "✨"); + n++; + } else { + break; + } +} + +function printOld(notEmpty) { + if (!notEmpty) reportOld += "\n"; + else reportOld += formatLine(" ", oldStorage[o]); +} + +function printNew(notEmpty, emoji) { + if (!notEmpty) reportNew += emoji + "\n"; + else reportNew += formatLine(emoji, newStorage[n]); +} + +function formatLine(emoji, item) { + emoji = emoji.padEnd(1, " "); + let slot = item.slot; + let offset = item.offset.toString().padEnd(2, " "); + let label = item.label.padEnd(25, " "); + let type = item.type; + + if (item.offset == 0) { + slot = slot.padEnd(8, " "); + offset = ""; + } else { + slot += ": "; + } + + return `${emoji} ${slot}${offset} ${label} ${type}\n`; +} + +// REPORT + +// remove \n from report +reportOld = reportOld.slice(0, -1); +reportNew = reportNew.slice(0, -1); + +const fs = require("fs"); +const path = require("path"); + +const filePath = path.parse(process.argv[4]); +const directoryPath = path.join(".", "storage_check_report", filePath.dir); + +// Create directories recursively +try { + fs.mkdirSync(directoryPath, { recursive: true }); +} catch (err) { + if (err.code !== "EEXIST") throw err; +} + +const reportOldPath = path.join(directoryPath, filePath.name + "-OLD"); +const reportNewPath = path.join(directoryPath, filePath.name + "-NEW"); + +// Write files +fs.writeFileSync(reportOldPath, reportOld); +fs.writeFileSync(reportNewPath, reportNew); diff --git a/script/util/check_storage.sh b/script/util/check_storage.sh new file mode 100644 index 0000000..4f7dd3c --- /dev/null +++ b/script/util/check_storage.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +# CLONE OLD VERSION + +# Check if the commit hash argument is provided +if [ -z "$1" ]; then + echo "Please provide the commit hash or tag as an argument." + exit 1 +fi + +# Define the path to the new subdirectory +old_version="script/util/storage_check_cache" + +# Check if the directory exists, then remove it +exists=0 +if [ -d "$old_version" ]; then + # Check if the current commit matches the target commit hash + prev_dir=$(pwd) + cd "$old_version" + if [ "$(git rev-parse HEAD)" = "$1" ]; then + exists=1 + fi + cd "$prev_dir" + if [ "$exists" -eq 0 ]; then + rm -rf "$old_version" + fi +fi + +if [ "$exists" -eq 0 ]; then + current_dir=$(pwd) + # Clone the current directory to the new subdirectory + git clone "file://$current_dir" "$old_version" + cd "$old_version" + + # Reset to a certain commit + git reset --hard "$1" + + forge install + + cd "$current_dir" +fi + +# ======================================================================== + +# GET FILE NAMES + +# Define a function to find .sol files +find_sol_files() { + local dir="$1" + local array_name="$2" + local filesWithPath=() + + while IFS= read -r -d $'\0' file; do + # Append the file name to the array + filesWithPath+=("$file") + done < <(find "$dir" -type f -name "*.sol" -print0) + + # Assign the array to the variable name specified by the second argument + eval "$array_name"='("${filesWithPath[@]}")' +} + +# Specify the directory where you want to search for .sol files +search_directory="src" + +# Declare empty arrays to store the file names +filesWithPath_old=() +filesWithPath_new=() + +current_dir=$(pwd) + +# Call the function for the old version directory +cd $old_version +find_sol_files "$search_directory" "filesWithPath_old" + +# Call the function for the new version directory +cd "$current_dir" +find_sol_files "$search_directory" "filesWithPath_new" + +# ======================================================================== + +# REPORT DELETED ONES + +if [ -d "script/util/storage_check_report" ]; then + rm -rf "script/util/storage_check_report" +fi + +differences=() +for item in "${filesWithPath_old[@]}"; do + skip= + for itemB in "${filesWithPath_new[@]}"; do + [[ $item == $itemB ]] && { skip=1; break; } + done + [[ -n $skip ]] || differences+=("$item") +done + +if [ ${#differences[@]} -gt 0 ]; then + mkdir -p "script/util/storage_check_report" + printf "%s\n" "${differences[@]}" > "script/util/storage_check_report.removed" +fi + +# ======================================================================== + +# COMPARE STORAGE LAYOUTS + +# Loop through each item in the array +for line in "${filesWithPath_old[@]}"; do + # Check if the line is not empty + if [ -n "$line" ] && [[ ! " ${differences[@]} " =~ " ${line} " ]]; then + # Run the 'forge inspect' command with the current item from the array + formated_name=${line}:$(basename "${line%.*}") + cd "$old_version" + output_old=$(forge inspect $formated_name storage) + cd "$current_dir" + output_new=$(forge inspect $formated_name storage) + + node ./script/util/storageCheckReporter.js "$output_old" "$output_new" ${line} + fi +done \ No newline at end of file From d98b6a1fa9c4080ea025493ca2699adac33f6f14 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:00:39 +0100 Subject: [PATCH 42/86] fix: correct path --- script/util/_storageCheckReporter.js | 2 +- script/util/check_storage.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/util/_storageCheckReporter.js b/script/util/_storageCheckReporter.js index 6d27434..d012e10 100644 --- a/script/util/_storageCheckReporter.js +++ b/script/util/_storageCheckReporter.js @@ -116,7 +116,7 @@ const fs = require("fs"); const path = require("path"); const filePath = path.parse(process.argv[4]); -const directoryPath = path.join(".", "storage_check_report", filePath.dir); +const directoryPath = path.join("script/util/storage_check_report", filePath.dir); // Create directories recursively try { diff --git a/script/util/check_storage.sh b/script/util/check_storage.sh index 4f7dd3c..0b9da2a 100644 --- a/script/util/check_storage.sh +++ b/script/util/check_storage.sh @@ -113,6 +113,6 @@ for line in "${filesWithPath_old[@]}"; do cd "$current_dir" output_new=$(forge inspect $formated_name storage) - node ./script/util/storageCheckReporter.js "$output_old" "$output_new" ${line} + node ./script/util/_storageCheckReporter.js "$output_old" "$output_new" ${line} fi done \ No newline at end of file From d51791f51269451e3e82e3b5f05689af43a0e291 Mon Sep 17 00:00:00 2001 From: gretzke Date: Wed, 8 Nov 2023 16:15:56 +0100 Subject: [PATCH 43/86] check whether PR into main is from staging --- .github/workflows/pr-check.yaml | 20 ++++++++++++++++++++ .github/workflows/pre-commit.yaml | 2 +- .github/workflows/test.yaml | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/pr-check.yaml diff --git a/.github/workflows/pr-check.yaml b/.github/workflows/pr-check.yaml new file mode 100644 index 0000000..fafbcd7 --- /dev/null +++ b/.github/workflows/pr-check.yaml @@ -0,0 +1,20 @@ +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: 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 index 1ded477..cc46bd4 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -3,7 +3,7 @@ name: pre-commit on: pull_request: - branches: [master, staging, dev, feat/**, fix/**] + branches: [main, master, staging, dev, feat/**, fix/**] jobs: pre-commit: diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 78a2ed5..42c3cda 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,7 +2,7 @@ name: test on: pull_request: - branches: [master, staging, dev, feat/**, fix/**] + branches: [main, master, staging, dev, feat/**, fix/**] env: FOUNDRY_PROFILE: ci From 1d24fddf952f3940f37a2331b3afe1fedbce58b3 Mon Sep 17 00:00:00 2001 From: gretzke Date: Wed, 8 Nov 2023 16:58:33 +0100 Subject: [PATCH 44/86] echo branches --- .github/workflows/pr-check.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pr-check.yaml b/.github/workflows/pr-check.yaml index fafbcd7..78af9e9 100644 --- a/.github/workflows/pr-check.yaml +++ b/.github/workflows/pr-check.yaml @@ -12,6 +12,10 @@ jobs: 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 From f86fd2a9b87ef3f77c0294e2c9902d89cf2603fb Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 10 Nov 2023 02:27:03 +0100 Subject: [PATCH 45/86] Add code style guideline --- CONTRIBUTING.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a1804e..9d987ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,6 +9,7 @@ - [Feature](#feature) - [Fix](#fix) - [Code Practices](#code-practices) + - [Code Style](#code-style) - [Interfaces](#interfaces) - [NatSpec \& Comments](#natspec--comments) - [Versioning](#versioning) @@ -66,6 +67,47 @@ Any bug fixes should be developed on a separate branch. The naming convention fo ## 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. From 1a4b53a526b816316ff3612bc2ae43c41e270fe2 Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 10 Nov 2023 02:28:01 +0100 Subject: [PATCH 46/86] forge install: solady v0.0.138 --- .gitmodules | 3 +++ lib/solady | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/solady diff --git a/.gitmodules b/.gitmodules index c90cdcb..15e3ba1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/solady"] + path = lib/solady + url = https://github.com/vectorized/solady diff --git a/lib/solady b/lib/solady new file mode 160000 index 0000000..cde0a5f --- /dev/null +++ b/lib/solady @@ -0,0 +1 @@ +Subproject commit cde0a5fb594da8655ba6bfcdc2e40a7c870c0cc0 From 3982c56ab04e38791e66d63df94374b37ea19e75 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Wed, 22 Nov 2023 12:21:34 +0100 Subject: [PATCH 47/86] fix: make compatible with OZ 5.0 --- CONTRIBUTING.md | 4 ++-- script/1.0.0/Deploy.s.sol | 12 +---------- script/1.0.0/input.json | 2 +- script/deployers/DeployCounter.s.sol | 24 ++++++++++++++------- script/util/deployer_template | 32 ++++++++++++++-------------- script/util/generateDeployer.js | 9 +++++--- test/1.0.0/Counter.t.sol | 6 ++---- 7 files changed, 44 insertions(+), 45 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d987ba..2500aff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -172,12 +172,12 @@ When upgrading a contract, most of the times just the new implementation is depl This repo provides a deployer template for consistency between scripts and unit tests. -A deployer is an `abstract` contract, meant to be inherited in scripts and tests. The deployer consists of two functions: `deploy` and `deploy_NoInit`. It handels the creation, proxification, and initialization of the contract. +A deployer is an abstract contract, meant to be inherited in scripts and tests. A deployer provides type-checks and handles the creation, proxification, and initialization of the contract. It consists of two functions: `deploy` and `deploy_NoInit`. To generate a new deployer: ``` -node script/util/generateDeployer.js [params] +node script/util/generateDeployer.js [init params] ``` ## Releases diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol index 3eea3c1..dc69d26 100644 --- a/script/1.0.0/Deploy.s.sol +++ b/script/1.0.0/Deploy.s.sol @@ -4,24 +4,14 @@ pragma solidity 0.8.22; import "forge-std/Script.sol"; import "script/util/ScriptHelpers.sol"; -import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; - import "script/deployers/DeployCounter.s.sol"; contract Deploy is Script, ScriptHelpers, CounterDeployer { using stdJson for string; - ProxyAdmin internal proxyAdmin; - - Counter internal counter; - function run() public { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); string memory input = vm.readFile("script/1.0.0/input.json"); - vm.broadcast(deployerPrivateKey); - proxyAdmin = new ProxyAdmin(input.readAddress($("ProxyAdmin.initialOwner"))); - - (counter,) = deployCounter(address(proxyAdmin), input.readUint($("Counter.number"))); + deployCounter(input.readAddress($("ProxyAdmin.initialOwner")), input.readUint($("Counter.number"))); } } diff --git a/script/1.0.0/input.json b/script/1.0.0/input.json index 5e2102b..8e5f492 100644 --- a/script/1.0.0/input.json +++ b/script/1.0.0/input.json @@ -5,7 +5,7 @@ "31337": { "ProxyAdmin": { - "initialOwner": "0x48789ECd4317eCc8dBdB1d06EF39F01c5862a941" + "initialOwner": "0x356f394005D3316ad54d8f22b40D02Cd539A4a3C" }, "Counter": { "number": 10 diff --git a/script/deployers/DeployCounter.s.sol b/script/deployers/DeployCounter.s.sol index 2b9b6e0..715fbcb 100644 --- a/script/deployers/DeployCounter.s.sol +++ b/script/deployers/DeployCounter.s.sol @@ -4,25 +4,33 @@ pragma solidity 0.8.22; import "forge-std/Script.sol"; import "src/Counter.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.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 { - function deployCounter(address proxyAdmin, uint256 number) internal returns (Counter proxyAsCounter, address logic) { + Counter internal counter; + ProxyAdmin internal counterProxyAdmin; + address internal counterLogic; + + function deployCounter(address proxyAdminOwner, uint256 number) internal { bytes memory initData = abi.encodeCall(Counter.initialize, (number)); - return _deployCounter(proxyAdmin, initData); + _deployCounter(proxyAdminOwner, initData); } - function deployCounter_NoInit(address proxyAdmin) internal returns (Counter proxyAsCounter, address logic) { - return _deployCounter(proxyAdmin, ""); + function deployCounter_NoInit(address proxyAdminOwner) internal { + _deployCounter(proxyAdminOwner, ""); } - function _deployCounter(address proxyAdmin, bytes memory initData) private returns (Counter proxyAsCounter, address logic) { + function _deployCounter(address proxyAdminOwner, bytes memory initData) private { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - logic = address(new Counter()); - proxyAsCounter = Counter(address(new TransparentUpgradeableProxy(logic, proxyAdmin, initData))); + counterLogic = address(new Counter()); + counter = Counter(address(new TransparentUpgradeableProxy(counterLogic, proxyAdminOwner, initData))); vm.stopBroadcast(); + + counterProxyAdmin = + ProxyAdmin(address(uint160(uint256(vm.load(address(counter), hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"))))); } } diff --git a/script/util/deployer_template b/script/util/deployer_template index 5401406..1948357 100644 --- a/script/util/deployer_template +++ b/script/util/deployer_template @@ -4,33 +4,33 @@ pragma solidity 0.8.22; import "forge-std/Script.sol"; import ""; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; abstract contract Deployer is Script { - function deploy( - address proxyAdmin, - - ) internal returns ( proxyAs, address logic) { + internal ; + ProxyAdmin internal ProxyAdmin; + address internal Logic; + + function deploy(address proxyAdminOwner, ) internal { bytes memory initData = abi.encodeCall(.initialize, ()); - return _deploy(proxyAdmin, initData); + _deploy(proxyAdminOwner, initData); } - function deploy_NoInit( - address proxyAdmin - ) internal returns ( proxyAs, address logic) { - return _deploy(proxyAdmin, ""); + function deploy_NoInit(address proxyAdminOwner) internal { + _deploy(proxyAdminOwner, ""); } - function _deploy( - address proxyAdmin, - bytes memory initData - ) private returns ( proxyAs, address logic) { + function _deploy(address proxyAdminOwner, bytes memory initData) private { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - logic = address(new ()); - proxyAs = (address(new TransparentUpgradeableProxy(logic, proxyAdmin, initData))); + Logic = address(new ()); + = (address(new TransparentUpgradeableProxy(Logic, proxyAdminOwner, initData))); vm.stopBroadcast(); + + ProxyAdmin = + ProxyAdmin(address(uint160(uint256(vm.load(address(), hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"))))); } } diff --git a/script/util/generateDeployer.js b/script/util/generateDeployer.js index 45a71c8..896286c 100644 --- a/script/util/generateDeployer.js +++ b/script/util/generateDeployer.js @@ -9,11 +9,16 @@ const replaceInFile = (filePath, newFilePath, replacementExample, replacementArg } let regexExample = new RegExp("", "g"); + let regexExampleVar = new RegExp("", "g"); let regexArgs = new RegExp("", "g"); let regexArgsNames = new RegExp("", "g"); let regexPathToExample = new RegExp("", "g"); let updatedData = data.replace(regexExample, replacementExample); + updatedData = updatedData.replace( + regexExampleVar, + replacementExample.charAt(0).toLowerCase() + replacementExample.slice(1) + ); updatedData = updatedData.replace(regexArgs, replacementArgs); updatedData = updatedData.replace(regexArgsNames, processString(replacementArgs)); updatedData = updatedData.replace(regexPathToExample, replacementPathToExample); @@ -82,9 +87,7 @@ if (fileNameParts.length > 1) { } if (!replacementPathToExample || !newFilePath) { - console.error( - "Usage: node script/util/generateDeployer.js [type arg, ...] ", - ); + console.error("Usage: node script/util/generateDeployer.js [init params] "); process.exit(1); } diff --git a/test/1.0.0/Counter.t.sol b/test/1.0.0/Counter.t.sol index 0c2615d..b9e9db8 100644 --- a/test/1.0.0/Counter.t.sol +++ b/test/1.0.0/Counter.t.sol @@ -7,10 +7,8 @@ import "test/util/TestHelpers.sol"; import "script/1.0.0/Deploy.s.sol"; abstract contract BeforeScript is Test, TestHelpers, CounterDeployer { - Counter internal counter; - - function setUp() public { - (counter,) = deployCounter_NoInit(makeAddr("")); + function setUp() public virtual { + deployCounter_NoInit(makeAddr("")); } } From 2b8045b9b893fb8cd1a91e6f01c19e5f68ee8b4e Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Wed, 22 Nov 2023 12:28:31 +0100 Subject: [PATCH 48/86] rm: storage checker program --- script/util/_storageCheckReporter.js | 133 --------------------------- script/util/check_storage.sh | 118 ------------------------ 2 files changed, 251 deletions(-) delete mode 100644 script/util/_storageCheckReporter.js delete mode 100644 script/util/check_storage.sh diff --git a/script/util/_storageCheckReporter.js b/script/util/_storageCheckReporter.js deleted file mode 100644 index d012e10..0000000 --- a/script/util/_storageCheckReporter.js +++ /dev/null @@ -1,133 +0,0 @@ -// PREPARE JSONS - -const oldObject = JSON.parse(process.argv[2]); -const newObject = JSON.parse(process.argv[3]); - -function removeField(jsonArray, fieldToRemove) { - jsonArray.forEach((json) => { - if (json.hasOwnProperty("astId")) { - delete json[fieldToRemove]; - } - }); -} - -removeField(oldObject.storage, "astId"); -removeField(newObject.storage, "astId"); - -function jsonsSame(json1, json2) { - return JSON.stringify(json1) === JSON.stringify(json2); -} - -if (jsonsSame(oldObject, newObject)) { - process.exit(0); -} - -let oldStorage = oldObject.storage; -let newStorage = newObject.storage; - -// COMPARE - -let reportOld = ""; -let reportNew = ""; - -function calcStart(item) { - let slot = parseInt(item.slot); - let offset = item.offset; - let result = slot * 256 + 1 + offset * 8; - if (slot + offset == 0) result = 0; - return result; -} - -function startsSame(item1, item2) { - return calcStart(item1) === calcStart(item2); -} - -function startsBefore(item1, item2) { - return calcStart(item1) < calcStart(item2); -} - -function isSame(item1, item2) { - return ( - item1.label === item2.label && - item1.type === item2.type && - jsonsSame(oldObject.types[item1.type], newObject.types[item2.type]) - ); -} - -let o = 0; -let n = 0; - -while (true) { - const item1 = oldStorage[o]; - const item2 = newStorage[n]; - - if (item1 && item2 && startsSame(item1, item2)) { - printOld(true); - printNew(true, isSame(item1, item2) ? " " : "❗️"); - o++; - n++; - } else if (item1 && (!item2 || startsBefore(item1, item2))) { - printOld(true); - printNew(false, "🗑️"); - o++; - } else if (item2 && (!item1 || startsBefore(item2, item1))) { - printOld(false); - printNew(true, "✨"); - n++; - } else { - break; - } -} - -function printOld(notEmpty) { - if (!notEmpty) reportOld += "\n"; - else reportOld += formatLine(" ", oldStorage[o]); -} - -function printNew(notEmpty, emoji) { - if (!notEmpty) reportNew += emoji + "\n"; - else reportNew += formatLine(emoji, newStorage[n]); -} - -function formatLine(emoji, item) { - emoji = emoji.padEnd(1, " "); - let slot = item.slot; - let offset = item.offset.toString().padEnd(2, " "); - let label = item.label.padEnd(25, " "); - let type = item.type; - - if (item.offset == 0) { - slot = slot.padEnd(8, " "); - offset = ""; - } else { - slot += ": "; - } - - return `${emoji} ${slot}${offset} ${label} ${type}\n`; -} - -// REPORT - -// remove \n from report -reportOld = reportOld.slice(0, -1); -reportNew = reportNew.slice(0, -1); - -const fs = require("fs"); -const path = require("path"); - -const filePath = path.parse(process.argv[4]); -const directoryPath = path.join("script/util/storage_check_report", filePath.dir); - -// Create directories recursively -try { - fs.mkdirSync(directoryPath, { recursive: true }); -} catch (err) { - if (err.code !== "EEXIST") throw err; -} - -const reportOldPath = path.join(directoryPath, filePath.name + "-OLD"); -const reportNewPath = path.join(directoryPath, filePath.name + "-NEW"); - -// Write files -fs.writeFileSync(reportOldPath, reportOld); -fs.writeFileSync(reportNewPath, reportNew); diff --git a/script/util/check_storage.sh b/script/util/check_storage.sh deleted file mode 100644 index 0b9da2a..0000000 --- a/script/util/check_storage.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/bash - -# CLONE OLD VERSION - -# Check if the commit hash argument is provided -if [ -z "$1" ]; then - echo "Please provide the commit hash or tag as an argument." - exit 1 -fi - -# Define the path to the new subdirectory -old_version="script/util/storage_check_cache" - -# Check if the directory exists, then remove it -exists=0 -if [ -d "$old_version" ]; then - # Check if the current commit matches the target commit hash - prev_dir=$(pwd) - cd "$old_version" - if [ "$(git rev-parse HEAD)" = "$1" ]; then - exists=1 - fi - cd "$prev_dir" - if [ "$exists" -eq 0 ]; then - rm -rf "$old_version" - fi -fi - -if [ "$exists" -eq 0 ]; then - current_dir=$(pwd) - # Clone the current directory to the new subdirectory - git clone "file://$current_dir" "$old_version" - cd "$old_version" - - # Reset to a certain commit - git reset --hard "$1" - - forge install - - cd "$current_dir" -fi - -# ======================================================================== - -# GET FILE NAMES - -# Define a function to find .sol files -find_sol_files() { - local dir="$1" - local array_name="$2" - local filesWithPath=() - - while IFS= read -r -d $'\0' file; do - # Append the file name to the array - filesWithPath+=("$file") - done < <(find "$dir" -type f -name "*.sol" -print0) - - # Assign the array to the variable name specified by the second argument - eval "$array_name"='("${filesWithPath[@]}")' -} - -# Specify the directory where you want to search for .sol files -search_directory="src" - -# Declare empty arrays to store the file names -filesWithPath_old=() -filesWithPath_new=() - -current_dir=$(pwd) - -# Call the function for the old version directory -cd $old_version -find_sol_files "$search_directory" "filesWithPath_old" - -# Call the function for the new version directory -cd "$current_dir" -find_sol_files "$search_directory" "filesWithPath_new" - -# ======================================================================== - -# REPORT DELETED ONES - -if [ -d "script/util/storage_check_report" ]; then - rm -rf "script/util/storage_check_report" -fi - -differences=() -for item in "${filesWithPath_old[@]}"; do - skip= - for itemB in "${filesWithPath_new[@]}"; do - [[ $item == $itemB ]] && { skip=1; break; } - done - [[ -n $skip ]] || differences+=("$item") -done - -if [ ${#differences[@]} -gt 0 ]; then - mkdir -p "script/util/storage_check_report" - printf "%s\n" "${differences[@]}" > "script/util/storage_check_report.removed" -fi - -# ======================================================================== - -# COMPARE STORAGE LAYOUTS - -# Loop through each item in the array -for line in "${filesWithPath_old[@]}"; do - # Check if the line is not empty - if [ -n "$line" ] && [[ ! " ${differences[@]} " =~ " ${line} " ]]; then - # Run the 'forge inspect' command with the current item from the array - formated_name=${line}:$(basename "${line%.*}") - cd "$old_version" - output_old=$(forge inspect $formated_name storage) - cd "$current_dir" - output_new=$(forge inspect $formated_name storage) - - node ./script/util/_storageCheckReporter.js "$output_old" "$output_new" ${line} - fi -done \ No newline at end of file From 07212e58a1c8ce3eec4ee03190dd90ec619daa01 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Wed, 22 Nov 2023 12:28:35 +0100 Subject: [PATCH 49/86] forge install: storage-layout-checker --- .gitmodules | 3 +++ lib/storage-layout-checker | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/storage-layout-checker diff --git a/.gitmodules b/.gitmodules index 15e3ba1..656bdc3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/solady"] path = lib/solady url = https://github.com/vectorized/solady +[submodule "lib/storage-layout-checker"] + path = lib/storage-layout-checker + url = https://github.com/0xPolygon/storage-layout-checker diff --git a/lib/storage-layout-checker b/lib/storage-layout-checker new file mode 160000 index 0000000..1503f4c --- /dev/null +++ b/lib/storage-layout-checker @@ -0,0 +1 @@ +Subproject commit 1503f4c782bb17f9a628c8761cadcf1dbd7551b9 From fc8464f7a279d71da2838976c5be77e0e9e7e795 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:11:51 +0100 Subject: [PATCH 50/86] revert: forge install --- .gitmodules | 3 --- lib/storage-layout-checker | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/storage-layout-checker diff --git a/.gitmodules b/.gitmodules index 656bdc3..15e3ba1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,3 @@ [submodule "lib/solady"] path = lib/solady url = https://github.com/vectorized/solady -[submodule "lib/storage-layout-checker"] - path = lib/storage-layout-checker - url = https://github.com/0xPolygon/storage-layout-checker diff --git a/lib/storage-layout-checker b/lib/storage-layout-checker deleted file mode 160000 index 1503f4c..0000000 --- a/lib/storage-layout-checker +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1503f4c782bb17f9a628c8761cadcf1dbd7551b9 From 2230b5b87c28cdc71141795e1b2d80b54fea52bc Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Fri, 24 Nov 2023 19:14:46 +0100 Subject: [PATCH 51/86] feat: add extractor.js --- script/util/extractor.js | 304 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 script/util/extractor.js diff --git a/script/util/extractor.js b/script/util/extractor.js new file mode 100644 index 0000000..91e1984 --- /dev/null +++ b/script/util/extractor.js @@ -0,0 +1,304 @@ +const fs = require("fs"); +const path = require("path"); +const { exec } = require("child_process"); + +// ========== ABOUT ========== + +/* + +Given the latest broadcast file, +Update the deployment history and latest data for the chain. + +Notes: + +- The script assumes that the proxy is created immediately after the implementation contract. +- Only TransparentUpgradeableProxy by OpenZeppelin is supported. + +*/ + +// ========== INPUTS ========== + +const scriptName = process.argv[2]; // Replace with the appropriate index for scriptName +const chainId = process.argv[3]; // Replace with the appropriate index for chainId + +async function main() { + // ========== APPEND TO HISTORY ========== + + // Used for getVersion() helper + const config = JSON.parse(fs.readFileSync(path.join(__dirname, "../config.json"), "utf-8")); + const rpcUrl = config.defaultRpc[chainId] || process.env.RPC_URL || "http://127.0.0.1:8545"; + + const filePath = path.join(__dirname, `../../broadcast/${scriptName}/${chainId}/run-latest.json`); + // Load JSON data from file + const jsonData = JSON.parse(fs.readFileSync(filePath, "utf-8")); + + const recordFilePath = path.join(__dirname, `../../deployments/json/${chainId}.json`); + + let recordData; + + try { + // Try to read the existing record.json file + recordData = JSON.parse(fs.readFileSync(recordFilePath, "utf8")); + } catch (error) { + // If the file doesn't exist, create a new object + recordData = { + chainId: chainId, + latest: {}, + history: [], + }; + } + + // If history is not empty, check if the latest entry is the same as the current commit + if (recordData.history.length > 0) { + const latestEntry = recordData.history[recordData.history.length - 1]; + if (latestEntry.commitHash === jsonData.commit) { + // If the latest entry is the same as the current commit, exit the script + console.log(`Commit ${jsonData.commit} already processed. Aborted.`); + return; + } + } + + //TODO: Uncomment (see function definition). + //prepareArtifacts(); + + // Filter transactions with "transactionType" as "CREATE" + const createTransactions = jsonData.transactions.filter((transaction) => transaction.transactionType === "CREATE"); + + // Initialize an object to store the items + const deploymentItems = {}; + + // Iterate through the filtered transactions + for (let i = 0; i < createTransactions.length; i++) { + const currentTransaction = createTransactions[i]; + + // Inside the loop where you process transactions + if (i < createTransactions.length - 1 && createTransactions[i + 1].contractName === "TransparentUpgradeableProxy") { + const upgradeableItem = { + implementation: currentTransaction.contractAddress, + proxyAdmin: createTransactions[i + 1].additionalContracts[0].address, + address: createTransactions[i + 1].contractAddress, + proxy: true, + version: (await getVersion(createTransactions[i + 1].contractAddress, rpcUrl)).version, + proxyType: "TransparentUpgradeableProxy", + deploymentTxn: createTransactions[i + 1].hash, + input: extractConstructorAndInitializeInputs(getABI(currentTransaction.contractName), { + constructor: currentTransaction.arguments, + // dump initData + initialize: createTransactions[i + 1].arguments[2], + }), + }; + + deploymentItems[currentTransaction.contractName] = upgradeableItem; + + // Skip the next iteration to avoid processing the proxy again + i++; + } else { + const maybeNonUpgradeableItem = { + address: currentTransaction.contractAddress, + proxy: false, + version: (await getVersion(currentTransaction.contractAddress, rpcUrl)).version, + deploymentTxn: currentTransaction.hash, + input: extractConstructorAndInitializeInputs(getABI(currentTransaction.contractName), { + constructor: currentTransaction.arguments, + }), + }; + + // Check if a contract with the same name exists in the latest data + if (recordData.latest.hasOwnProperty(currentTransaction.contractName)) { + const matchedItem = recordData.latest[currentTransaction.contractName]; + + // Check if the latest item has a proxy + if (matchedItem.proxy) { + // Update the current object with data from the latest item + maybeNonUpgradeableItem.implementation = currentTransaction.contractAddress; + maybeNonUpgradeableItem.proxyAdmin = matchedItem.proxyAdmin; + maybeNonUpgradeableItem.address = matchedItem.address; + maybeNonUpgradeableItem.proxy = true; + maybeNonUpgradeableItem.proxyType = matchedItem.proxyType; + maybeNonUpgradeableItem.deploymentTxn = matchedItem.deploymentTxn; + } + + // Define the order of fields + const fieldOrder = [ + "implementation", + "proxyAdmin", + "address", + "proxy", + "version", + "proxyType", + "deploymentTxn", + "input", + ]; + + // Create the object with the desired field order + const upgradedItem = {}; + for (const fieldName of fieldOrder) { + upgradedItem[fieldName] = maybeNonUpgradeableItem[fieldName]; + } + + deploymentItems[currentTransaction.contractName] = upgradedItem; + } else { + deploymentItems[currentTransaction.contractName] = maybeNonUpgradeableItem; + } + } + } + + // Now you have named items in the `deploymentItems` object + + // Create a new JSON with the desired format + const historyItem = { contracts: deploymentItems, timestamp: jsonData.timestamp, commitHash: jsonData.commit }; + + // Append the historyItem to the history array + recordData.history.push(historyItem); + + // ========== UPDATE LATEST ========== + + for (const contractName in historyItem.contracts) { + if (historyItem.contracts.hasOwnProperty(contractName)) { + const contractData = historyItem.contracts[contractName]; + + // Check if an item with the same contract name exists in the latest object + if (recordData.latest[contractName]) { + // Update the existing entry in the latest object + recordData.latest[contractName] = { + ...recordData.latest[contractName], // Preserve existing data + ...contractData, // Update with new data from historyItem + timestamp: historyItem.timestamp, // Add timestamp + commitHash: historyItem.commitHash, // Add commitHash + }; + // Exclude the 'input' field from the latest object + delete recordData.latest[contractName].input; + } else { + // Add a new entry to the latest object + recordData.latest[contractName] = { + ...contractData, // Add new data from historyItem + timestamp: historyItem.timestamp, // Add timestamp + commitHash: historyItem.commitHash, // Add commitHash + }; + // Exclude the 'input' field from the latest object + delete recordData.latest[contractName].input; + } + } + } + + // ========== SAVE CHANGES ========== + + // Ensure the directory exists before writing the file + const directoryPath = path.dirname(recordFilePath); + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath, { recursive: true }); // Create the directory if it doesn't exist + } + + // Write the updated object back to the record.json file + fs.writeFileSync(recordFilePath, JSON.stringify(recordData, null, 2), "utf8"); +} + +console.log("Extracting..."); + +main(); + +console.log(`Extraction complete.`); + +// ========== HELPERS ========== + +// IN: contract address and RPC URL +// OUT: contract version (.version) +async function getVersion(contractAddress, rpcUrl) { + const hexToAscii = (str) => hexToUtf8(str).replace(/[\u0000-\u0008,\u000A-\u001F,\u007F-\u00A0]+/g, ""); // remove non-ascii chars + const hexToUtf8 = (str) => new TextDecoder().decode(hexToUint8Array(str)); // note: TextDecoder present in node, update if not using nodejs + function hexToUint8Array(hex) { + const value = hex.toLowerCase().startsWith("0x") ? hex.slice(2) : hex; + return new Uint8Array(Math.ceil(value.length / 2)).map((_, i) => parseInt(value.substring(i * 2, i * 2 + 2), 16)); + } + + try { + const res = await ( + await fetch(rpcUrl, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: Date.now(), + method: "eth_call", + params: [{ to: contractAddress, data: "0x54fd4d50" }, "latest"], // version()(string) + }), + }) + ).json(); + if (res.error) throw new Error(res.error.message); + return { version: hexToAscii(res.result)?.trim() || res.result }; + } catch (e) { + if (e.message === "execution reverted") return { version: undefined }; // contract does not implement getVersion() + if (e.message.includes("fetch is not defined")) { + console.warn("use node 18+"); + } + throw e; + } +} + +// IN: contract ABI and input (constructor and initialize) values +// OUT: mappings of input names to values +// Note: If .initialize is a string, .initData will be dumped. +function extractConstructorAndInitializeInputs(abi, inputData) { + const inputMapping = {}; + + // Find the constructor in the ABI by type "constructor" + const constructorFunc = abi.find((func) => func.type === "constructor"); + + // Find the initialize function in the ABI by name "initialize" + const initializeFunc = abi.find((func) => func.type === "function" && func.name === "initialize"); + + if (constructorFunc && inputData.constructor) { + // Match constructor inputs with their names + inputMapping.constructor = {}; + constructorFunc.inputs.forEach((input, index) => { + inputMapping.constructor[input.name] = inputData.constructor[index]; + }); + } + + if (initializeFunc) { + if (typeof inputData.initialize === "string") { + // If initialize is a string, create an "initData" field + inputMapping.initData = inputData.initialize; + } else if (inputData.initialize) { + // Match initialize inputs with their names + inputMapping.initialize = {}; + initializeFunc.inputs.forEach((input, index) => { + inputMapping.initialize[input.name] = inputData.initialize[index]; + }); + } + } + + return inputMapping; +} + +// IN: contract name +// OUT: contract ABI +function getABI(contractName) { + const filePath = path.join(__dirname, `../../out/${contractName}.sol/${contractName}.json`); + const fileData = fs.readFileSync(filePath, "utf8"); + const abi = JSON.parse(fileData).abi; + return abi; +} + +// Note: Makes sure contract artifacts are up-to-date. +function prepareArtifacts() { + //TODO: Make JS wait for these commands to finish before continuing. + + console.log(`Preparing artifacts...`); + + exec("forge clean", (error) => { + if (error) { + console.error(`Failed to forge clean.`); + return; + } + }); + + exec("forge build", (error) => { + if (error) { + console.error(`Failed to forge build.`); + return; + } + }); + + console.log(`Artifacts ready. Continuing...`); +} From b20ef6c59132498f38c3a13778d45dcbabe76619 Mon Sep 17 00:00:00 2001 From: gretzke Date: Sat, 25 Nov 2023 03:29:11 +0100 Subject: [PATCH 52/86] wait for artifacts to build --- script/util/extractor.js | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/script/util/extractor.js b/script/util/extractor.js index 91e1984..ea83fea 100644 --- a/script/util/extractor.js +++ b/script/util/extractor.js @@ -1,6 +1,8 @@ const fs = require("fs"); const path = require("path"); const { exec } = require("child_process"); +const Util = require("util"); +const asyncExec = Util.promisify(exec); // ========== ABOUT ========== @@ -22,6 +24,8 @@ const scriptName = process.argv[2]; // Replace with the appropriate index for sc const chainId = process.argv[3]; // Replace with the appropriate index for chainId async function main() { + console.log("Extracting..."); + // ========== APPEND TO HISTORY ========== // Used for getVersion() helper @@ -58,8 +62,7 @@ async function main() { } } - //TODO: Uncomment (see function definition). - //prepareArtifacts(); + await prepareArtifacts(); // Filter transactions with "transactionType" as "CREATE" const createTransactions = jsonData.transactions.filter((transaction) => transaction.transactionType === "CREATE"); @@ -191,14 +194,11 @@ async function main() { // Write the updated object back to the record.json file fs.writeFileSync(recordFilePath, JSON.stringify(recordData, null, 2), "utf8"); + console.log(`Extraction complete.`); } -console.log("Extracting..."); - main(); -console.log(`Extraction complete.`); - // ========== HELPERS ========== // IN: contract address and RPC URL @@ -281,24 +281,11 @@ function getABI(contractName) { } // Note: Makes sure contract artifacts are up-to-date. -function prepareArtifacts() { - //TODO: Make JS wait for these commands to finish before continuing. - +async function prepareArtifacts() { console.log(`Preparing artifacts...`); - exec("forge clean", (error) => { - if (error) { - console.error(`Failed to forge clean.`); - return; - } - }); - - exec("forge build", (error) => { - if (error) { - console.error(`Failed to forge build.`); - return; - } - }); + await asyncExec("forge clean"); + await asyncExec("forge build"); console.log(`Artifacts ready. Continuing...`); } From eee3aecda35e6320c32be51d8e62382449ad2327 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:08:35 +0530 Subject: [PATCH 53/86] chore: force trailing comma --- .prettierrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierrc b/.prettierrc index 0c4d091..29b171c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,6 +4,7 @@ "useTabs": false, "singleQuote": false, "bracketSpacing": true, + "trailingComma": "all", "overrides": [ { "files": "*.sol", From 13bada33c14c9361464aecd09ca67ad37175a9ff Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:10:06 +0530 Subject: [PATCH 54/86] feat(extract.js): rm input section, add inputs with new spec --- script/util/extract.js | 61 +++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/script/util/extract.js b/script/util/extract.js index f1f8ffa..64a3939 100644 --- a/script/util/extract.js +++ b/script/util/extract.js @@ -417,38 +417,37 @@ Deployed contracts: - ${contractInfos .map( - ({ contract, contractName }) => - `[${contractName.replace(/([A-Z])/g, " $1").trim()}](${ - getEtherscanLink(chainId, contract.address) || contract.address - })${ - contract.proxyType - ? ` ([Implementation](${ - getEtherscanLink(chainId, contract.implementation) || contract.implementation - }))` - : `` - }`, - ) - .join("\n- ")} - -
-Inputs - + ({ contract, contractName }) => ` +
+ ${contractName + .replace(/([A-Z])/g, " $1") + .trim()}${ + contract.proxyType + ? ` (Implementation)` + : `` + } +
+ + + + + ${Object.entries(contract.input) + .map( + ([key, value]) => ` - - - - ${Object.entries(contractInfos[0].input) - .map( - ([key, value]) => ` - - - -`, - ) - .join("\n")} -
ParameterValue
ParameterValue
${key}${value}
-
- `, + ${key} + ${value} + `, + ) + .join("\n")} + + + `, + ) + .join("\n- ")} +`, ) .join("\n\n"); From ddcb44b0fa0d383a768adda8ba026f85f3e5adfc Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:57:02 +0100 Subject: [PATCH 55/86] fix: disable initializers --- src/Counter.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Counter.sol b/src/Counter.sol index 23c6eb1..b579bd5 100644 --- a/src/Counter.sol +++ b/src/Counter.sol @@ -7,6 +7,10 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini contract Counter is ICounter, Initializable { uint256 public number; + constructor() { + _disableInitializers(); + } + function initialize(uint256 initialNumber) public initializer { number = initialNumber; } From f929f8b83d27dc003c0763dffd9efbb1bed485bd Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:57:53 +0100 Subject: [PATCH 56/86] feat: robust extractor --- script/util/extractor.js | 337 ++++++++++++++++++++------------------- 1 file changed, 174 insertions(+), 163 deletions(-) diff --git a/script/util/extractor.js b/script/util/extractor.js index ea83fea..d306594 100644 --- a/script/util/extractor.js +++ b/script/util/extractor.js @@ -1,50 +1,40 @@ const fs = require("fs"); const path = require("path"); -const { exec } = require("child_process"); -const Util = require("util"); -const asyncExec = Util.promisify(exec); +const { execSync } = require("child_process"); // ========== ABOUT ========== /* Given the latest broadcast file, -Update the deployment history and latest data for the chain. +Updates the deployment history and latest data for the chain. -Notes: - -- The script assumes that the proxy is created immediately after the implementation contract. -- Only TransparentUpgradeableProxy by OpenZeppelin is supported. +Note: Only TransparentUpgradeableProxy by OpenZeppelin is supported at the moment. */ -// ========== INPUTS ========== - -const scriptName = process.argv[2]; // Replace with the appropriate index for scriptName -const chainId = process.argv[3]; // Replace with the appropriate index for chainId - -async function main() { +async function main(scriptName, chainId) { console.log("Extracting..."); - // ========== APPEND TO HISTORY ========== + // ========== PREPARE FILES ========== - // Used for getVersion() helper + // For getVersion helper const config = JSON.parse(fs.readFileSync(path.join(__dirname, "../config.json"), "utf-8")); const rpcUrl = config.defaultRpc[chainId] || process.env.RPC_URL || "http://127.0.0.1:8545"; + // Latest broadcast const filePath = path.join(__dirname, `../../broadcast/${scriptName}/${chainId}/run-latest.json`); - // Load JSON data from file const jsonData = JSON.parse(fs.readFileSync(filePath, "utf-8")); + // Previously extracted data const recordFilePath = path.join(__dirname, `../../deployments/json/${chainId}.json`); - let recordData; + // Try to read previously extracted data try { - // Try to read the existing record.json file recordData = JSON.parse(fs.readFileSync(recordFilePath, "utf8")); } catch (error) { - // If the file doesn't exist, create a new object + // If the file doesn't exist, create a new JSON recordData = { chainId: chainId, latest: {}, @@ -52,149 +42,190 @@ async function main() { }; } - // If history is not empty, check if the latest entry is the same as the current commit + // Abort if commit processed if (recordData.history.length > 0) { const latestEntry = recordData.history[recordData.history.length - 1]; if (latestEntry.commitHash === jsonData.commit) { - // If the latest entry is the same as the current commit, exit the script - console.log(`Commit ${jsonData.commit} already processed. Aborted.`); - return; + console.error(`Commit ${jsonData.commit} already processed. Aborted.`); + process.exit(1); } } - await prepareArtifacts(); + // Generate Forge artifacts + prepareArtifacts(); - // Filter transactions with "transactionType" as "CREATE" + // ========== UPDATE LATEST ========== + + const upgradeableTemplate = { + implementation: "", + address: "", + proxy: true, + version: "", + proxyType: "TransparentUpgradeableProxy", + deploymentTxn: "", + proxyAdmin: "", + input: {}, + }; + + const nonUpgradeableTemplate = { + address: "", + proxy: false, + version: "", + deploymentTxn: "", + input: {}, + }; + + // Filter CREATE transactions const createTransactions = jsonData.transactions.filter((transaction) => transaction.transactionType === "CREATE"); - // Initialize an object to store the items - const deploymentItems = {}; + // For history + const contracts = {}; - // Iterate through the filtered transactions + // Iterate over transactions for (let i = 0; i < createTransactions.length; i++) { const currentTransaction = createTransactions[i]; + const contractName = currentTransaction.contractName; - // Inside the loop where you process transactions - if (i < createTransactions.length - 1 && createTransactions[i + 1].contractName === "TransparentUpgradeableProxy") { - const upgradeableItem = { - implementation: currentTransaction.contractAddress, - proxyAdmin: createTransactions[i + 1].additionalContracts[0].address, - address: createTransactions[i + 1].contractAddress, - proxy: true, - version: (await getVersion(createTransactions[i + 1].contractAddress, rpcUrl)).version, - proxyType: "TransparentUpgradeableProxy", - deploymentTxn: createTransactions[i + 1].hash, - input: extractConstructorAndInitializeInputs(getABI(currentTransaction.contractName), { - constructor: currentTransaction.arguments, - // dump initData - initialize: createTransactions[i + 1].arguments[2], - }), - }; - - deploymentItems[currentTransaction.contractName] = upgradeableItem; + // ====== TYPE: CONTRACT NOT PROXY ===== + if (contractName !== "TransparentUpgradeableProxy") { + // Contract exists in latest + if (recordData.latest.hasOwnProperty(contractName)) { + const matchedItem = recordData.latest[contractName]; - // Skip the next iteration to avoid processing the proxy again - i++; - } else { - const maybeNonUpgradeableItem = { - address: currentTransaction.contractAddress, - proxy: false, - version: (await getVersion(currentTransaction.contractAddress, rpcUrl)).version, - deploymentTxn: currentTransaction.hash, - input: extractConstructorAndInitializeInputs(getABI(currentTransaction.contractName), { - constructor: currentTransaction.arguments, - }), - }; - - // Check if a contract with the same name exists in the latest data - if (recordData.latest.hasOwnProperty(currentTransaction.contractName)) { - const matchedItem = recordData.latest[currentTransaction.contractName]; - - // Check if the latest item has a proxy + // The latest is upgradeable if (matchedItem.proxy) { - // Update the current object with data from the latest item - maybeNonUpgradeableItem.implementation = currentTransaction.contractAddress; - maybeNonUpgradeableItem.proxyAdmin = matchedItem.proxyAdmin; - maybeNonUpgradeableItem.address = matchedItem.address; - maybeNonUpgradeableItem.proxy = true; - maybeNonUpgradeableItem.proxyType = matchedItem.proxyType; - maybeNonUpgradeableItem.deploymentTxn = matchedItem.deploymentTxn; + // CASE: New implementation created + const upgradeableItem = { + ...upgradeableTemplate, + implementation: currentTransaction.contractAddress, + proxyAdmin: matchedItem.proxyAdmin, + address: matchedItem.address, + proxy: true, + version: (await getVersion(matchedItem.address, rpcUrl)).version, + proxyType: matchedItem.proxyType, + deploymentTxn: matchedItem.deploymentTxn, + input: { + constructor: matchConstructorInputs(getABI(contractName), currentTransaction.arguments), + initializationTxn: "TODO", + }, + }; + + // Append it to history item + contracts[contractName] = upgradeableItem; + // Update latest item + let copyOfUpgradeableItem = { ...upgradeableItem }; + delete copyOfUpgradeableItem.input; + copyOfUpgradeableItem.timestamp = jsonData.timestamp; + copyOfUpgradeableItem.commitHash = jsonData.commit; + recordData.latest[contractName] = copyOfUpgradeableItem; + } else { + // The latest wasn't upgradeable + // CASE: Duplicate non-upgradeable contract + // TODO Allow if newer version. + console.error(`${contractName} is duplicate non-upgradeable. Aborted.`); + process.exit(1); } - - // Define the order of fields - const fieldOrder = [ - "implementation", - "proxyAdmin", - "address", - "proxy", - "version", - "proxyType", - "deploymentTxn", - "input", - ]; - - // Create the object with the desired field order - const upgradedItem = {}; - for (const fieldName of fieldOrder) { - upgradedItem[fieldName] = maybeNonUpgradeableItem[fieldName]; - } - - deploymentItems[currentTransaction.contractName] = upgradedItem; } else { - deploymentItems[currentTransaction.contractName] = maybeNonUpgradeableItem; + // Contract didn't exist in latest + + // Search for proxy in subsequent transactions + let proxyFound = false; + + for (let j = i + 1; j < createTransactions.length; j++) { + const nextTransaction = createTransactions[j]; + // Proxy found + if ( + nextTransaction.contractName === "TransparentUpgradeableProxy" && + nextTransaction.arguments[0] === currentTransaction.contractAddress + ) { + // CASE: New upgradeable contract + const upgradeableItem = { + ...upgradeableTemplate, + implementation: currentTransaction.contractAddress, + proxyAdmin: nextTransaction.additionalContracts[0].address, + address: nextTransaction.contractAddress, + proxy: true, + version: (await getVersion(nextTransaction.contractAddress, rpcUrl)).version, + proxyType: nextTransaction.contractName, + deploymentTxn: nextTransaction.hash, + input: { + constructor: matchConstructorInputs(getABI(contractName), currentTransaction.arguments), + initializationTxn: "TODO", + }, + }; + + // Append it to history item + contracts[contractName] = upgradeableItem; + // Update latest item + let copyOfUpgradeableItem = { ...upgradeableItem }; + delete copyOfUpgradeableItem.input; + copyOfUpgradeableItem.timestamp = jsonData.timestamp; + copyOfUpgradeableItem.commitHash = jsonData.commit; + recordData.latest[contractName] = copyOfUpgradeableItem; + + proxyFound = true; + } + } + // Didn't find proxy + if (!proxyFound) { + // CASE: New non-upgradeable contract + const nonUpgradeableItem = { + ...nonUpgradeableTemplate, + address: currentTransaction.contractAddress, + version: (await getVersion(currentTransaction.contractAddress, rpcUrl)).version, + deploymentTxn: currentTransaction.hash, + input: { constructor: matchConstructorInputs(getABI(contractName), currentTransaction.arguments) }, + }; + + // Append it to history item + contracts[contractName] = nonUpgradeableItem; + // Update latest item + let copyOfNonUpgradeableItem = { ...nonUpgradeableItem }; + delete copyOfNonUpgradeableItem.input; + copyOfNonUpgradeableItem.timestamp = jsonData.timestamp; + copyOfNonUpgradeableItem.commitHash = jsonData.commit; + recordData.latest[contractName] = copyOfNonUpgradeableItem; + } + } + } else { + // ===== TYPE: PROXY ===== + + // Check if proxy has been processed + for (const contractName in recordData.latest) { + if (recordData.latest.hasOwnProperty(contractName)) { + const latestItem = recordData.latest[contractName]; + if (latestItem.address === currentTransaction.contractAddress) { + // CASE: Proxy done + break; + } else { + // CASE: Unexpected proxy + console.error(`Unexpected proxy encountered. Aborted.`); + process.exit(1); + } + } } } } - // Now you have named items in the `deploymentItems` object - - // Create a new JSON with the desired format - const historyItem = { contracts: deploymentItems, timestamp: jsonData.timestamp, commitHash: jsonData.commit }; - - // Append the historyItem to the history array - recordData.history.push(historyItem); - - // ========== UPDATE LATEST ========== + // ========== APPEND TO HISTORY ========== - for (const contractName in historyItem.contracts) { - if (historyItem.contracts.hasOwnProperty(contractName)) { - const contractData = historyItem.contracts[contractName]; - - // Check if an item with the same contract name exists in the latest object - if (recordData.latest[contractName]) { - // Update the existing entry in the latest object - recordData.latest[contractName] = { - ...recordData.latest[contractName], // Preserve existing data - ...contractData, // Update with new data from historyItem - timestamp: historyItem.timestamp, // Add timestamp - commitHash: historyItem.commitHash, // Add commitHash - }; - // Exclude the 'input' field from the latest object - delete recordData.latest[contractName].input; - } else { - // Add a new entry to the latest object - recordData.latest[contractName] = { - ...contractData, // Add new data from historyItem - timestamp: historyItem.timestamp, // Add timestamp - commitHash: historyItem.commitHash, // Add commitHash - }; - // Exclude the 'input' field from the latest object - delete recordData.latest[contractName].input; - } - } - } + recordData.history.push({ contracts, timestamp: jsonData.timestamp, commitHash: jsonData.commit }); - // ========== SAVE CHANGES ========== + /* ========== SAVE CHANGES ========== - // Ensure the directory exists before writing the file + // Create file if it doesn't exist const directoryPath = path.dirname(recordFilePath); if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath, { recursive: true }); // Create the directory if it doesn't exist + fs.mkdirSync(directoryPath, { recursive: true }); } - // Write the updated object back to the record.json file + // Write to file fs.writeFileSync(recordFilePath, JSON.stringify(recordData, null, 2), "utf8"); - console.log(`Extraction complete.`); + */ + + console.log(`Extraction complete!`); + + return recordData; } main(); @@ -235,39 +266,19 @@ async function getVersion(contractAddress, rpcUrl) { } } -// IN: contract ABI and input (constructor and initialize) values +// IN: contract ABI and input values // OUT: mappings of input names to values -// Note: If .initialize is a string, .initData will be dumped. -function extractConstructorAndInitializeInputs(abi, inputData) { +function matchConstructorInputs(abi, inputData) { const inputMapping = {}; - // Find the constructor in the ABI by type "constructor" const constructorFunc = abi.find((func) => func.type === "constructor"); - // Find the initialize function in the ABI by name "initialize" - const initializeFunc = abi.find((func) => func.type === "function" && func.name === "initialize"); - - if (constructorFunc && inputData.constructor) { - // Match constructor inputs with their names - inputMapping.constructor = {}; + if (constructorFunc && inputData) { constructorFunc.inputs.forEach((input, index) => { - inputMapping.constructor[input.name] = inputData.constructor[index]; + inputMapping[input.name] = inputData[index]; }); } - if (initializeFunc) { - if (typeof inputData.initialize === "string") { - // If initialize is a string, create an "initData" field - inputMapping.initData = inputData.initialize; - } else if (inputData.initialize) { - // Match initialize inputs with their names - inputMapping.initialize = {}; - initializeFunc.inputs.forEach((input, index) => { - inputMapping.initialize[input.name] = inputData.initialize[index]; - }); - } - } - return inputMapping; } @@ -280,12 +291,12 @@ function getABI(contractName) { return abi; } -// Note: Makes sure contract artifacts are up-to-date. -async function prepareArtifacts() { +// Note: Ensures contract artifacts are up-to-date. +function prepareArtifacts() { console.log(`Preparing artifacts...`); - await asyncExec("forge clean"); - await asyncExec("forge build"); + execSync("forge clean"); + execSync("forge build"); - console.log(`Artifacts ready. Continuing...`); + console.log(`Artifacts ready. Continuing.`); } From 0721cfa669823945716278774e141de30959cc06 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:59:12 +0100 Subject: [PATCH 57/86] script: json -> sol --- script/1.0.0/Deploy.s.sol | 9 ++++++--- script/1.0.0/Input.sol | 32 ++++++++++++++++++++++++++++++++ script/1.0.0/input.json | 14 -------------- script/util/ScriptHelpers.sol | 10 +--------- 4 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 script/1.0.0/Input.sol delete mode 100644 script/1.0.0/input.json diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol index dc69d26..3a3694f 100644 --- a/script/1.0.0/Deploy.s.sol +++ b/script/1.0.0/Deploy.s.sol @@ -4,14 +4,17 @@ pragma solidity 0.8.22; import "forge-std/Script.sol"; import "script/util/ScriptHelpers.sol"; +import "./Input.sol"; import "script/deployers/DeployCounter.s.sol"; -contract Deploy is Script, ScriptHelpers, CounterDeployer { +contract Deploy is Script, ScriptHelpers, Input, CounterDeployer { using stdJson for string; function run() public { - string memory input = vm.readFile("script/1.0.0/input.json"); + deployCounter_NoInit(getInput().ProxyAdmin.initialOwner); + } - deployCounter(input.readAddress($("ProxyAdmin.initialOwner")), input.readUint($("Counter.number"))); + function _run_AllNew() internal { + deployCounter(getInput().ProxyAdmin.initialOwner, getInput().Counter.number); } } diff --git a/script/1.0.0/Input.sol b/script/1.0.0/Input.sol new file mode 100644 index 0000000..db61cf0 --- /dev/null +++ b/script/1.0.0/Input.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.22; + +import "forge-std/Script.sol"; +import "script/util/ScriptHelpers.sol"; + +import "script/deployers/DeployCounter.s.sol"; + +abstract contract Input { + struct ProxyAdminInput { + address initialOwner; + } + + struct CounterInput { + uint256 number; + } + + struct NewInput { + ProxyAdminInput ProxyAdmin; + CounterInput Counter; + } + + mapping(uint256 chainId => NewInput input) internal input; + + constructor() { + input[31_337] = NewInput({ProxyAdmin: ProxyAdminInput({initialOwner: 0x356f394005D3316ad54d8f22b40D02Cd539A4a3C}), Counter: CounterInput({number: 10})}); + } + + function getInput() internal view returns (NewInput memory) { + return input[block.chainid]; + } +} diff --git a/script/1.0.0/input.json b/script/1.0.0/input.json deleted file mode 100644 index 8e5f492..0000000 --- a/script/1.0.0/input.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "1": {}, - - "5": {}, - - "31337": { - "ProxyAdmin": { - "initialOwner": "0x356f394005D3316ad54d8f22b40D02Cd539A4a3C" - }, - "Counter": { - "number": 10 - } - } -} diff --git a/script/util/ScriptHelpers.sol b/script/util/ScriptHelpers.sol index 6eb218e..5e8adbb 100644 --- a/script/util/ScriptHelpers.sol +++ b/script/util/ScriptHelpers.sol @@ -3,12 +3,4 @@ pragma solidity 0.8.22; import "forge-std/Script.sol"; -abstract contract ScriptHelpers is Script { - using stdJson for string; - - ///@notice Returns the JSON field for the current chain ID. - function $(string memory field) internal view returns (string memory) { - string memory chainIdSlug = string.concat('["', vm.toString(block.chainid), '"]'); - return string.concat(chainIdSlug, ".", field); - } -} +abstract contract ScriptHelpers is Script {} From 254805bd28301629cbae69ab499e6c1f77c18f69 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:59:57 +0100 Subject: [PATCH 58/86] test: update tests --- test/1.0.0/Counter.t.sol | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/1.0.0/Counter.t.sol b/test/1.0.0/Counter.t.sol index b9e9db8..f2e1d63 100644 --- a/test/1.0.0/Counter.t.sol +++ b/test/1.0.0/Counter.t.sol @@ -6,22 +6,28 @@ import "test/util/TestHelpers.sol"; import "script/1.0.0/Deploy.s.sol"; -abstract contract BeforeScript is Test, TestHelpers, CounterDeployer { +abstract contract BeforeScript is Test, TestHelpers, Deploy { function setUp() public virtual { - deployCounter_NoInit(makeAddr("")); + _run_AllNew(); } } contract CounterTest_Zero is BeforeScript { + function test_RevertsIf_ImplementationInitialized() public { + vm.expectRevert(Initializable.InvalidInitialization.selector); + Counter(counterLogic).initialize(1); + } + function test_Initializes(uint256 number) public { - counter.initialize(number); + input[block.chainid].Counter.number = number; + _run_AllNew(); assertEq(counter.number(), number); } } abstract contract AfterScript is Test, TestHelpers, Deploy { function setUp() public virtual { - run(); + _run_AllNew(); } } From 594f16a1f5efcffad1835a0910f558e064d23986 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:08:37 +0530 Subject: [PATCH 59/86] chore: rename to extractAndSaveJson --- script/util/extractor.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/script/util/extractor.js b/script/util/extractor.js index d306594..6eecdd5 100644 --- a/script/util/extractor.js +++ b/script/util/extractor.js @@ -13,7 +13,7 @@ Note: Only TransparentUpgradeableProxy by OpenZeppelin is supported at the momen */ -async function main(scriptName, chainId) { +async function extractAndSaveJson(scriptName, chainId) { console.log("Extracting..."); // ========== PREPARE FILES ========== @@ -211,7 +211,7 @@ async function main(scriptName, chainId) { recordData.history.push({ contracts, timestamp: jsonData.timestamp, commitHash: jsonData.commit }); - /* ========== SAVE CHANGES ========== + // ========== SAVE CHANGES ========== // Create file if it doesn't exist const directoryPath = path.dirname(recordFilePath); @@ -221,15 +221,12 @@ async function main(scriptName, chainId) { // Write to file fs.writeFileSync(recordFilePath, JSON.stringify(recordData, null, 2), "utf8"); - */ console.log(`Extraction complete!`); return recordData; } -main(); - // ========== HELPERS ========== // IN: contract address and RPC URL @@ -300,3 +297,5 @@ function prepareArtifacts() { console.log(`Artifacts ready. Continuing.`); } + +module.exports = { extractAndSaveJson }; From 20749012b7a5939bdaed49ab31c6c96a0b23cb7d Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:09:27 +0530 Subject: [PATCH 60/86] fix: use extractAndSaveJson, add initializationTxn link --- script/util/extract.js | 296 +++++++---------------------------------- 1 file changed, 48 insertions(+), 248 deletions(-) diff --git a/script/util/extract.js b/script/util/extract.js index 64a3939..04276c0 100644 --- a/script/util/extract.js +++ b/script/util/extract.js @@ -1,231 +1,24 @@ -const { readFileSync, existsSync, writeFileSync, mkdirSync } = require("fs"); -const { execSync } = require("child_process"); +const { readFileSync, existsSync, writeFileSync } = require("fs"); const { join } = require("path"); +const { extractAndSaveJson } = require("./extractor.js"); /** * @description Extracts contract deployment data from run-latest.json (foundry broadcast output) and writes to deployments/{chainId}.json - * @usage node script/utils/extract.js {chainId} [version = "1.0.0"] [scriptName = "Deploy.s.sol"] + * @usage node script/utils/extract.js {chainId} [scriptName = "Deploy.s.sol"] * @dev * currently only supports TransparentUpgradeableProxy pattern */ async function main() { validateInputs(); - let [chainId, version, scriptName] = process.argv.slice(2); - if (!version?.length) version = "1.0.0"; + let [chainId, scriptName] = process.argv.slice(2); if (!scriptName?.length) scriptName = "Deploy.s.sol"; - const commitHash = getCommitHash(); - const data = JSON.parse( - readFileSync(join(__dirname, `../../broadcast/${scriptName}/${chainId}/run-latest.json`), "utf-8"), - ); - const config = JSON.parse(readFileSync(join(__dirname, "../config.json"), "utf-8")); - const input = JSON.parse(readFileSync(join(__dirname, `../${version}/input.json`), "utf-8")); - const rpcUrl = config.defaultRpc[chainId] || process.env.RPC_URL || "http://127.0.0.1:8545"; - const deployments = data.transactions.filter(({ transactionType }) => transactionType === "CREATE"); - - const outPath = join(__dirname, `../../deployments/json/${chainId}.json`); - if (!existsSync(join(__dirname, "../../deployments/"))) mkdirSync(join(__dirname, "../../deployments/")); - if (!existsSync(join(__dirname, "../../deployments/json/"))) mkdirSync(join(__dirname, "../../deployments/json/")); - const out = JSON.parse( - (existsSync(outPath) && readFileSync(outPath, "utf-8")) || JSON.stringify({ chainId, latest: {}, history: [] }), - ); - - const timestamp = data.timestamp; - let latestContracts = {}; - if (Object.keys(out.latest).length === 0) { - const deployedContractsMap = new Map( - [...deployments].map(({ contractAddress, contractName }) => [contractAddress, contractName]), - ); - - // first deployment - // todo(future): add support for other proxy patterns - const proxies = await Promise.all( - deployments - .filter(({ contractName }) => contractName === "TransparentUpgradeableProxy") - .map(async ({ arguments, contractAddress, hash }) => ({ - implementation: arguments[0], - proxyAdmin: arguments[1], - address: contractAddress, - contractName: deployedContractsMap.get(arguments[0]), - proxy: true, - ...(await getVersion(contractAddress, rpcUrl)), - proxyType: "TransparentUpgradeableProxy", - timestamp, - deploymentTxn: hash, - commitHash, - })), - ); - const nonProxies = await Promise.all( - deployments - .filter( - ({ contractName }) => - contractName !== "TransparentUpgradeableProxy" && !proxies.find((p) => p.contractName === contractName), - ) - .map(async ({ contractName, contractAddress, hash }) => ({ - address: contractAddress, - contractName, - proxy: false, - ...(await getVersion(contractAddress, rpcUrl)), - timestamp, - deploymentTxn: hash, - commitHash, - })), - ); - const contracts = [...proxies, ...nonProxies].reduce((obj, { contractName, ...rest }) => { - obj[contractName] = rest; - return obj; - }, {}); - latestContracts = contracts; - out.history.push({ - contracts: Object.entries(contracts).reduce((obj, [key, { timestamp, commitHash, ...rest }]) => { - obj[key] = rest; - return obj; - }, {}), - input: input[chainId], - timestamp, - commitHash, - }); - } else { - if (out.history.find((h) => h.commitHash === commitHash)) return console.log("warn: commitHash already deployed"); // if commitHash already exists in history, return - - for (const { contractName, contractAddress } of deployments) { - if (Object.keys(out.latest).includes(contractName) && out.latest[contractName].proxy) { - // new deployment, check if implementation changed on chain - if (out.latest[contractName].proxyType !== "TransparentUpgradeableProxy") continue; // only support TransparentUpgradeableProxy pattern - const currentImplementation = getImplementationAddress( - out.latest[contractName].address, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - rpcUrl, - ); - if (currentImplementation === out.latest[contractName].implementation) - throw new Error( - `Implementation for ${contractName}(${out.latest[contractName].address}) did not change - ${currentImplementation}, deployed - ${contractAddress}`, - ); - if (currentImplementation !== contractAddress) - throw new Error( - `Implementation mismatch for ${contractName}(${out.latest[contractName].address}), onchain - ${currentImplementation}, deployed - ${contractAddress}`, - ); - - // currentImplementation === contractAddress - // implementation changed, update latestContracts - latestContracts[contractName] = { - ...out.latest[contractName], - implementation: toChecksumAddress(currentImplementation), - version: (await getVersion(currentImplementation, rpcUrl))?.version || version, - timestamp, - commitHash, - }; - out.history.unshift({ - contracts: Object.entries(latestContracts).reduce((obj, [key, { timestamp, commitHash, ...rest }]) => { - obj[key] = rest; - return obj; - }, {}), - input: input[chainId], - timestamp, - commitHash, - }); - } - } - - const deployedContractsMap = new Map( - Object.entries(out.latest).map(([contractName, { address }]) => [address.toLowerCase(), contractName]), - ); - - for (const { transaction, transactionType } of data.transactions) { - if ( - transactionType === "CALL" && - deployedContractsMap.get(transaction.to.toLowerCase()) === "ProxyAdmin" && - transaction.data.startsWith("0x99a88ec4") // upgrade(address, address) - ) { - const proxyAddress = "0x" + transaction.data.slice(34, 74); - const newImplementationAddress = "0x" + transaction.data.slice(98, 138); - const contractName = deployedContractsMap.get(proxyAddress.toLowerCase()); - - latestContracts[contractName] = { - ...out.latest[contractName], - implementation: toChecksumAddress(newImplementationAddress), - version: (await getVersion(newImplementationAddress, rpcUrl))?.version || version, - timestamp, - commitHash, - }; - out.history.unshift({ - contracts: Object.entries(latestContracts).reduce((obj, [key, { timestamp, commitHash, ...rest }]) => { - obj[key] = rest; - return obj; - }, {}), - input: input[chainId], - timestamp, - commitHash, - }); - } - } - } - - // overwrite latest with changed contracts - out.latest = { - ...out.latest, - ...latestContracts, - }; - - writeFileSync(outPath, JSON.stringify(out, null, 2)); - generateMarkdown(out); -} - -function getCommitHash() { - return execSync("git rev-parse HEAD").toString().trim(); // note: update if not using git -} - -function toChecksumAddress(address) { - try { - return execSync(`cast to-check-sum-address ${address}`).toString().trim(); // note: update if not using cast - } catch (e) { - console.log("ERROR", e); - return address; - } -} - -function getImplementationAddress(proxyAddress, implementationSlot, rpcUrl) { - try { - const implementationAddress = execSync(`cast storage ${proxyAddress} ${implementationSlot} --rpc-url ${rpcUrl}`) - .toString() - .trim(); // note: update if not using cast - if (implementationAddress === "0x0000000000000000000000000000000000000000000000000000000000000000") - throw new Error(`empty implementation address for ${proxyAddress} at slot ${implementationSlot}`); - const trimmedAddress = "0x" + implementationAddress.substring(66 - 40, 66); - return toChecksumAddress(trimmedAddress); - } catch (e) { - console.log("ERROR", e); - return "0x0000000000000000000000000000000000000000"; - } -} - -async function getVersion(contractAddress, rpcUrl) { - try { - const res = await ( - await fetch(rpcUrl, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: Date.now(), - method: "eth_call", - params: [{ to: contractAddress, data: "0x54fd4d50" }, "latest"], // version()(string) - }), - }) - ).json(); - if (res.error) throw new Error(res.error.message); - return { version: hexToAscii(res.result)?.trim() || res.result }; - } catch (e) { - if (e.message === "execution reverted") return { version: undefined }; // contract does not implement getVersion() - if (e.message.includes("fetch is not defined")) { - console.warn("use node 18+"); - } - throw e; - } + validateInputs(); + generateAndSaveMarkdown(await extractAndSaveJson(scriptName, chainId)); } -function generateMarkdown(input) { - let out = `# Polygon Ecosystem Token\n\n`; - // read name from foundry.toml +function generateAndSaveMarkdown(input) { + console.log("Generating markdown..."); + let out = `# Polygon Foundry Template\n\n`; out += `\n### Table of Contents\n- [Summary](#summary)\n- [Contracts](#contracts)\n\t- `; out += Object.keys(input.latest) @@ -256,7 +49,7 @@ function generateMarkdown(input) { ([contractName, { address, version }]) => ` ${contractName} - ${address} + ${getEtherscanLinkAnchor(input.chainId, address)} ${version || `N/A`} `, ) @@ -307,6 +100,7 @@ ${generateProxyInformationIfProxy({ ${deploymentHistoryMd}`; writeFileSync(join(__dirname, `../../deployments/${input.chainId}.md`), out, "utf-8"); + console.log("Generation complete!"); } function getEtherscanLink(chainId, address, slug = "address") { @@ -316,15 +110,22 @@ function getEtherscanLink(chainId, address, slug = "address") { return `https://etherscan.io/${slug}/${address}`; case 5: return `https://goerli.etherscan.io/${slug}/${address}`; - default: + case 11155111: + return `https://sepolia.etherscan.io/${slug}/${address}`; + case 31337: return ``; - // return `https://blockscan.com/${slug}/${address}`; + default: + return `https://blockscan.com/${slug}/${address}`; } } function getEtherscanLinkMd(chainId, address, slug = "address") { const etherscanLink = getEtherscanLink(chainId, address, slug); return etherscanLink.length ? `[${address}](${etherscanLink})` : address; } +function getEtherscanLinkAnchor(chainId, address, slug = "address") { + const etherscanLink = getEtherscanLink(chainId, address, slug); + return etherscanLink.length ? `${address}` : address; +} function generateProxyInformationIfProxy({ address, @@ -363,7 +164,7 @@ function generateProxyInformationIfProxy({ }) => ` ${version} - ${implementation} + ${getEtherscanLinkAnchor(chainId, implementation)} ${commitHash.slice( 0, 7, @@ -427,22 +228,40 @@ Deployed contracts: getEtherscanLink(chainId, contract.implementation) || contract.implementation }">Implementation)` : `` + }${ + isTransaction(contract.input.initializationTxn) + ? ` (Initialization Txn)` + : `` } + ${ + Object.keys(contract.input.constructor).length + ? ` - ${Object.entries(contract.input) + ${Object.entries(contract.input.constructor) .map( ([key, value]) => ` - + `, ) .join("\n")}
Parameter Value
${key}${value}${ + isAddress(value) || isTransaction(value) + ? getEtherscanLinkAnchor(chainId, value, isTransaction(value) ? "tx" : "address") + : value + }
+` + : `` + } `, ) @@ -458,40 +277,21 @@ function prettifyTimestamp(timestamp) { return new Date(timestamp * 1000).toUTCString().replace("GMT", "UTC"); } -const hexToAscii = (str) => hexToUtf8(str).replace(/[\u0000-\u0008,\u000A-\u001F,\u007F-\u00A0]+/g, ""); // remove non-ascii chars -const hexToUtf8 = (str) => new TextDecoder().decode(hexToUint8Array(str)); // note: TextDecoder present in node, update if not using nodejs -function hexToUint8Array(hex) { - const value = hex.toLowerCase().startsWith("0x") ? hex.slice(2) : hex; - return new Uint8Array(Math.ceil(value.length / 2)).map((_, i) => parseInt(value.substring(i * 2, i * 2 + 2), 16)); +function isTransaction(str) { + return /^0x([A-Fa-f0-9]{64})$/.test(str); +} +function isAddress(str) { + return /^0x([A-Fa-f0-9]{40})$/.test(str); } function validateInputs() { - let [chainId, version, scriptName] = process.argv.slice(2); + let [chainId, scriptName] = process.argv.slice(2); let printUsageAndExit = false; - if ( - !( - typeof chainId === "string" && - ["string", "undefined"].includes(typeof version) && - ["string", "undefined"].includes(typeof scriptName) - ) || - chainId === "help" - ) { + if (!(typeof chainId === "string" && ["string", "undefined"].includes(typeof scriptName)) || chainId === "help") { if (chainId !== "help") console.log(`error: invalid inputs: ${JSON.stringify({ chainId, version, scriptName }, null, 0)}\n`); printUsageAndExit = true; } - if ( - version && - !( - existsSync(join(__dirname, `../${version}/input.json`)) && - existsSync(join(__dirname, `../${version}/${scriptName}`)) - ) - ) { - console.log( - `error: script/${version}/input.json or script/${version}/${scriptName || ""} does not exist\n`, - ); - printUsageAndExit = true; - } if (printUsageAndExit) { console.log(`usage: node script/utils/extract.js {chainId} [version = "1.0.0"] [scriptName = "Deploy.s.sol"]`); process.exit(1); From 575ecf6a1ad446f000d4ac7e759e08f1ad3b7539 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:13:33 +0530 Subject: [PATCH 61/86] chore: isolate md generation --- script/util/extract.js | 278 +------------------------------- script/util/generateMarkdown.js | 267 ++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+), 275 deletions(-) create mode 100644 script/util/generateMarkdown.js diff --git a/script/util/extract.js b/script/util/extract.js index 04276c0..778020d 100644 --- a/script/util/extract.js +++ b/script/util/extract.js @@ -1,7 +1,5 @@ -const { readFileSync, existsSync, writeFileSync } = require("fs"); -const { join } = require("path"); const { extractAndSaveJson } = require("./extractor.js"); - +const { generateAndSaveMarkdown } = require("./generateMarkdown.js"); /** * @description Extracts contract deployment data from run-latest.json (foundry broadcast output) and writes to deployments/{chainId}.json * @usage node script/utils/extract.js {chainId} [scriptName = "Deploy.s.sol"] @@ -12,288 +10,18 @@ async function main() { validateInputs(); let [chainId, scriptName] = process.argv.slice(2); if (!scriptName?.length) scriptName = "Deploy.s.sol"; - validateInputs(); generateAndSaveMarkdown(await extractAndSaveJson(scriptName, chainId)); } -function generateAndSaveMarkdown(input) { - console.log("Generating markdown..."); - let out = `# Polygon Foundry Template\n\n`; - - out += `\n### Table of Contents\n- [Summary](#summary)\n- [Contracts](#contracts)\n\t- `; - out += Object.keys(input.latest) - .map( - (c) => - `[${c.replace(/([A-Z])/g, " $1").trim()}](#${c - .replace(/([A-Z])/g, "-$1") - .trim() - .slice(1) - .toLowerCase()})`, - ) - .join("\n\t- "); - out += `\n- [Deployment History](#deployment-history)`; - const { deploymentHistoryMd, allVersions } = generateDeploymentHistory(input.history, input.latest, input.chainId); - out += Object.keys(allVersions) - .map((v) => `\n\t- [${v}](#${v.replace(/\./g, "")})`) - .join(""); - - out += `\n\n## Summary - - - - - - `; - out += Object.entries(input.latest) - .map( - ([contractName, { address, version }]) => - ` - - - - `, - ) - .join("\n"); - out += `
ContractAddressVersion
${contractName}${getEtherscanLinkAnchor(input.chainId, address)}${version || `N/A`}
\n`; - - out += `\n## Contracts\n\n`; - - out += Object.entries(input.latest) - .map( - ([ - contractName, - { address, deploymentTxn, version, commitHash, timestamp, proxyType, implementation, proxyAdmin }, - ]) => `### ${contractName.replace(/([A-Z])/g, " $1").trim()} - -Address: ${getEtherscanLinkMd(input.chainId, address)} - -Deployment Txn: ${getEtherscanLinkMd(input.chainId, deploymentTxn, "tx")} - -${ - typeof version === "undefined" - ? "" - : `Version: [${version}](https://github.com/0xPolygon/pol-token/releases/tag/${version})` -} - -Commit Hash: [${commitHash.slice(0, 7)}](https://github.com/0xPolygon/pol-token/commit/${commitHash}) - -${prettifyTimestamp(timestamp)} -${generateProxyInformationIfProxy({ - address, - contractName, - proxyType, - implementation, - proxyAdmin, - history: input.history, - chainId: input.chainId, -})}`, - ) - .join("\n\n --- \n\n"); - - out += ` - ----- - - -### Deployment History - -${deploymentHistoryMd}`; - - writeFileSync(join(__dirname, `../../deployments/${input.chainId}.md`), out, "utf-8"); - console.log("Generation complete!"); -} - -function getEtherscanLink(chainId, address, slug = "address") { - chainId = parseInt(chainId); - switch (chainId) { - case 1: - return `https://etherscan.io/${slug}/${address}`; - case 5: - return `https://goerli.etherscan.io/${slug}/${address}`; - case 11155111: - return `https://sepolia.etherscan.io/${slug}/${address}`; - case 31337: - return ``; - default: - return `https://blockscan.com/${slug}/${address}`; - } -} -function getEtherscanLinkMd(chainId, address, slug = "address") { - const etherscanLink = getEtherscanLink(chainId, address, slug); - return etherscanLink.length ? `[${address}](${etherscanLink})` : address; -} -function getEtherscanLinkAnchor(chainId, address, slug = "address") { - const etherscanLink = getEtherscanLink(chainId, address, slug); - return etherscanLink.length ? `${address}` : address; -} - -function generateProxyInformationIfProxy({ - address, - contractName, - proxyType, - implementation, - proxyAdmin, - history, - chainId, -}) { - let out = ``; - if (typeof proxyType === "undefined") return out; - out += `\n\n_Proxy Information_\n\n`; - out += `\n\nProxy Type: ${proxyType}\n\n`; - out += `\n\nImplementation: ${getEtherscanLinkMd(chainId, implementation)}\n\n`; - out += `\n\nProxy Admin: ${getEtherscanLinkMd(chainId, proxyAdmin)}\n\n`; - - const historyOfProxy = history.filter((h) => h?.contracts[contractName]?.address === address); - if (historyOfProxy.length === 0) return out; - out += `\n`; - out += ` -
-Implementation History - - - - - - ${historyOfProxy - .map( - ({ - contracts: { - [contractName]: { implementation, version }, - }, - commitHash, - }) => ` - - - - - `, - ) - .join("")} -
VersionAddressCommit Hash
${version}${getEtherscanLinkAnchor(chainId, implementation)}${commitHash.slice( - 0, - 7, - )}
-
- `; - return out; -} - -function generateDeploymentHistory(history, latest, chainId) { - let allVersions = {}; - if (history.length === 0) { - const inputPath = join(__dirname, "../1.0.0/input.json"); - const input = JSON.parse((existsSync(inputPath) && readFileSync(inputPath, "utf-8")) || `{"${chainId}":{}}`)[ - chainId - ]; - allVersions = Object.entries(latest).reduce((obj, [contractName, contract]) => { - if (typeof contract.version === "undefined") return obj; - if (!obj[contract.version]) obj[contract.version] = []; - obj[contract.version].push({ contract, contractName, input }); - return obj; - }, {}); - } else { - allVersions = history.reduce((obj, { contracts, input, timestamp, commitHash }) => { - Object.entries(contracts).forEach(([contractName, contract]) => { - if (typeof contract.version === "undefined") return; - if (!obj[contract.version]) obj[contract.version] = []; - obj[contract.version].push({ contract: { ...contract, timestamp, commitHash }, contractName, input }); - }); - return obj; - }, {}); - } - - let out = ``; - out += Object.entries(allVersions) - .map( - ([version, contractInfos]) => ` -### [${version}](https://github.com/0xPolygon/pol-token/releases/tag/${version}) - -${prettifyTimestamp(contractInfos[0].contract.timestamp)} - -Commit Hash: [${contractInfos[0].contract.commitHash.slice(0, 7)}](https://github.com/0xPolygon/pol-token/commit/${ - contractInfos[0].contract.commitHash - }) - -Deployed contracts: - -- ${contractInfos - .map( - ({ contract, contractName }) => ` -
- ${contractName - .replace(/([A-Z])/g, " $1") - .trim()}${ - contract.proxyType - ? ` (Implementation)` - : `` - }${ - isTransaction(contract.input.initializationTxn) - ? ` (Initialization Txn)` - : `` - } - ${ - Object.keys(contract.input.constructor).length - ? ` - - - - - - ${Object.entries(contract.input.constructor) - .map( - ([key, value]) => ` - - - - `, - ) - .join("\n")} -
ParameterValue
${key}${ - isAddress(value) || isTransaction(value) - ? getEtherscanLinkAnchor(chainId, value, isTransaction(value) ? "tx" : "address") - : value - }
-` - : `` - } -
- `, - ) - .join("\n- ")} -`, - ) - .join("\n\n"); - - return { deploymentHistoryMd: out, allVersions }; -} - -function prettifyTimestamp(timestamp) { - return new Date(timestamp * 1000).toUTCString().replace("GMT", "UTC"); -} - -function isTransaction(str) { - return /^0x([A-Fa-f0-9]{64})$/.test(str); -} -function isAddress(str) { - return /^0x([A-Fa-f0-9]{40})$/.test(str); -} - function validateInputs() { let [chainId, scriptName] = process.argv.slice(2); let printUsageAndExit = false; if (!(typeof chainId === "string" && ["string", "undefined"].includes(typeof scriptName)) || chainId === "help") { - if (chainId !== "help") - console.log(`error: invalid inputs: ${JSON.stringify({ chainId, version, scriptName }, null, 0)}\n`); + if (chainId !== "help") console.log(`error: invalid inputs: ${JSON.stringify({ chainId, scriptName }, null, 0)}\n`); printUsageAndExit = true; } if (printUsageAndExit) { - console.log(`usage: node script/utils/extract.js {chainId} [version = "1.0.0"] [scriptName = "Deploy.s.sol"]`); + console.log(`usage: node script/utils/extract.js {chainId} [scriptName = "Deploy.s.sol"]`); process.exit(1); } } diff --git a/script/util/generateMarkdown.js b/script/util/generateMarkdown.js new file mode 100644 index 0000000..2414938 --- /dev/null +++ b/script/util/generateMarkdown.js @@ -0,0 +1,267 @@ +const { readFileSync, existsSync, writeFileSync } = require("fs"); +const { join } = require("path"); + +function generateAndSaveMarkdown(input) { + console.log("Generating markdown..."); + let out = `# Polygon Foundry Template\n\n`; + + out += `\n### Table of Contents\n- [Summary](#summary)\n- [Contracts](#contracts)\n\t- `; + out += Object.keys(input.latest) + .map( + (c) => + `[${c.replace(/([A-Z])/g, " $1").trim()}](#${c + .replace(/([A-Z])/g, "-$1") + .trim() + .slice(1) + .toLowerCase()})`, + ) + .join("\n\t- "); + out += `\n- [Deployment History](#deployment-history)`; + const { deploymentHistoryMd, allVersions } = generateDeploymentHistory(input.history, input.latest, input.chainId); + out += Object.keys(allVersions) + .map((v) => `\n\t- [${v}](#${v.replace(/\./g, "")})`) + .join(""); + + out += `\n\n## Summary + + + + + + `; + out += Object.entries(input.latest) + .map( + ([contractName, { address, version }]) => + ` + + + + `, + ) + .join("\n"); + out += `
ContractAddressVersion
${contractName}${getEtherscanLinkAnchor(input.chainId, address)}${version || `N/A`}
\n`; + + out += `\n## Contracts\n\n`; + + out += Object.entries(input.latest) + .map( + ([ + contractName, + { address, deploymentTxn, version, commitHash, timestamp, proxyType, implementation, proxyAdmin }, + ]) => `### ${contractName.replace(/([A-Z])/g, " $1").trim()} + + Address: ${getEtherscanLinkMd(input.chainId, address)} + + Deployment Txn: ${getEtherscanLinkMd(input.chainId, deploymentTxn, "tx")} + + ${ + typeof version === "undefined" + ? "" + : `Version: [${version}](https://github.com/0xPolygon/pol-token/releases/tag/${version})` + } + + Commit Hash: [${commitHash.slice(0, 7)}](https://github.com/0xPolygon/pol-token/commit/${commitHash}) + + ${prettifyTimestamp(timestamp)} + ${generateProxyInformationIfProxy({ + address, + contractName, + proxyType, + implementation, + proxyAdmin, + history: input.history, + chainId: input.chainId, + })}`, + ) + .join("\n\n --- \n\n"); + + out += ` + + ---- + + + ### Deployment History + + ${deploymentHistoryMd}`; + + writeFileSync(join(__dirname, `../../deployments/${input.chainId}.md`), out, "utf-8"); + console.log("Generation complete!"); +} + +function getEtherscanLink(chainId, address, slug = "address") { + chainId = parseInt(chainId); + switch (chainId) { + case 1: + return `https://etherscan.io/${slug}/${address}`; + case 5: + return `https://goerli.etherscan.io/${slug}/${address}`; + case 11155111: + return `https://sepolia.etherscan.io/${slug}/${address}`; + case 31337: + return ``; + default: + return `https://blockscan.com/${slug}/${address}`; + } +} +function getEtherscanLinkMd(chainId, address, slug = "address") { + const etherscanLink = getEtherscanLink(chainId, address, slug); + return etherscanLink.length ? `[${address}](${etherscanLink})` : address; +} +function getEtherscanLinkAnchor(chainId, address, slug = "address") { + const etherscanLink = getEtherscanLink(chainId, address, slug); + return etherscanLink.length ? `${address}` : address; +} + +function generateProxyInformationIfProxy({ + address, + contractName, + proxyType, + implementation, + proxyAdmin, + history, + chainId, +}) { + let out = ``; + if (typeof proxyType === "undefined") return out; + out += `\n\n_Proxy Information_\n\n`; + out += `\n\nProxy Type: ${proxyType}\n\n`; + out += `\n\nImplementation: ${getEtherscanLinkMd(chainId, implementation)}\n\n`; + out += `\n\nProxy Admin: ${getEtherscanLinkMd(chainId, proxyAdmin)}\n\n`; + + const historyOfProxy = history.filter((h) => h?.contracts[contractName]?.address === address); + if (historyOfProxy.length === 0) return out; + out += `\n`; + out += ` +
+ Implementation History + + + + + + ${historyOfProxy + .map( + ({ + contracts: { + [contractName]: { implementation, version }, + }, + commitHash, + }) => ` + + + + + `, + ) + .join("")} +
VersionAddressCommit Hash
${version}${getEtherscanLinkAnchor(chainId, implementation)}${commitHash.slice( + 0, + 7, + )}
+
+ `; + return out; +} + +function generateDeploymentHistory(history, latest, chainId) { + let allVersions = {}; + if (history.length === 0) { + const inputPath = join(__dirname, "../1.0.0/input.json"); + const input = JSON.parse((existsSync(inputPath) && readFileSync(inputPath, "utf-8")) || `{"${chainId}":{}}`)[ + chainId + ]; + allVersions = Object.entries(latest).reduce((obj, [contractName, contract]) => { + if (typeof contract.version === "undefined") return obj; + if (!obj[contract.version]) obj[contract.version] = []; + obj[contract.version].push({ contract, contractName, input }); + return obj; + }, {}); + } else { + allVersions = history.reduce((obj, { contracts, input, timestamp, commitHash }) => { + Object.entries(contracts).forEach(([contractName, contract]) => { + if (typeof contract.version === "undefined") return; + if (!obj[contract.version]) obj[contract.version] = []; + obj[contract.version].push({ contract: { ...contract, timestamp, commitHash }, contractName, input }); + }); + return obj; + }, {}); + } + + let out = ``; + out += Object.entries(allVersions) + .map( + ([version, contractInfos]) => ` + ### [${version}](https://github.com/0xPolygon/pol-token/releases/tag/${version}) + + ${prettifyTimestamp(contractInfos[0].contract.timestamp)} + + Commit Hash: [${contractInfos[0].contract.commitHash.slice(0, 7)}](https://github.com/0xPolygon/pol-token/commit/${ + contractInfos[0].contract.commitHash + }) + + Deployed contracts: + + - ${contractInfos + .map( + ({ contract, contractName }) => ` +
+ ${contractName + .replace(/([A-Z])/g, " $1") + .trim()}${ + contract.proxyType + ? ` (Implementation)` + : `` + }${ + isTransaction(contract.input.initializationTxn) + ? ` (Initialization Txn)` + : `` + } + ${ + Object.keys(contract.input.constructor).length + ? ` + + + + + + ${Object.entries(contract.input.constructor) + .map( + ([key, value]) => ` + + + + `, + ) + .join("\n")} +
ParameterValue
${key}${ + isAddress(value) || isTransaction(value) + ? getEtherscanLinkAnchor(chainId, value, isTransaction(value) ? "tx" : "address") + : value + }
+ ` + : `` + } +
+ `, + ) + .join("\n- ")} + `, + ) + .join("\n\n"); + + return { deploymentHistoryMd: out, allVersions }; +} + +function prettifyTimestamp(timestamp) { + return new Date(timestamp * 1000).toUTCString().replace("GMT", "UTC"); +} + +function isTransaction(str) { + return /^0x([A-Fa-f0-9]{64})$/.test(str); +} +function isAddress(str) { + return /^0x([A-Fa-f0-9]{40})$/.test(str); +} +module.exports = { generateAndSaveMarkdown }; From c5bf6ad9965fab1241588279a98b774c7d92c690 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:52:13 +0530 Subject: [PATCH 62/86] feat: md gen, add dynamic projectmetadata --- script/util/generateMarkdown.js | 59 +++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/script/util/generateMarkdown.js b/script/util/generateMarkdown.js index 2414938..acc9a1b 100644 --- a/script/util/generateMarkdown.js +++ b/script/util/generateMarkdown.js @@ -1,11 +1,15 @@ +const { execSync } = require("child_process"); const { readFileSync, existsSync, writeFileSync } = require("fs"); const { join } = require("path"); +const projectGitUrl = getProjectUrl(); +const projectName = getProjectName(); + function generateAndSaveMarkdown(input) { console.log("Generating markdown..."); - let out = `# Polygon Foundry Template\n\n`; + let out = `# ${projectName}\n\n`; - out += `\n### Table of Contents\n- [Summary](#summary)\n- [Contracts](#contracts)\n\t- `; + out += `\n### Table of Contents\n- [Summary](#summary)\n- [Contracts](#contracts)\n- `; out += Object.keys(input.latest) .map( (c) => @@ -23,20 +27,20 @@ function generateAndSaveMarkdown(input) { .join(""); out += `\n\n## Summary - - - - - - `; +
ContractAddressVersion
+ + + + +`; out += Object.entries(input.latest) .map( ([contractName, { address, version }]) => ` - - - - `, + + + + `, ) .join("\n"); out += `
ContractAddressVersion
${contractName}${getEtherscanLinkAnchor(input.chainId, address)}${version || `N/A`}
${contractName}${getEtherscanLinkAnchor(input.chainId, address)}${version || `N/A`}
\n`; @@ -54,13 +58,9 @@ function generateAndSaveMarkdown(input) { Deployment Txn: ${getEtherscanLinkMd(input.chainId, deploymentTxn, "tx")} - ${ - typeof version === "undefined" - ? "" - : `Version: [${version}](https://github.com/0xPolygon/pol-token/releases/tag/${version})` - } + ${typeof version === "undefined" ? "" : `Version: [${version}](${projectGitUrl}/releases/tag/${version})`} - Commit Hash: [${commitHash.slice(0, 7)}](https://github.com/0xPolygon/pol-token/commit/${commitHash}) + Commit Hash: [${commitHash.slice(0, 7)}](${projectGitUrl}/commit/${commitHash}) ${prettifyTimestamp(timestamp)} ${generateProxyInformationIfProxy({ @@ -148,12 +148,9 @@ function generateProxyInformationIfProxy({ commitHash, }) => ` - ${version} + ${version} ${getEtherscanLinkAnchor(chainId, implementation)} - ${commitHash.slice( - 0, - 7, - )} + ${commitHash.slice(0, 7)} `, ) .join("")} @@ -191,11 +188,11 @@ function generateDeploymentHistory(history, latest, chainId) { out += Object.entries(allVersions) .map( ([version, contractInfos]) => ` - ### [${version}](https://github.com/0xPolygon/pol-token/releases/tag/${version}) + ### [${version}](${projectGitUrl}/releases/tag/${version}) ${prettifyTimestamp(contractInfos[0].contract.timestamp)} - Commit Hash: [${contractInfos[0].contract.commitHash.slice(0, 7)}](https://github.com/0xPolygon/pol-token/commit/${ + Commit Hash: [${contractInfos[0].contract.commitHash.slice(0, 7)}](${projectGitUrl}/commit/${ contractInfos[0].contract.commitHash }) @@ -261,7 +258,19 @@ function prettifyTimestamp(timestamp) { function isTransaction(str) { return /^0x([A-Fa-f0-9]{64})$/.test(str); } + function isAddress(str) { return /^0x([A-Fa-f0-9]{40})$/.test(str); } + +function getProjectUrl() { + return execSync(`git remote get-url origin`, { encoding: "utf-8" }) + .trim() + .replace(/\.git$/, ""); +} + +function getProjectName() { + return execSync(`git remote get-url origin | cut -d '/' -f 5 | cut -d '.' -f 1`, { encoding: "utf-8" }).trim(); +} + module.exports = { generateAndSaveMarkdown }; From d7e9dd680b864a575590bf6197ae9e5049505492 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:52:25 +0530 Subject: [PATCH 63/86] feat: skipJsonFlag --- script/util/extract.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/script/util/extract.js b/script/util/extract.js index 778020d..2e349ac 100644 --- a/script/util/extract.js +++ b/script/util/extract.js @@ -1,3 +1,5 @@ +const { readFileSync, existsSync } = require("fs"); +const path = require("path"); const { extractAndSaveJson } = require("./extractor.js"); const { generateAndSaveMarkdown } = require("./generateMarkdown.js"); /** @@ -7,23 +9,35 @@ const { generateAndSaveMarkdown } = require("./generateMarkdown.js"); * currently only supports TransparentUpgradeableProxy pattern */ async function main() { - validateInputs(); - let [chainId, scriptName] = process.argv.slice(2); - if (!scriptName?.length) scriptName = "Deploy.s.sol"; - generateAndSaveMarkdown(await extractAndSaveJson(scriptName, chainId)); + let [chainId, scriptName, skipJsonFlag] = validateAndExtractInputs(); + let json; + if (!skipJsonFlag.length) json = await extractAndSaveJson(scriptName, chainId); + else { + console.log("Skipping json extraction, using existing json file"); + const recordFilePath = path.join(__dirname, `../../deployments/json/${chainId}.json`); + if (!existsSync(recordFilePath)) throw new Error(`error: ${recordFilePath} does not exist`); + json = JSON.parse(readFileSync(recordFilePath, "utf-8")); + } + generateAndSaveMarkdown(json); } -function validateInputs() { - let [chainId, scriptName] = process.argv.slice(2); +function validateAndExtractInputs() { + let [chainId, scriptName, skipJsonFlag] = process.argv.slice(2); let printUsageAndExit = false; if (!(typeof chainId === "string" && ["string", "undefined"].includes(typeof scriptName)) || chainId === "help") { if (chainId !== "help") console.log(`error: invalid inputs: ${JSON.stringify({ chainId, scriptName }, null, 0)}\n`); printUsageAndExit = true; } + if (typeof skipJsonFlag !== "undefined" && !["--skip-json", "-s"].includes(skipJsonFlag)) { + console.log(`error: invalid flag: ${JSON.stringify({ skipJsonFlag }, null, 0)}\n`); + printUsageAndExit = true; + } if (printUsageAndExit) { - console.log(`usage: node script/utils/extract.js {chainId} [scriptName = "Deploy.s.sol"]`); + console.log(`usage: node script/utils/extract.js {chainId} [scriptName = "Deploy.s.sol"] [--skip-json | -s]`); process.exit(1); } + if (!scriptName?.length) scriptName = "Deploy.s.sol"; + return [chainId, scriptName, skipJsonFlag]; } main(); From 47d0207559513689e2b98f802e0c69def37dd44b Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:55:40 +0530 Subject: [PATCH 64/86] fix: rm fetch usage --- script/util/extractor.js | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/script/util/extractor.js b/script/util/extractor.js index 6eecdd5..bdd4dff 100644 --- a/script/util/extractor.js +++ b/script/util/extractor.js @@ -232,34 +232,15 @@ async function extractAndSaveJson(scriptName, chainId) { // IN: contract address and RPC URL // OUT: contract version (.version) async function getVersion(contractAddress, rpcUrl) { - const hexToAscii = (str) => hexToUtf8(str).replace(/[\u0000-\u0008,\u000A-\u001F,\u007F-\u00A0]+/g, ""); // remove non-ascii chars - const hexToUtf8 = (str) => new TextDecoder().decode(hexToUint8Array(str)); // note: TextDecoder present in node, update if not using nodejs - function hexToUint8Array(hex) { - const value = hex.toLowerCase().startsWith("0x") ? hex.slice(2) : hex; - return new Uint8Array(Math.ceil(value.length / 2)).map((_, i) => parseInt(value.substring(i * 2, i * 2 + 2), 16)); - } - try { - const res = await ( - await fetch(rpcUrl, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: Date.now(), - method: "eth_call", - params: [{ to: contractAddress, data: "0x54fd4d50" }, "latest"], // version()(string) - }), - }) - ).json(); - if (res.error) throw new Error(res.error.message); - return { version: hexToAscii(res.result)?.trim() || res.result }; + return { + version: execSync(`cast call ${contractAddress} 'version()(string)' --rpc-url ${rpcUrl}`, { + encoding: "utf-8", + }).trim(), + }; // note: update if not using cast } catch (e) { - if (e.message === "execution reverted") return { version: undefined }; // contract does not implement getVersion() - if (e.message.includes("fetch is not defined")) { - console.warn("use node 18+"); - } - throw e; + if (!e.message.includes("execution reverted")) console.log("ERROR", e); // contract does not implement version(), log otherwise + return { version: undefined }; } } From dbcc0d921461a02228755ec7b9dc777150d264da Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:12:35 +0100 Subject: [PATCH 65/86] docs: correct docs --- script/util/extract.js | 2 +- script/util/extractor.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/script/util/extract.js b/script/util/extract.js index 2e349ac..e571210 100644 --- a/script/util/extract.js +++ b/script/util/extract.js @@ -4,7 +4,7 @@ const { extractAndSaveJson } = require("./extractor.js"); const { generateAndSaveMarkdown } = require("./generateMarkdown.js"); /** * @description Extracts contract deployment data from run-latest.json (foundry broadcast output) and writes to deployments/{chainId}.json - * @usage node script/utils/extract.js {chainId} [scriptName = "Deploy.s.sol"] + * @usage node script/util/extract.js {chainId} [scriptName = "Deploy.s.sol"] [--skip-json | -s] * @dev * currently only supports TransparentUpgradeableProxy pattern */ diff --git a/script/util/extractor.js b/script/util/extractor.js index bdd4dff..26c789f 100644 --- a/script/util/extractor.js +++ b/script/util/extractor.js @@ -189,7 +189,6 @@ async function extractAndSaveJson(scriptName, chainId) { } } else { // ===== TYPE: PROXY ===== - // Check if proxy has been processed for (const contractName in recordData.latest) { if (recordData.latest.hasOwnProperty(contractName)) { @@ -199,7 +198,7 @@ async function extractAndSaveJson(scriptName, chainId) { break; } else { // CASE: Unexpected proxy - console.error(`Unexpected proxy encountered. Aborted.`); + console.error(`Unexpected proxy ${currentTransaction.contractAddress}. Aborted.`); process.exit(1); } } From cf02f53784419981a11156af1e350c6d4e7e2e59 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:07:17 +0100 Subject: [PATCH 66/86] chore: clean up --- script/util/extract.js | 43 ----- script/util/extractor.js | 281 -------------------------------- script/util/generateMarkdown.js | 276 ------------------------------- test/1.0.0/Counter.t.sol | 5 +- 4 files changed, 2 insertions(+), 603 deletions(-) delete mode 100644 script/util/extract.js delete mode 100644 script/util/extractor.js delete mode 100644 script/util/generateMarkdown.js diff --git a/script/util/extract.js b/script/util/extract.js deleted file mode 100644 index e571210..0000000 --- a/script/util/extract.js +++ /dev/null @@ -1,43 +0,0 @@ -const { readFileSync, existsSync } = require("fs"); -const path = require("path"); -const { extractAndSaveJson } = require("./extractor.js"); -const { generateAndSaveMarkdown } = require("./generateMarkdown.js"); -/** - * @description Extracts contract deployment data from run-latest.json (foundry broadcast output) and writes to deployments/{chainId}.json - * @usage node script/util/extract.js {chainId} [scriptName = "Deploy.s.sol"] [--skip-json | -s] - * @dev - * currently only supports TransparentUpgradeableProxy pattern - */ -async function main() { - let [chainId, scriptName, skipJsonFlag] = validateAndExtractInputs(); - let json; - if (!skipJsonFlag.length) json = await extractAndSaveJson(scriptName, chainId); - else { - console.log("Skipping json extraction, using existing json file"); - const recordFilePath = path.join(__dirname, `../../deployments/json/${chainId}.json`); - if (!existsSync(recordFilePath)) throw new Error(`error: ${recordFilePath} does not exist`); - json = JSON.parse(readFileSync(recordFilePath, "utf-8")); - } - generateAndSaveMarkdown(json); -} - -function validateAndExtractInputs() { - let [chainId, scriptName, skipJsonFlag] = process.argv.slice(2); - let printUsageAndExit = false; - if (!(typeof chainId === "string" && ["string", "undefined"].includes(typeof scriptName)) || chainId === "help") { - if (chainId !== "help") console.log(`error: invalid inputs: ${JSON.stringify({ chainId, scriptName }, null, 0)}\n`); - printUsageAndExit = true; - } - if (typeof skipJsonFlag !== "undefined" && !["--skip-json", "-s"].includes(skipJsonFlag)) { - console.log(`error: invalid flag: ${JSON.stringify({ skipJsonFlag }, null, 0)}\n`); - printUsageAndExit = true; - } - if (printUsageAndExit) { - console.log(`usage: node script/utils/extract.js {chainId} [scriptName = "Deploy.s.sol"] [--skip-json | -s]`); - process.exit(1); - } - if (!scriptName?.length) scriptName = "Deploy.s.sol"; - return [chainId, scriptName, skipJsonFlag]; -} - -main(); diff --git a/script/util/extractor.js b/script/util/extractor.js deleted file mode 100644 index 26c789f..0000000 --- a/script/util/extractor.js +++ /dev/null @@ -1,281 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const { execSync } = require("child_process"); - -// ========== ABOUT ========== - -/* - -Given the latest broadcast file, -Updates the deployment history and latest data for the chain. - -Note: Only TransparentUpgradeableProxy by OpenZeppelin is supported at the moment. - -*/ - -async function extractAndSaveJson(scriptName, chainId) { - console.log("Extracting..."); - - // ========== PREPARE FILES ========== - - // For getVersion helper - const config = JSON.parse(fs.readFileSync(path.join(__dirname, "../config.json"), "utf-8")); - const rpcUrl = config.defaultRpc[chainId] || process.env.RPC_URL || "http://127.0.0.1:8545"; - - // Latest broadcast - const filePath = path.join(__dirname, `../../broadcast/${scriptName}/${chainId}/run-latest.json`); - const jsonData = JSON.parse(fs.readFileSync(filePath, "utf-8")); - - // Previously extracted data - const recordFilePath = path.join(__dirname, `../../deployments/json/${chainId}.json`); - let recordData; - - // Try to read previously extracted data - try { - recordData = JSON.parse(fs.readFileSync(recordFilePath, "utf8")); - } catch (error) { - // If the file doesn't exist, create a new JSON - recordData = { - chainId: chainId, - latest: {}, - history: [], - }; - } - - // Abort if commit processed - if (recordData.history.length > 0) { - const latestEntry = recordData.history[recordData.history.length - 1]; - if (latestEntry.commitHash === jsonData.commit) { - console.error(`Commit ${jsonData.commit} already processed. Aborted.`); - process.exit(1); - } - } - - // Generate Forge artifacts - prepareArtifacts(); - - // ========== UPDATE LATEST ========== - - const upgradeableTemplate = { - implementation: "", - address: "", - proxy: true, - version: "", - proxyType: "TransparentUpgradeableProxy", - deploymentTxn: "", - proxyAdmin: "", - input: {}, - }; - - const nonUpgradeableTemplate = { - address: "", - proxy: false, - version: "", - deploymentTxn: "", - input: {}, - }; - - // Filter CREATE transactions - const createTransactions = jsonData.transactions.filter((transaction) => transaction.transactionType === "CREATE"); - - // For history - const contracts = {}; - - // Iterate over transactions - for (let i = 0; i < createTransactions.length; i++) { - const currentTransaction = createTransactions[i]; - const contractName = currentTransaction.contractName; - - // ====== TYPE: CONTRACT NOT PROXY ===== - if (contractName !== "TransparentUpgradeableProxy") { - // Contract exists in latest - if (recordData.latest.hasOwnProperty(contractName)) { - const matchedItem = recordData.latest[contractName]; - - // The latest is upgradeable - if (matchedItem.proxy) { - // CASE: New implementation created - const upgradeableItem = { - ...upgradeableTemplate, - implementation: currentTransaction.contractAddress, - proxyAdmin: matchedItem.proxyAdmin, - address: matchedItem.address, - proxy: true, - version: (await getVersion(matchedItem.address, rpcUrl)).version, - proxyType: matchedItem.proxyType, - deploymentTxn: matchedItem.deploymentTxn, - input: { - constructor: matchConstructorInputs(getABI(contractName), currentTransaction.arguments), - initializationTxn: "TODO", - }, - }; - - // Append it to history item - contracts[contractName] = upgradeableItem; - // Update latest item - let copyOfUpgradeableItem = { ...upgradeableItem }; - delete copyOfUpgradeableItem.input; - copyOfUpgradeableItem.timestamp = jsonData.timestamp; - copyOfUpgradeableItem.commitHash = jsonData.commit; - recordData.latest[contractName] = copyOfUpgradeableItem; - } else { - // The latest wasn't upgradeable - // CASE: Duplicate non-upgradeable contract - // TODO Allow if newer version. - console.error(`${contractName} is duplicate non-upgradeable. Aborted.`); - process.exit(1); - } - } else { - // Contract didn't exist in latest - - // Search for proxy in subsequent transactions - let proxyFound = false; - - for (let j = i + 1; j < createTransactions.length; j++) { - const nextTransaction = createTransactions[j]; - // Proxy found - if ( - nextTransaction.contractName === "TransparentUpgradeableProxy" && - nextTransaction.arguments[0] === currentTransaction.contractAddress - ) { - // CASE: New upgradeable contract - const upgradeableItem = { - ...upgradeableTemplate, - implementation: currentTransaction.contractAddress, - proxyAdmin: nextTransaction.additionalContracts[0].address, - address: nextTransaction.contractAddress, - proxy: true, - version: (await getVersion(nextTransaction.contractAddress, rpcUrl)).version, - proxyType: nextTransaction.contractName, - deploymentTxn: nextTransaction.hash, - input: { - constructor: matchConstructorInputs(getABI(contractName), currentTransaction.arguments), - initializationTxn: "TODO", - }, - }; - - // Append it to history item - contracts[contractName] = upgradeableItem; - // Update latest item - let copyOfUpgradeableItem = { ...upgradeableItem }; - delete copyOfUpgradeableItem.input; - copyOfUpgradeableItem.timestamp = jsonData.timestamp; - copyOfUpgradeableItem.commitHash = jsonData.commit; - recordData.latest[contractName] = copyOfUpgradeableItem; - - proxyFound = true; - } - } - // Didn't find proxy - if (!proxyFound) { - // CASE: New non-upgradeable contract - const nonUpgradeableItem = { - ...nonUpgradeableTemplate, - address: currentTransaction.contractAddress, - version: (await getVersion(currentTransaction.contractAddress, rpcUrl)).version, - deploymentTxn: currentTransaction.hash, - input: { constructor: matchConstructorInputs(getABI(contractName), currentTransaction.arguments) }, - }; - - // Append it to history item - contracts[contractName] = nonUpgradeableItem; - // Update latest item - let copyOfNonUpgradeableItem = { ...nonUpgradeableItem }; - delete copyOfNonUpgradeableItem.input; - copyOfNonUpgradeableItem.timestamp = jsonData.timestamp; - copyOfNonUpgradeableItem.commitHash = jsonData.commit; - recordData.latest[contractName] = copyOfNonUpgradeableItem; - } - } - } else { - // ===== TYPE: PROXY ===== - // Check if proxy has been processed - for (const contractName in recordData.latest) { - if (recordData.latest.hasOwnProperty(contractName)) { - const latestItem = recordData.latest[contractName]; - if (latestItem.address === currentTransaction.contractAddress) { - // CASE: Proxy done - break; - } else { - // CASE: Unexpected proxy - console.error(`Unexpected proxy ${currentTransaction.contractAddress}. Aborted.`); - process.exit(1); - } - } - } - } - } - - // ========== APPEND TO HISTORY ========== - - recordData.history.push({ contracts, timestamp: jsonData.timestamp, commitHash: jsonData.commit }); - - // ========== SAVE CHANGES ========== - - // Create file if it doesn't exist - const directoryPath = path.dirname(recordFilePath); - if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath, { recursive: true }); - } - - // Write to file - fs.writeFileSync(recordFilePath, JSON.stringify(recordData, null, 2), "utf8"); - - console.log(`Extraction complete!`); - - return recordData; -} - -// ========== HELPERS ========== - -// IN: contract address and RPC URL -// OUT: contract version (.version) -async function getVersion(contractAddress, rpcUrl) { - try { - return { - version: execSync(`cast call ${contractAddress} 'version()(string)' --rpc-url ${rpcUrl}`, { - encoding: "utf-8", - }).trim(), - }; // note: update if not using cast - } catch (e) { - if (!e.message.includes("execution reverted")) console.log("ERROR", e); // contract does not implement version(), log otherwise - return { version: undefined }; - } -} - -// IN: contract ABI and input values -// OUT: mappings of input names to values -function matchConstructorInputs(abi, inputData) { - const inputMapping = {}; - - const constructorFunc = abi.find((func) => func.type === "constructor"); - - if (constructorFunc && inputData) { - constructorFunc.inputs.forEach((input, index) => { - inputMapping[input.name] = inputData[index]; - }); - } - - return inputMapping; -} - -// IN: contract name -// OUT: contract ABI -function getABI(contractName) { - const filePath = path.join(__dirname, `../../out/${contractName}.sol/${contractName}.json`); - const fileData = fs.readFileSync(filePath, "utf8"); - const abi = JSON.parse(fileData).abi; - return abi; -} - -// Note: Ensures contract artifacts are up-to-date. -function prepareArtifacts() { - console.log(`Preparing artifacts...`); - - execSync("forge clean"); - execSync("forge build"); - - console.log(`Artifacts ready. Continuing.`); -} - -module.exports = { extractAndSaveJson }; diff --git a/script/util/generateMarkdown.js b/script/util/generateMarkdown.js deleted file mode 100644 index acc9a1b..0000000 --- a/script/util/generateMarkdown.js +++ /dev/null @@ -1,276 +0,0 @@ -const { execSync } = require("child_process"); -const { readFileSync, existsSync, writeFileSync } = require("fs"); -const { join } = require("path"); - -const projectGitUrl = getProjectUrl(); -const projectName = getProjectName(); - -function generateAndSaveMarkdown(input) { - console.log("Generating markdown..."); - let out = `# ${projectName}\n\n`; - - out += `\n### Table of Contents\n- [Summary](#summary)\n- [Contracts](#contracts)\n- `; - out += Object.keys(input.latest) - .map( - (c) => - `[${c.replace(/([A-Z])/g, " $1").trim()}](#${c - .replace(/([A-Z])/g, "-$1") - .trim() - .slice(1) - .toLowerCase()})`, - ) - .join("\n\t- "); - out += `\n- [Deployment History](#deployment-history)`; - const { deploymentHistoryMd, allVersions } = generateDeploymentHistory(input.history, input.latest, input.chainId); - out += Object.keys(allVersions) - .map((v) => `\n\t- [${v}](#${v.replace(/\./g, "")})`) - .join(""); - - out += `\n\n## Summary - - - - - -`; - out += Object.entries(input.latest) - .map( - ([contractName, { address, version }]) => - ` - - - - `, - ) - .join("\n"); - out += `
ContractAddressVersion
${contractName}${getEtherscanLinkAnchor(input.chainId, address)}${version || `N/A`}
\n`; - - out += `\n## Contracts\n\n`; - - out += Object.entries(input.latest) - .map( - ([ - contractName, - { address, deploymentTxn, version, commitHash, timestamp, proxyType, implementation, proxyAdmin }, - ]) => `### ${contractName.replace(/([A-Z])/g, " $1").trim()} - - Address: ${getEtherscanLinkMd(input.chainId, address)} - - Deployment Txn: ${getEtherscanLinkMd(input.chainId, deploymentTxn, "tx")} - - ${typeof version === "undefined" ? "" : `Version: [${version}](${projectGitUrl}/releases/tag/${version})`} - - Commit Hash: [${commitHash.slice(0, 7)}](${projectGitUrl}/commit/${commitHash}) - - ${prettifyTimestamp(timestamp)} - ${generateProxyInformationIfProxy({ - address, - contractName, - proxyType, - implementation, - proxyAdmin, - history: input.history, - chainId: input.chainId, - })}`, - ) - .join("\n\n --- \n\n"); - - out += ` - - ---- - - - ### Deployment History - - ${deploymentHistoryMd}`; - - writeFileSync(join(__dirname, `../../deployments/${input.chainId}.md`), out, "utf-8"); - console.log("Generation complete!"); -} - -function getEtherscanLink(chainId, address, slug = "address") { - chainId = parseInt(chainId); - switch (chainId) { - case 1: - return `https://etherscan.io/${slug}/${address}`; - case 5: - return `https://goerli.etherscan.io/${slug}/${address}`; - case 11155111: - return `https://sepolia.etherscan.io/${slug}/${address}`; - case 31337: - return ``; - default: - return `https://blockscan.com/${slug}/${address}`; - } -} -function getEtherscanLinkMd(chainId, address, slug = "address") { - const etherscanLink = getEtherscanLink(chainId, address, slug); - return etherscanLink.length ? `[${address}](${etherscanLink})` : address; -} -function getEtherscanLinkAnchor(chainId, address, slug = "address") { - const etherscanLink = getEtherscanLink(chainId, address, slug); - return etherscanLink.length ? `${address}` : address; -} - -function generateProxyInformationIfProxy({ - address, - contractName, - proxyType, - implementation, - proxyAdmin, - history, - chainId, -}) { - let out = ``; - if (typeof proxyType === "undefined") return out; - out += `\n\n_Proxy Information_\n\n`; - out += `\n\nProxy Type: ${proxyType}\n\n`; - out += `\n\nImplementation: ${getEtherscanLinkMd(chainId, implementation)}\n\n`; - out += `\n\nProxy Admin: ${getEtherscanLinkMd(chainId, proxyAdmin)}\n\n`; - - const historyOfProxy = history.filter((h) => h?.contracts[contractName]?.address === address); - if (historyOfProxy.length === 0) return out; - out += `\n`; - out += ` -
- Implementation History - - - - - - ${historyOfProxy - .map( - ({ - contracts: { - [contractName]: { implementation, version }, - }, - commitHash, - }) => ` - - - - - `, - ) - .join("")} -
VersionAddressCommit Hash
${version}${getEtherscanLinkAnchor(chainId, implementation)}${commitHash.slice(0, 7)}
-
- `; - return out; -} - -function generateDeploymentHistory(history, latest, chainId) { - let allVersions = {}; - if (history.length === 0) { - const inputPath = join(__dirname, "../1.0.0/input.json"); - const input = JSON.parse((existsSync(inputPath) && readFileSync(inputPath, "utf-8")) || `{"${chainId}":{}}`)[ - chainId - ]; - allVersions = Object.entries(latest).reduce((obj, [contractName, contract]) => { - if (typeof contract.version === "undefined") return obj; - if (!obj[contract.version]) obj[contract.version] = []; - obj[contract.version].push({ contract, contractName, input }); - return obj; - }, {}); - } else { - allVersions = history.reduce((obj, { contracts, input, timestamp, commitHash }) => { - Object.entries(contracts).forEach(([contractName, contract]) => { - if (typeof contract.version === "undefined") return; - if (!obj[contract.version]) obj[contract.version] = []; - obj[contract.version].push({ contract: { ...contract, timestamp, commitHash }, contractName, input }); - }); - return obj; - }, {}); - } - - let out = ``; - out += Object.entries(allVersions) - .map( - ([version, contractInfos]) => ` - ### [${version}](${projectGitUrl}/releases/tag/${version}) - - ${prettifyTimestamp(contractInfos[0].contract.timestamp)} - - Commit Hash: [${contractInfos[0].contract.commitHash.slice(0, 7)}](${projectGitUrl}/commit/${ - contractInfos[0].contract.commitHash - }) - - Deployed contracts: - - - ${contractInfos - .map( - ({ contract, contractName }) => ` -
- ${contractName - .replace(/([A-Z])/g, " $1") - .trim()}${ - contract.proxyType - ? ` (Implementation)` - : `` - }${ - isTransaction(contract.input.initializationTxn) - ? ` (Initialization Txn)` - : `` - } - ${ - Object.keys(contract.input.constructor).length - ? ` - - - - - - ${Object.entries(contract.input.constructor) - .map( - ([key, value]) => ` - - - - `, - ) - .join("\n")} -
ParameterValue
${key}${ - isAddress(value) || isTransaction(value) - ? getEtherscanLinkAnchor(chainId, value, isTransaction(value) ? "tx" : "address") - : value - }
- ` - : `` - } -
- `, - ) - .join("\n- ")} - `, - ) - .join("\n\n"); - - return { deploymentHistoryMd: out, allVersions }; -} - -function prettifyTimestamp(timestamp) { - return new Date(timestamp * 1000).toUTCString().replace("GMT", "UTC"); -} - -function isTransaction(str) { - return /^0x([A-Fa-f0-9]{64})$/.test(str); -} - -function isAddress(str) { - return /^0x([A-Fa-f0-9]{40})$/.test(str); -} - -function getProjectUrl() { - return execSync(`git remote get-url origin`, { encoding: "utf-8" }) - .trim() - .replace(/\.git$/, ""); -} - -function getProjectName() { - return execSync(`git remote get-url origin | cut -d '/' -f 5 | cut -d '.' -f 1`, { encoding: "utf-8" }).trim(); -} - -module.exports = { generateAndSaveMarkdown }; diff --git a/test/1.0.0/Counter.t.sol b/test/1.0.0/Counter.t.sol index f2e1d63..1486a1c 100644 --- a/test/1.0.0/Counter.t.sol +++ b/test/1.0.0/Counter.t.sol @@ -8,7 +8,7 @@ import "script/1.0.0/Deploy.s.sol"; abstract contract BeforeScript is Test, TestHelpers, Deploy { function setUp() public virtual { - _run_AllNew(); + deployCounter_NoInit(makeAddr("")); } } @@ -19,8 +19,7 @@ contract CounterTest_Zero is BeforeScript { } function test_Initializes(uint256 number) public { - input[block.chainid].Counter.number = number; - _run_AllNew(); + counter.initialize(number); assertEq(counter.number(), number); } } From c7c2f5c468d9a841c089ca6c8e5fa7fb229b0c09 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Tue, 28 Nov 2023 18:08:57 +0100 Subject: [PATCH 67/86] fix: simplify 1/2 --- script/1.0.0/Deploy.s.sol | 8 +-- script/1.0.0/Input.sol | 32 --------- script/1.0.0/Inputs.sol | 22 ++++++ script/deployers/DeployCounter.s.sol | 4 ++ script/util/deployer_template | 36 ---------- script/util/generateDeployer.js | 100 --------------------------- test/1.0.0/Counter.t.sol | 7 +- 7 files changed, 31 insertions(+), 178 deletions(-) delete mode 100644 script/1.0.0/Input.sol create mode 100644 script/1.0.0/Inputs.sol delete mode 100644 script/util/deployer_template delete mode 100644 script/util/generateDeployer.js diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol index 3a3694f..3fd0a88 100644 --- a/script/1.0.0/Deploy.s.sol +++ b/script/1.0.0/Deploy.s.sol @@ -4,17 +4,17 @@ pragma solidity 0.8.22; import "forge-std/Script.sol"; import "script/util/ScriptHelpers.sol"; -import "./Input.sol"; +import "./Inputs.sol"; import "script/deployers/DeployCounter.s.sol"; -contract Deploy is Script, ScriptHelpers, Input, CounterDeployer { +contract Deploy is Script, ScriptHelpers, Inputs, CounterDeployer { using stdJson for string; function run() public { - deployCounter_NoInit(getInput().ProxyAdmin.initialOwner); + deployCounter_NoInit(input.proxyAdminOwner); } function _run_AllNew() internal { - deployCounter(getInput().ProxyAdmin.initialOwner, getInput().Counter.number); + deployCounter(input.proxyAdminOwner, input.Counter.number); } } diff --git a/script/1.0.0/Input.sol b/script/1.0.0/Input.sol deleted file mode 100644 index db61cf0..0000000 --- a/script/1.0.0/Input.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.22; - -import "forge-std/Script.sol"; -import "script/util/ScriptHelpers.sol"; - -import "script/deployers/DeployCounter.s.sol"; - -abstract contract Input { - struct ProxyAdminInput { - address initialOwner; - } - - struct CounterInput { - uint256 number; - } - - struct NewInput { - ProxyAdminInput ProxyAdmin; - CounterInput Counter; - } - - mapping(uint256 chainId => NewInput input) internal input; - - constructor() { - input[31_337] = NewInput({ProxyAdmin: ProxyAdminInput({initialOwner: 0x356f394005D3316ad54d8f22b40D02Cd539A4a3C}), Counter: CounterInput({number: 10})}); - } - - function getInput() internal view returns (NewInput memory) { - return input[block.chainid]; - } -} diff --git a/script/1.0.0/Inputs.sol b/script/1.0.0/Inputs.sol new file mode 100644 index 0000000..346030b --- /dev/null +++ b/script/1.0.0/Inputs.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.22; + +import {CounterInput} from "script/deployers/DeployCounter.s.sol"; + +abstract contract Inputs { + struct Input { + address proxyAdminOwner; + CounterInput Counter; + } + + Input internal input; + + mapping(uint256 chainId => Input input) internal _input; + + constructor() { + _input[31_337] = Input(0x356f394005D3316ad54d8f22b40D02Cd539A4a3C, CounterInput({number: 10})); + + // Do NOT remove. Sets the input for the current chain. + input = _input[block.chainid]; + } +} diff --git a/script/deployers/DeployCounter.s.sol b/script/deployers/DeployCounter.s.sol index 715fbcb..214a0dc 100644 --- a/script/deployers/DeployCounter.s.sol +++ b/script/deployers/DeployCounter.s.sol @@ -7,6 +7,10 @@ import "src/Counter.sol"; import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +struct CounterInput { + uint256 number; +} + abstract contract CounterDeployer is Script { Counter internal counter; ProxyAdmin internal counterProxyAdmin; diff --git a/script/util/deployer_template b/script/util/deployer_template deleted file mode 100644 index 1948357..0000000 --- a/script/util/deployer_template +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.22; - -import "forge-std/Script.sol"; - -import ""; -import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; - -abstract contract Deployer is Script { - internal ; - ProxyAdmin internal ProxyAdmin; - address internal Logic; - - function deploy(address proxyAdminOwner, ) internal { - bytes memory initData = abi.encodeCall(.initialize, ()); - - _deploy(proxyAdminOwner, initData); - } - - function deploy_NoInit(address proxyAdminOwner) internal { - _deploy(proxyAdminOwner, ""); - } - - function _deploy(address proxyAdminOwner, bytes memory initData) private { - vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - - Logic = address(new ()); - = (address(new TransparentUpgradeableProxy(Logic, proxyAdminOwner, initData))); - - vm.stopBroadcast(); - - ProxyAdmin = - ProxyAdmin(address(uint160(uint256(vm.load(address(), hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"))))); - } -} diff --git a/script/util/generateDeployer.js b/script/util/generateDeployer.js deleted file mode 100644 index 896286c..0000000 --- a/script/util/generateDeployer.js +++ /dev/null @@ -1,100 +0,0 @@ -const fs = require("fs"); -const path = require("path"); - -// Function to replace occurrences of 'Example', 'uint256 arg', and 'arg' in a file -const replaceInFile = (filePath, newFilePath, replacementExample, replacementArgs, replacementPathToExample) => { - fs.readFile(filePath, "utf8", (err, data) => { - if (err) { - return console.error(err); - } - - let regexExample = new RegExp("", "g"); - let regexExampleVar = new RegExp("", "g"); - let regexArgs = new RegExp("", "g"); - let regexArgsNames = new RegExp("", "g"); - let regexPathToExample = new RegExp("", "g"); - - let updatedData = data.replace(regexExample, replacementExample); - updatedData = updatedData.replace( - regexExampleVar, - replacementExample.charAt(0).toLowerCase() + replacementExample.slice(1) - ); - updatedData = updatedData.replace(regexArgs, replacementArgs); - updatedData = updatedData.replace(regexArgsNames, processString(replacementArgs)); - updatedData = updatedData.replace(regexPathToExample, replacementPathToExample); - - fs.writeFile(newFilePath, updatedData, "utf8", (err) => { - if (err) { - console.error(err); - } else { - console.log("Deployer generated."); - - if (replacementArgs.length === 0) { - fs.readFile(newFilePath, "utf8", (err, data) => { - if (err) { - return console.error(err); - } - - // Find the index of the first occurrence of "address proxyAdmin" and replace it - const index = data.indexOf("address proxyAdmin,\n"); - if (index !== -1) { - const newData = - data.slice(0, index) + "address proxyAdmin" + data.slice(index + "address proxyAdmin,\n".length); - // Write the updated content back to the file - fs.writeFile(newFilePath, newData, "utf8", (err) => { - if (err) return console.error(err); - }); - } else { - console.log('No match found for "address proxyAdmin," in the file.'); - } - }); - } - } - }); - }); -}; - -function processString(inputString) { - if (inputString.includes(",")) { - const words = inputString.split(","); - const lastWords = words.map((word) => word.trim().split(" ").pop()); - return lastWords.join(", "); - } else { - return inputString.trim().split(" ").pop(); - } -} - -// Replace occurrences in the specified file with the provided arguments -const filePath = "script/util/deployer_template"; -const replacementPathToExample = process.argv[2]; -const replacementArgs = process.argv[3]; -const newFilePath = process.argv[4]; -let replacementExample; - -// Extract the file name from the path by splitting the string based on the '/' delimiter -const parts = replacementPathToExample.split("/"); -// Get the last part of the path, which is the file name with the extension -const fileNameWithExtension = parts[parts.length - 1]; -// Split the file name by the dot('.') to get the name and the extension separately -const fileNameParts = fileNameWithExtension.split("."); -// Check if there is more than one element in the fileNameParts array -if (fileNameParts.length > 1) { - // Join the parts of the file name excluding the last element (the extension) - replacementExample = fileNameParts.slice(0, -1).join("."); -} else { - // The file name as it is if no extension is found - replacementExample = fileNameParts[0]; -} - -if (!replacementPathToExample || !newFilePath) { - console.error("Usage: node script/util/generateDeployer.js [init params] "); - process.exit(1); -} - -let filePathPrefix = newFilePath; - -const formattedPath = path.join(filePathPrefix, "Deploy" + replacementExample + ".s.sol"); - -replaceInFile(filePath, formattedPath, replacementExample, replacementArgs, replacementPathToExample); - -// TODO: Format the new file diff --git a/test/1.0.0/Counter.t.sol b/test/1.0.0/Counter.t.sol index 1486a1c..f62a93a 100644 --- a/test/1.0.0/Counter.t.sol +++ b/test/1.0.0/Counter.t.sol @@ -6,18 +6,13 @@ import "test/util/TestHelpers.sol"; import "script/1.0.0/Deploy.s.sol"; -abstract contract BeforeScript is Test, TestHelpers, Deploy { +abstract contract BeforeScript is Test, TestHelpers, CounterDeployer { function setUp() public virtual { deployCounter_NoInit(makeAddr("")); } } contract CounterTest_Zero is BeforeScript { - function test_RevertsIf_ImplementationInitialized() public { - vm.expectRevert(Initializable.InvalidInitialization.selector); - Counter(counterLogic).initialize(1); - } - function test_Initializes(uint256 number) public { counter.initialize(number); assertEq(counter.number(), number); From 177d6ff0da96392d1a58eec4622885a213d78cef Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Tue, 28 Nov 2023 18:09:46 +0100 Subject: [PATCH 68/86] forge install: deployment-log-generator --- .gitmodules | 3 +++ lib/deployment-log-generator | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/deployment-log-generator diff --git a/.gitmodules b/.gitmodules index 15e3ba1..c9426e7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [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 diff --git a/lib/deployment-log-generator b/lib/deployment-log-generator new file mode 160000 index 0000000..bd1b716 --- /dev/null +++ b/lib/deployment-log-generator @@ -0,0 +1 @@ +Subproject commit bd1b7166476cca7a21239fb5bf1ed71bbb60b6f2 From a2c8114c4f9c9a982371362d7e9cd2c2339a8b6d Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Tue, 28 Nov 2023 18:10:01 +0100 Subject: [PATCH 69/86] forge install: storage-layout-checker --- .gitmodules | 3 +++ lib/storage-layout-checker | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/storage-layout-checker diff --git a/.gitmodules b/.gitmodules index c9426e7..d70593c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "lib/deployment-log-generator"] path = lib/deployment-log-generator url = https://github.com/0xPolygon/deployment-log-generator +[submodule "lib/storage-layout-checker"] + path = lib/storage-layout-checker + url = https://github.com/0xPolygon/storage-layout-checker diff --git a/lib/storage-layout-checker b/lib/storage-layout-checker new file mode 160000 index 0000000..1503f4c --- /dev/null +++ b/lib/storage-layout-checker @@ -0,0 +1 @@ +Subproject commit 1503f4c782bb17f9a628c8761cadcf1dbd7551b9 From 2d36094275815fea3c3ae2cc197990829bb33d39 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Tue, 28 Nov 2023 18:10:49 +0100 Subject: [PATCH 70/86] forge install: contract-deployer-template --- .gitmodules | 3 +++ lib/contract-deployer-template | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/contract-deployer-template diff --git a/.gitmodules b/.gitmodules index d70593c..6043cb4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "lib/storage-layout-checker"] path = lib/storage-layout-checker url = https://github.com/0xPolygon/storage-layout-checker +[submodule "lib/contract-deployer-template"] + path = lib/contract-deployer-template + url = https://github.com/0xPolygon/contract-deployer-template diff --git a/lib/contract-deployer-template b/lib/contract-deployer-template new file mode 160000 index 0000000..5102b13 --- /dev/null +++ b/lib/contract-deployer-template @@ -0,0 +1 @@ +Subproject commit 5102b139dcec4e42552237d7860ecafca611d176 From 4a0653593de613d1a5ab0ad006c425b31c1004d4 Mon Sep 17 00:00:00 2001 From: Simon Dosch Date: Wed, 29 Nov 2023 16:38:46 +0100 Subject: [PATCH 71/86] add features in Readme --- README.md | 14 ++++++++++++++ docs/autogen/src/README.md | 16 +++++++++++++++- .../src/src/Counter.sol/contract.Counter.md | 9 ++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2859bb4..2f76f87 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,25 @@ TODO: summary of the features of the template repo #### Table of Contents +- [Features](#features) - [Setup](#setup) - [Deployment](#deployment) - [Docs](#docs) - [Contributing](#contributing) +## Features + +- **auto-generated docs** with `./doc.gen.sh` +- **storage check** when upgrading contracts +- **pre-commit** hooks (need to be installed once. Config in `.pre-commit-config.yaml`) +- **report generation** with `extract.js` (uses broadcast file to generate full report of deployments) +- **Forced code-review** (`CODEOWNERS`) +- **PR template** (`PULL_REQUEST_TEMPLATE.md`) +- auto-generate deploy helpers (generateDeployer.js) +- Solidity file specifying inputs for deploy scripts (Input.sol) +- _solady_ for gas-sensitive use cases +- IVersioned interface + ## Setup Follow these steps to set up your local environment: diff --git a/docs/autogen/src/README.md b/docs/autogen/src/README.md index 2f79c48..2f76f87 100644 --- a/docs/autogen/src/README.md +++ b/docs/autogen/src/README.md @@ -7,11 +7,25 @@ TODO: summary of the features of the template repo #### Table of Contents +- [Features](#features) - [Setup](#setup) - [Deployment](#deployment) - [Docs](#docs) - [Contributing](#contributing) +## Features + +- **auto-generated docs** with `./doc.gen.sh` +- **storage check** when upgrading contracts +- **pre-commit** hooks (need to be installed once. Config in `.pre-commit-config.yaml`) +- **report generation** with `extract.js` (uses broadcast file to generate full report of deployments) +- **Forced code-review** (`CODEOWNERS`) +- **PR template** (`PULL_REQUEST_TEMPLATE.md`) +- auto-generate deploy helpers (generateDeployer.js) +- Solidity file specifying inputs for deploy scripts (Input.sol) +- _solady_ for gas-sensitive use cases +- IVersioned interface + ## Setup Follow these steps to set up your local environment: @@ -45,4 +59,4 @@ If you want to contribute to this project, please check [CONTRIBUTING.md](CONTRI --- -Copyright (C) 2023 PT Services DMCC +© 2023 PT Services DMCC diff --git a/docs/autogen/src/src/Counter.sol/contract.Counter.md b/docs/autogen/src/src/Counter.sol/contract.Counter.md index 27b0485..d99d846 100644 --- a/docs/autogen/src/src/Counter.sol/contract.Counter.md +++ b/docs/autogen/src/src/Counter.sol/contract.Counter.md @@ -1,5 +1,5 @@ # Counter -[Git Source](https://github.com/0xPolygon/foundry-template/blob/55b07186cd4779cbe55cc2f262f992aeabaf34ad/src/Counter.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/2d36094275815fea3c3ae2cc197990829bb33d39/src/Counter.sol) **Inherits:** [ICounter](/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md), Initializable @@ -14,6 +14,13 @@ uint256 public number; ## Functions +### constructor + + +```solidity +constructor(); +``` + ### initialize From 8bb2c11306cde7866fac4ec4c4779e957972f0d4 Mon Sep 17 00:00:00 2001 From: Zero Ekkusu <94782988+ZeroEkkusu@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:57:12 +0100 Subject: [PATCH 72/86] fix(script): make inputs safer --- script/1.0.0/Deploy.s.sol | 6 +++--- script/1.0.0/Inputs.sol | 10 +++++++--- script/deployers/DeployCounter.s.sol | 4 ++-- test/1.0.0/Counter.t.sol | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol index 3fd0a88..596d342 100644 --- a/script/1.0.0/Deploy.s.sol +++ b/script/1.0.0/Deploy.s.sol @@ -11,10 +11,10 @@ contract Deploy is Script, ScriptHelpers, Inputs, CounterDeployer { using stdJson for string; function run() public { - deployCounter_NoInit(input.proxyAdminOwner); + deployCounter_NoInit(input.proxyAdmin.initialOwner); } - function _run_AllNew() internal { - deployCounter(input.proxyAdminOwner, input.Counter.number); + function _run_All() internal { + deployCounter(input.proxyAdmin.initialOwner, input.counter); } } diff --git a/script/1.0.0/Inputs.sol b/script/1.0.0/Inputs.sol index 346030b..44d38fc 100644 --- a/script/1.0.0/Inputs.sol +++ b/script/1.0.0/Inputs.sol @@ -4,9 +4,13 @@ pragma solidity 0.8.22; import {CounterInput} from "script/deployers/DeployCounter.s.sol"; abstract contract Inputs { + struct ProxyAdminInput { + address initialOwner; + } + struct Input { - address proxyAdminOwner; - CounterInput Counter; + ProxyAdminInput proxyAdmin; + CounterInput counter; } Input internal input; @@ -14,7 +18,7 @@ abstract contract Inputs { mapping(uint256 chainId => Input input) internal _input; constructor() { - _input[31_337] = Input(0x356f394005D3316ad54d8f22b40D02Cd539A4a3C, CounterInput({number: 10})); + _input[31_337] = Input(ProxyAdminInput({initialOwner: 0x356f394005D3316ad54d8f22b40D02Cd539A4a3C}), CounterInput({number: 10})); // Do NOT remove. Sets the input for the current chain. input = _input[block.chainid]; diff --git a/script/deployers/DeployCounter.s.sol b/script/deployers/DeployCounter.s.sol index 214a0dc..1696100 100644 --- a/script/deployers/DeployCounter.s.sol +++ b/script/deployers/DeployCounter.s.sol @@ -16,8 +16,8 @@ abstract contract CounterDeployer is Script { ProxyAdmin internal counterProxyAdmin; address internal counterLogic; - function deployCounter(address proxyAdminOwner, uint256 number) internal { - bytes memory initData = abi.encodeCall(Counter.initialize, (number)); + function deployCounter(address proxyAdminOwner, CounterInput memory input) internal { + bytes memory initData = abi.encodeCall(Counter.initialize, (input.number)); _deployCounter(proxyAdminOwner, initData); } diff --git a/test/1.0.0/Counter.t.sol b/test/1.0.0/Counter.t.sol index f62a93a..43250da 100644 --- a/test/1.0.0/Counter.t.sol +++ b/test/1.0.0/Counter.t.sol @@ -21,7 +21,7 @@ contract CounterTest_Zero is BeforeScript { abstract contract AfterScript is Test, TestHelpers, Deploy { function setUp() public virtual { - _run_AllNew(); + _run_All(); } } From e3be09e288733981d374eff2b25e6c283a17801f Mon Sep 17 00:00:00 2001 From: gretzke Date: Thu, 30 Nov 2023 01:58:19 +0100 Subject: [PATCH 73/86] adjust nvmrc and fix link --- .nvmrc | 2 +- CONTRIBUTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.nvmrc b/.nvmrc index 3c79f30..25bf17f 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.16.0 \ No newline at end of file +18 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2500aff..6d74909 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ 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/#post-commit) +- [Install pre-commit](https://pre-commit.com/#installation) - Install pre commit hooks: `pre-commit install` ## Pre-commit Hooks From eda6f3ab7028eea13a56aa67ce6bf5bf8d462a1c Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 1 Dec 2023 01:50:36 +0100 Subject: [PATCH 74/86] fix pre-commit --- docs/autogen/src/src/Counter.sol/contract.Counter.md | 4 ++-- .../src/src/interface/ICounter.sol/interface.ICounter.md | 4 ++-- script/util/doc_gen.sh | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/autogen/src/src/Counter.sol/contract.Counter.md b/docs/autogen/src/src/Counter.sol/contract.Counter.md index d99d846..c2acdc5 100644 --- a/docs/autogen/src/src/Counter.sol/contract.Counter.md +++ b/docs/autogen/src/src/Counter.sol/contract.Counter.md @@ -1,8 +1,8 @@ # Counter -[Git Source](https://github.com/0xPolygon/foundry-template/blob/2d36094275815fea3c3ae2cc197990829bb33d39/src/Counter.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/e3be09e288733981d374eff2b25e6c283a17801f/src/Counter.sol) **Inherits:** -[ICounter](/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md), Initializable +[ICounter](/src/interface/ICounter.sol/interface.ICounter.md), Initializable ## State Variables diff --git a/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md b/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md index d207307..b0392a4 100644 --- a/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md +++ b/docs/autogen/src/src/interface/ICounter.sol/interface.ICounter.md @@ -1,8 +1,8 @@ # ICounter -[Git Source](https://github.com/0xPolygon/foundry-template/blob/55b07186cd4779cbe55cc2f262f992aeabaf34ad/src/interface/ICounter.sol) +[Git Source](https://github.com/0xPolygon/foundry-template/blob/e3be09e288733981d374eff2b25e6c283a17801f/src/interface/ICounter.sol) **Inherits:** -[IVersioned](/docs/autogen/src/src/interface/IVersioned.sol/interface.IVersioned.md) +[IVersioned](/src/interface/IVersioned.sol/interface.IVersioned.md) ## Functions diff --git a/script/util/doc_gen.sh b/script/util/doc_gen.sh index 5098b44..97ed58c 100755 --- a/script/util/doc_gen.sh +++ b/script/util/doc_gen.sh @@ -1,5 +1,6 @@ #!/bin/bash +foundryup # generate docs forge doc -b -o docs/autogen @@ -11,8 +12,6 @@ files=$(git diff --name-only -- 'docs/autogen/*') for file in $files; do # Get the diff for the file, only lines that start with - or + diff=$(git diff $file | grep '^[+-][^+-]') - echo $file - echo "$diff" | wc -l # 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 From 40c6b99d9d366332a2f140989d3ad674370f4998 Mon Sep 17 00:00:00 2001 From: gretzke Date: Sat, 2 Dec 2023 05:16:14 +0100 Subject: [PATCH 75/86] forge install: contract-deployer-template 0.1.0 --- lib/contract-deployer-template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contract-deployer-template b/lib/contract-deployer-template index 5102b13..06adad7 160000 --- a/lib/contract-deployer-template +++ b/lib/contract-deployer-template @@ -1 +1 @@ -Subproject commit 5102b139dcec4e42552237d7860ecafca611d176 +Subproject commit 06adad7f2acb59ca10dac8703e1e30d4ed9a21c9 From 0b5490ddd6138509fdb89844bd28ec803e1f7777 Mon Sep 17 00:00:00 2001 From: gretzke Date: Sat, 2 Dec 2023 05:29:49 +0100 Subject: [PATCH 76/86] Update scripts and tests for new deployer template --- script/1.0.0/Deploy.s.sol | 20 ---------- script/1.0.0/Inputs.sol | 26 ------------- script/Deploy.s.sol | 17 ++++++++ script/config.json | 7 ---- ...loyCounter.s.sol => CounterDeployer.s.sol} | 39 ++++++++++--------- test/{1.0.0 => }/Counter.t.sol | 18 ++++++--- 6 files changed, 50 insertions(+), 77 deletions(-) delete mode 100644 script/1.0.0/Deploy.s.sol delete mode 100644 script/1.0.0/Inputs.sol create mode 100644 script/Deploy.s.sol delete mode 100644 script/config.json rename script/deployers/{DeployCounter.s.sol => CounterDeployer.s.sol} (50%) rename test/{1.0.0 => }/Counter.t.sol (65%) diff --git a/script/1.0.0/Deploy.s.sol b/script/1.0.0/Deploy.s.sol deleted file mode 100644 index 596d342..0000000 --- a/script/1.0.0/Deploy.s.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.22; - -import "forge-std/Script.sol"; -import "script/util/ScriptHelpers.sol"; - -import "./Inputs.sol"; -import "script/deployers/DeployCounter.s.sol"; - -contract Deploy is Script, ScriptHelpers, Inputs, CounterDeployer { - using stdJson for string; - - function run() public { - deployCounter_NoInit(input.proxyAdmin.initialOwner); - } - - function _run_All() internal { - deployCounter(input.proxyAdmin.initialOwner, input.counter); - } -} diff --git a/script/1.0.0/Inputs.sol b/script/1.0.0/Inputs.sol deleted file mode 100644 index 44d38fc..0000000 --- a/script/1.0.0/Inputs.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.22; - -import {CounterInput} from "script/deployers/DeployCounter.s.sol"; - -abstract contract Inputs { - struct ProxyAdminInput { - address initialOwner; - } - - struct Input { - ProxyAdminInput proxyAdmin; - CounterInput counter; - } - - Input internal input; - - mapping(uint256 chainId => Input input) internal _input; - - constructor() { - _input[31_337] = Input(ProxyAdminInput({initialOwner: 0x356f394005D3316ad54d8f22b40D02Cd539A4a3C}), CounterInput({number: 10})); - - // Do NOT remove. Sets the input for the current chain. - input = _input[block.chainid]; - } -} diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..df53c53 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.22; + +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/config.json b/script/config.json deleted file mode 100644 index 77253a4..0000000 --- a/script/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "defaultRpc": { - "31337": "http://127.0.0.1:8545", - "1": "https://eth.llamarpc.com", - "5": "https://ethereum-goerli.publicnode.com" - } -} diff --git a/script/deployers/DeployCounter.s.sol b/script/deployers/CounterDeployer.s.sol similarity index 50% rename from script/deployers/DeployCounter.s.sol rename to script/deployers/CounterDeployer.s.sol index 1696100..a99f915 100644 --- a/script/deployers/DeployCounter.s.sol +++ b/script/deployers/CounterDeployer.s.sol @@ -1,5 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.22; +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// import "forge-std/Script.sol"; @@ -7,34 +11,33 @@ import "src/Counter.sol"; import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -struct CounterInput { - uint256 number; -} - abstract contract CounterDeployer is Script { Counter internal counter; ProxyAdmin internal counterProxyAdmin; - address internal counterLogic; - - function deployCounter(address proxyAdminOwner, CounterInput memory input) internal { - bytes memory initData = abi.encodeCall(Counter.initialize, (input.number)); - - _deployCounter(proxyAdminOwner, initData); - } + address internal counterImplementation; - function deployCounter_NoInit(address proxyAdminOwner) internal { - _deployCounter(proxyAdminOwner, ""); - } + function deployCounterTransparent(address proxyAdminOwner, uint256 initialNumber) + internal + returns (address implementation, address proxyAdmin, address proxy) + { + bytes memory initData = abi.encodeCall(Counter.initialize, (initialNumber)); - function _deployCounter(address proxyAdminOwner, bytes memory initData) private { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - counterLogic = address(new Counter()); - counter = Counter(address(new TransparentUpgradeableProxy(counterLogic, proxyAdminOwner, initData))); + 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/test/1.0.0/Counter.t.sol b/test/Counter.t.sol similarity index 65% rename from test/1.0.0/Counter.t.sol rename to test/Counter.t.sol index 43250da..afa38af 100644 --- a/test/1.0.0/Counter.t.sol +++ b/test/Counter.t.sol @@ -4,24 +4,30 @@ pragma solidity 0.8.22; import "forge-std/Test.sol"; import "test/util/TestHelpers.sol"; -import "script/1.0.0/Deploy.s.sol"; +import "script/deployers/CounterDeployer.s.sol"; abstract contract BeforeScript is Test, TestHelpers, CounterDeployer { function setUp() public virtual { - deployCounter_NoInit(makeAddr("")); + counter = Counter(deployCounterImplementation()); } } contract CounterTest_Zero is BeforeScript { - function test_Initializes(uint256 number) public { + function test_InitialState() public { + assertEq(counter.number(), 0); + } + + function test_RevertsOnInitialization(uint256 number) public { + vm.expectRevert(Initializable.InvalidInitialization.selector); counter.initialize(number); - assertEq(counter.number(), number); } } -abstract contract AfterScript is Test, TestHelpers, Deploy { +abstract contract AfterScript is Test, TestHelpers, CounterDeployer { function setUp() public virtual { - _run_All(); + address proxyAdmin = makeAddr("alice"); + uint256 initialNumber = 10; + deployCounterTransparent(proxyAdmin, initialNumber); } } From 61bc3a731baf3b78a88af277c90b57c4155766fa Mon Sep 17 00:00:00 2001 From: gretzke Date: Sun, 3 Dec 2023 03:26:51 +0100 Subject: [PATCH 77/86] bump solidity version --- foundry.toml | 6 +----- script/Deploy.s.sol | 2 +- script/util/ScriptHelpers.sol | 2 +- src/Counter.sol | 2 +- src/interface/ICounter.sol | 2 +- src/interface/IVersioned.sol | 2 +- test/Counter.t.sol | 2 +- test/util/TestHelpers.sol | 2 +- 8 files changed, 8 insertions(+), 12 deletions(-) diff --git a/foundry.toml b/foundry.toml index 46e9911..baea3d3 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,13 +5,9 @@ libs = ["lib"] optimizer = true optimizer_runs = 999999 via_ir = true -solc = "0.8.22" +solc = "0.8.23" verbosity = 2 ffi = true -fs_permissions = [ - { access = "read", path = "scripts/config.json" }, - { access = "read", path = "script/1.0.0/input.json" } -] remappings = [ "forge-std=lib/forge-std/src", diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index df53c53..a8ee7f8 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.22; +pragma solidity 0.8.23; import "forge-std/Script.sol"; import "script/util/ScriptHelpers.sol"; diff --git a/script/util/ScriptHelpers.sol b/script/util/ScriptHelpers.sol index 5e8adbb..18a1247 100644 --- a/script/util/ScriptHelpers.sol +++ b/script/util/ScriptHelpers.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.22; +pragma solidity 0.8.23; import "forge-std/Script.sol"; diff --git a/src/Counter.sol b/src/Counter.sol index b579bd5..37782ec 100644 --- a/src/Counter.sol +++ b/src/Counter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.22; +pragma solidity 0.8.23; import {ICounter, IVersioned} from "./interface/ICounter.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; diff --git a/src/interface/ICounter.sol b/src/interface/ICounter.sol index d499905..57985b4 100644 --- a/src/interface/ICounter.sol +++ b/src/interface/ICounter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.22; +pragma solidity 0.8.23; import {IVersioned} from "./IVersioned.sol"; diff --git a/src/interface/IVersioned.sol b/src/interface/IVersioned.sol index dc6caae..0897f48 100644 --- a/src/interface/IVersioned.sol +++ b/src/interface/IVersioned.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.22; +pragma solidity 0.8.23; interface IVersioned { /// @return The version of the contract diff --git a/test/Counter.t.sol b/test/Counter.t.sol index afa38af..04cfd67 100644 --- a/test/Counter.t.sol +++ b/test/Counter.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.22; +pragma solidity 0.8.23; import "forge-std/Test.sol"; import "test/util/TestHelpers.sol"; diff --git a/test/util/TestHelpers.sol b/test/util/TestHelpers.sol index 1d03de4..b524dc5 100644 --- a/test/util/TestHelpers.sol +++ b/test/util/TestHelpers.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.22; +pragma solidity 0.8.23; import "forge-std/Test.sol"; From e78768d8049038300f16c445d6b1125afbe43301 Mon Sep 17 00:00:00 2001 From: gretzke Date: Sun, 3 Dec 2023 04:49:18 +0100 Subject: [PATCH 78/86] do not format autogenerated deployment logs --- .prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierignore b/.prettierignore index e92cb50..45f4529 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,4 @@ lib/ cache/ docs/autogenerated *.sol +deployments/ From cabbe16cf89731006983077ad6578996a7f4ed9c Mon Sep 17 00:00:00 2001 From: gretzke Date: Tue, 5 Dec 2023 03:16:19 +0100 Subject: [PATCH 79/86] update libs --- lib/contract-deployer-template | 2 +- lib/forge-std | 2 +- lib/solady | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/contract-deployer-template b/lib/contract-deployer-template index 06adad7..c068023 160000 --- a/lib/contract-deployer-template +++ b/lib/contract-deployer-template @@ -1 +1 @@ -Subproject commit 06adad7f2acb59ca10dac8703e1e30d4ed9a21c9 +Subproject commit c06802347e69e97574ab7c0a272ad4af5f7f7cef diff --git a/lib/forge-std b/lib/forge-std index f73c73d..2f11269 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 +Subproject commit 2f112697506eab12d433a65fdc31a639548fe365 diff --git a/lib/solady b/lib/solady index cde0a5f..c565332 160000 --- a/lib/solady +++ b/lib/solady @@ -1 +1 @@ -Subproject commit cde0a5fb594da8655ba6bfcdc2e40a7c870c0cc0 +Subproject commit c565332afef2d4f993fde402541487e4e331fddd From 572f3552d65a99e04688c300139e6dcf801c9482 Mon Sep 17 00:00:00 2001 From: gretzke Date: Thu, 7 Dec 2023 01:53:47 +0100 Subject: [PATCH 80/86] MIT + Apache 2 License --- LICENSE-APACHE.md | 201 +++++++++++++++++++++++++++++++++++ LICENSE.md => LICENSE-MIT.md | 0 2 files changed, 201 insertions(+) create mode 100644 LICENSE-APACHE.md rename LICENSE.md => LICENSE-MIT.md (100%) diff --git a/LICENSE-APACHE.md b/LICENSE-APACHE.md new file mode 100644 index 0000000..97c3519 --- /dev/null +++ b/LICENSE-APACHE.md @@ -0,0 +1,201 @@ + 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 2023 PT Services DMCC + +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/LICENSE.md b/LICENSE-MIT.md similarity index 100% rename from LICENSE.md rename to LICENSE-MIT.md From 379ebeeab73ebd9b6a1d08245bf884dd28f384e2 Mon Sep 17 00:00:00 2001 From: gretzke Date: Thu, 7 Dec 2023 03:21:17 +0100 Subject: [PATCH 81/86] Update documentation --- CONTRIBUTING.md | 43 +++++++++----------------------------- README.md | 21 +++++-------------- docs/README.md | 2 +- docs/autogen/src/README.md | 21 +++++-------------- 4 files changed, 21 insertions(+), 66 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d74909..d23fc03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ Follow these steps to set up your local environment for development: ## Pre-commit Hooks -Follow the [installation steps](#install) to enable pre-commit hooks. To ensure consistency in our formatting we use `pre-commit` 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. +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). @@ -118,7 +118,7 @@ Interfaces should be the entrypoint for all contracts. When exploring the a cont ## 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 Info Generator](#deployment-info-generation) to extract information about the version. +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](lib/deployment-log-generator/README.md) 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. @@ -126,15 +126,15 @@ Whenever contracts are modified, only the version of the changed contracts shoul ### Deployer Template -This repo provides a deployer template for consistency between scripts and unit tests. For more information on how to use the template, check [here](#deployer-template-script). +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](lib/contract-deployer-template/README.md). ## Deployment -This repo utilizes versioned deployments. Any changes to a contract should update the version of this specific contract. To deploy a new version of a contract, create a new deployment script in a directory named after the new version of the modified contracts (e.g., `1.0.0`). 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. +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 for consistency between scripts and unit tests. For more information on how to use the template, check [here](#deployer-template-script). +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](lib/contract-deployer-template/README.md). ### Deployment @@ -153,31 +153,7 @@ Deploy the contracts to one of the predefined networks by providing the accordin 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/1.0.0/Deploy.s.sol --broadcast --rpc-url --verify -``` - -### Deployment Info Generation - -A JSON and Markdown file can be generated in the `deployments` directory containing various information about the deployment itself as well as past deployments using the following command. To find out more about versioning of contracts within this repo, check [here](CONTRIBUTING.md#versioning). - -```shell -node script/util/extract.js -``` - -As the `chainId`, provide the chainId of the network the contracts were deployed to as a number. The supplied `version` should be the version of the modified contracts and the sub directory the deployment script is located in (e.g., `1.0.0`). The `scriptName` should be the file name of the script used in the deployment (e.g., `Deploy.s.sol`). - -When upgrading a contract, most of the times just the new implementation is deployed and the actual upgrade is triggered by a governance process or a multisig. The script will check whether the implementation of the upgraded contract was updated to the deployed version and if not, it will fail and not generate any files. - -## Deployer Template Script - -This repo provides a deployer template for consistency between scripts and unit tests. - -A deployer is an abstract contract, meant to be inherited in scripts and tests. A deployer provides type-checks and handles the creation, proxification, and initialization of the contract. It consists of two functions: `deploy` and `deploy_NoInit`. - -To generate a new deployer: - -``` -node script/util/generateDeployer.js [init params] +forge script script/Deploy.s.sol --broadcast --rpc-url --verify ``` ## Releases @@ -187,8 +163,9 @@ The release should include the following: - In case of a MAJOR version - changelog - - summary of new features - summary of breaking changes + - summary of new features + - summary of fixes - In case of a MINOR version - changelog - summary of new features @@ -196,5 +173,5 @@ The release should include the following: - In case of a PATCH version - changelog - summary of fixes -- Deployment information -- TODO +- Deployment information (can be copied from the generated log files) + - Addresses of the deployed contracts diff --git a/README.md b/README.md index 2f76f87..f10da63 100644 --- a/README.md +++ b/README.md @@ -3,29 +3,18 @@ [![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) -TODO: summary of the features of the template repo +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 -- [Features](#features) - [Setup](#setup) - [Deployment](#deployment) - [Docs](#docs) - [Contributing](#contributing) -## Features - -- **auto-generated docs** with `./doc.gen.sh` -- **storage check** when upgrading contracts -- **pre-commit** hooks (need to be installed once. Config in `.pre-commit-config.yaml`) -- **report generation** with `extract.js` (uses broadcast file to generate full report of deployments) -- **Forced code-review** (`CODEOWNERS`) -- **PR template** (`PULL_REQUEST_TEMPLATE.md`) -- auto-generate deploy helpers (generateDeployer.js) -- Solidity file specifying inputs for deploy scripts (Input.sol) -- _solady_ for gas-sensitive use cases -- IVersioned interface - ## Setup Follow these steps to set up your local environment: @@ -44,7 +33,7 @@ This repo utilizes versioned deployments. For more information on how to use for Smart contracts are deployed or upgraded using the following command: ```shell -forge script script/1.0.0/Deploy.s.sol --broadcast --rpc-url --verify +forge script script/Deploy.s.sol --broadcast --rpc-url --verify ``` ## Docs diff --git a/docs/README.md b/docs/README.md index c6b7910..14ae96a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,3 @@ # Documentation -TODO +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/src/README.md b/docs/autogen/src/README.md index 2f76f87..f10da63 100644 --- a/docs/autogen/src/README.md +++ b/docs/autogen/src/README.md @@ -3,29 +3,18 @@ [![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) -TODO: summary of the features of the template repo +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 -- [Features](#features) - [Setup](#setup) - [Deployment](#deployment) - [Docs](#docs) - [Contributing](#contributing) -## Features - -- **auto-generated docs** with `./doc.gen.sh` -- **storage check** when upgrading contracts -- **pre-commit** hooks (need to be installed once. Config in `.pre-commit-config.yaml`) -- **report generation** with `extract.js` (uses broadcast file to generate full report of deployments) -- **Forced code-review** (`CODEOWNERS`) -- **PR template** (`PULL_REQUEST_TEMPLATE.md`) -- auto-generate deploy helpers (generateDeployer.js) -- Solidity file specifying inputs for deploy scripts (Input.sol) -- _solady_ for gas-sensitive use cases -- IVersioned interface - ## Setup Follow these steps to set up your local environment: @@ -44,7 +33,7 @@ This repo utilizes versioned deployments. For more information on how to use for Smart contracts are deployed or upgraded using the following command: ```shell -forge script script/1.0.0/Deploy.s.sol --broadcast --rpc-url --verify +forge script script/Deploy.s.sol --broadcast --rpc-url --verify ``` ## Docs From 27d52d2b56738b078af631a027a1e477da754843 Mon Sep 17 00:00:00 2001 From: gretzke Date: Thu, 7 Dec 2023 03:51:27 +0100 Subject: [PATCH 82/86] forge install: deployment-log-generator 0.1.0 --- lib/deployment-log-generator | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/deployment-log-generator b/lib/deployment-log-generator index bd1b716..9b06687 160000 --- a/lib/deployment-log-generator +++ b/lib/deployment-log-generator @@ -1 +1 @@ -Subproject commit bd1b7166476cca7a21239fb5bf1ed71bbb60b6f2 +Subproject commit 9b06687770ba02ab90f043b7ca49d3c322f0928c From 6856cbaeed826c594431098ecb4ae237a309ad56 Mon Sep 17 00:00:00 2001 From: gretzke Date: Thu, 7 Dec 2023 03:54:41 +0100 Subject: [PATCH 83/86] fix links --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d23fc03..d1eb1dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -118,7 +118,7 @@ Interfaces should be the entrypoint for all contracts. When exploring the a cont ## 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](lib/deployment-log-generator/README.md) to extract information about the version. +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. @@ -126,7 +126,7 @@ Whenever contracts are modified, only the version of the changed contracts shoul ### 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](lib/contract-deployer-template/README.md). +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 @@ -134,7 +134,7 @@ This repo utilizes versioned deployments. Any changes to a contract should updat ### 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](lib/contract-deployer-template/README.md). +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 From 3cf42709b731ffe231e1bbe35ecd20ddd83a81f4 Mon Sep 17 00:00:00 2001 From: gretzke Date: Thu, 7 Dec 2023 18:08:35 +0100 Subject: [PATCH 84/86] update libraries --- lib/contract-deployer-template | 2 +- lib/deployment-log-generator | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/contract-deployer-template b/lib/contract-deployer-template index c068023..0e491fd 160000 --- a/lib/contract-deployer-template +++ b/lib/contract-deployer-template @@ -1 +1 @@ -Subproject commit c06802347e69e97574ab7c0a272ad4af5f7f7cef +Subproject commit 0e491fd153eda12e4535e669f0fd29235cad2673 diff --git a/lib/deployment-log-generator b/lib/deployment-log-generator index 9b06687..a9a6e7a 160000 --- a/lib/deployment-log-generator +++ b/lib/deployment-log-generator @@ -1 +1 @@ -Subproject commit 9b06687770ba02ab90f043b7ca49d3c322f0928c +Subproject commit a9a6e7a81ddd0803041f30647fa867dc67aade72 From 1b170ac292e8d138135d18fe1b62556b509288f4 Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 8 Dec 2023 00:47:14 +0100 Subject: [PATCH 85/86] license update --- LICENSE-APACHE.md | 27 +-------------------------- LICENSE-MIT.md | 2 -- README.md | 15 +++++++++++++++ docs/autogen/src/README.md | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/LICENSE-APACHE.md b/LICENSE-APACHE.md index 97c3519..4f05bc1 100644 --- a/LICENSE-APACHE.md +++ b/LICENSE-APACHE.md @@ -173,29 +173,4 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 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 2023 PT Services DMCC - -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. + END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT.md b/LICENSE-MIT.md index 95817e3..f7f7438 100644 --- a/LICENSE-MIT.md +++ b/LICENSE-MIT.md @@ -1,7 +1,5 @@ MIT License -Copyright (c) 2023 PT Services DMCC - 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. diff --git a/README.md b/README.md index f10da63..2fa34f9 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This template repo is a quick and easy way to get started with a new Solidity pr - [Deployment](#deployment) - [Docs](#docs) - [Contributing](#contributing) +- [License](#license) ## Setup @@ -46,6 +47,20 @@ When exploring the contracts within this repository, it is recommended to start 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/README.md b/docs/autogen/src/README.md index f10da63..2fa34f9 100644 --- a/docs/autogen/src/README.md +++ b/docs/autogen/src/README.md @@ -14,6 +14,7 @@ This template repo is a quick and easy way to get started with a new Solidity pr - [Deployment](#deployment) - [Docs](#docs) - [Contributing](#contributing) +- [License](#license) ## Setup @@ -46,6 +47,20 @@ When exploring the contracts within this repository, it is recommended to start 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 From 332407da02b0c3123d6a558c749b564bce500987 Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 8 Dec 2023 14:48:25 +0100 Subject: [PATCH 86/86] update libraries --- .gitmodules | 3 --- lib/contract-deployer-template | 2 +- lib/deployment-log-generator | 2 +- lib/storage-layout-checker | 1 - 4 files changed, 2 insertions(+), 6 deletions(-) delete mode 160000 lib/storage-layout-checker diff --git a/.gitmodules b/.gitmodules index 6043cb4..712087f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,9 +13,6 @@ [submodule "lib/deployment-log-generator"] path = lib/deployment-log-generator url = https://github.com/0xPolygon/deployment-log-generator -[submodule "lib/storage-layout-checker"] - path = lib/storage-layout-checker - url = https://github.com/0xPolygon/storage-layout-checker [submodule "lib/contract-deployer-template"] path = lib/contract-deployer-template url = https://github.com/0xPolygon/contract-deployer-template diff --git a/lib/contract-deployer-template b/lib/contract-deployer-template index 0e491fd..3101afc 160000 --- a/lib/contract-deployer-template +++ b/lib/contract-deployer-template @@ -1 +1 @@ -Subproject commit 0e491fd153eda12e4535e669f0fd29235cad2673 +Subproject commit 3101afcefef8a677dec4562d1a1b73c74b4364d8 diff --git a/lib/deployment-log-generator b/lib/deployment-log-generator index a9a6e7a..f3fc0bf 160000 --- a/lib/deployment-log-generator +++ b/lib/deployment-log-generator @@ -1 +1 @@ -Subproject commit a9a6e7a81ddd0803041f30647fa867dc67aade72 +Subproject commit f3fc0bfd4a7caee49cf6eb6ac172bc012a339fc1 diff --git a/lib/storage-layout-checker b/lib/storage-layout-checker deleted file mode 160000 index 1503f4c..0000000 --- a/lib/storage-layout-checker +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1503f4c782bb17f9a628c8761cadcf1dbd7551b9