From c1938c2c1c0fe1efd1e39652122eb990b9216baa Mon Sep 17 00:00:00 2001 From: igorroncevic <57319163+igorroncevic@users.noreply.github.com> Date: Tue, 5 May 2026 11:31:01 +0200 Subject: [PATCH 1/4] feat: improvements --- .env.example | 10 +++ .github/actions/setup/action.yml | 19 +++++ .github/workflows/ci.yml | 96 +++++++++++++++++++++ .github/workflows/test.yml | 38 --------- .gitignore | 14 ++++ .pre-commit-config.yaml | 37 +++++++++ .solhint.json | 55 ++++++++++++ .solhintignore | 0 Makefile | 65 +++++++++++++++ README.md | 57 ++++++------- foundry.toml | 34 +++++++- script/.solhint.json | 37 +++++++++ script/shell/mine-salt.sh | 138 +++++++++++++++++++++++++++++++ slither.config.json | 5 ++ test/.solhint.json | 47 +++++++++++ 15 files changed, 576 insertions(+), 76 deletions(-) create mode 100644 .env.example create mode 100644 .github/actions/setup/action.yml create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/test.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .solhint.json create mode 100644 .solhintignore create mode 100644 Makefile create mode 100644 script/.solhint.json create mode 100755 script/shell/mine-salt.sh create mode 100644 slither.config.json create mode 100644 test/.solhint.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..841dc7e --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +######################################## +## Keys and Addresses ## +######################################## +PRIVATE_KEY= +DEPLOYER_ADDRESS= + +######################################## +## RPC URLs ## +######################################## +ETH_RPC_URL= \ No newline at end of file diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..49bbd46 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,19 @@ +name: "Setup" +description: "Check out the repo and install Foundry" + +runs: + using: "composite" + steps: + - name: "Check out the repo" + uses: "actions/checkout@v4" + with: + submodules: recursive + + - name: "Install Foundry" + uses: "foundry-rs/foundry-toolchain@v1" + with: + version: "stable" + + - name: "Show the Foundry config" + shell: "bash" + run: "forge --version && echo '\n' && forge config" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..11a2274 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,96 @@ +name: "CI" + +on: + workflow_dispatch: + pull_request: + push: + branches: + - "main" + +env: + # Add more RPC URLs as needed + ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} + +jobs: + build: + runs-on: "ubuntu-latest" + steps: + - name: "Setup" + uses: "./.github/actions/setup" + + - name: "Build the contracts and print their size" + run: "forge build --sizes --deny warnings" + + - name: "Add build summary" + run: | + echo "## Build result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + format: + runs-on: "ubuntu-latest" + needs: build + steps: + - name: "Setup" + uses: "./.github/actions/setup" + + - name: "Install solhint" + run: "npm i -g solhint" + + - name: "Print solhint version" + run: "solhint --version" + + - name: "Lint the code" + run: "make lint" + + - name: "Add lint summary" + run: | + echo "## Lint result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + slither: + runs-on: ubuntu-latest + needs: build + steps: + - name: "Setup" + uses: "./.github/actions/setup" + + - name: "Build contracts in src/ folder" + run: forge build --build-info --skip test script + + - name: "Run slither analyzer" + uses: crytic/slither-action@v0.4.0 + with: + ignore-compile: false + slither-version: "0.11.0" + fail-on: "low" + + - name: "Add slither summary" + run: | + echo "## Slither result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + test: + runs-on: "ubuntu-latest" + needs: build + steps: + - name: "Setup" + uses: "./.github/actions/setup" + + - name: "Generate a fuzz seed that changes weekly to avoid burning through RPC allowance" + run: > + echo "FOUNDRY_FUZZ_SEED=$( + echo $(($EPOCHSECONDS - $EPOCHSECONDS % 604800)) + )" >> $GITHUB_ENV + + - name: "Run the tests" + run: "FOUNDRY_PROFILE=ci forge test --force --isolate -vvv --show-progress --gas-snapshot-check true" + + - name: "Check test coverage" + env: + COVERAGE_MIN: 100 + run: make coverage-check + + - name: "Add test summary" + run: | + echo "## Tests result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index b79c8d4..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: CI - -permissions: {} - -on: - push: - pull_request: - workflow_dispatch: - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - name: Foundry project - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Show Forge version - run: forge --version - - - name: Run Forge fmt - run: forge fmt --check - - - name: Run Forge build - run: forge build --sizes - - - name: Run Forge tests - run: forge test -vvv diff --git a/.gitignore b/.gitignore index 85198aa..974d4fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Compiler files cache/ out/ +coverage # Ignores development broadcast logs !/broadcast @@ -12,3 +13,16 @@ docs/ # Dotenv file .env + +# Agents +.claude +.agents + +# Miscellaneous +*.log +.DS_Store +.pnp.* +lcov.info +.gas-snapshot +.cursorignore +coverage.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ac9ef14 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,37 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: mixed-line-ending + name: Mixed Line Ending (convert to LF) + args: ["--fix=lf"] + description: Forces to replace line ending by the UNIX 'lf' character. + exclude: "^docs/autogen" + + - repo: local + hooks: + - id: format + name: Run lint + description: Run lint with `make lint` + language: system + entry: make lint + exclude: "^lib/" + types: [solidity] + pass_filenames: false + + - id: slither + name: Run Slither + description: Run Slither with `make slither` + language: system + entry: make slither + types: [solidity] + pass_filenames: false + + - id: snapshot + name: Generate gas snapshot (might take a while) + description: Generate gas snapshot with `make snapshot` (might take a while) + language: system + entry: bash -c 'make snapshot-pre-commit 2>&1 | tee /dev/tty' + types: [solidity] + pass_filenames: true + require_serial: true diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..220cd26 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,55 @@ +{ + "extends": "solhint:recommended", + "rules": { + "code-complexity": ["error", 8], + "compiler-version": ["error", ">=0.8.34"], + "func-visibility": [ + "error", + { + "ignoreConstructors": true + } + ], + "max-line-length": ["error", 125], + "one-contract-per-file": "error", + "named-parameters-mapping": "error", + "func-param-name-mixedcase": "error", + "modifier-name-mixedcase": "error", + "ordering": "error", + "func-name-mixedcase": "error", + "private-vars-leading-underscore": "off", + "no-console": "error", + "not-rely-on-time": "off", + "gas-custom-errors": "error", + "no-unused-vars": "error", + "no-complex-fallback": "off", + "payable-fallback": "off", + "avoid-tx-origin": "error", + "no-inline-assembly": "off", + "avoid-low-level-calls": "off", + "import-path-check": "off", + "func-named-parameters": "warn", + "interface-starts-with-i": "error", + "use-natspec": [ + "warn", + { + "notice": { + "enabled": true + }, + "param": { + "enabled": true + }, + "return": { + "enabled": true + }, + "author": { "enabled": false } + } + ], + "gas-strict-inequalities": "off", + "no-empty-blocks": "off", + "gas-indexed-events": "off", + "gas-increment-by-one": "off", + "gas-struct-packing": "off", + "function-max-lines": "off", + "max-states-count": "off" + } +} diff --git a/.solhintignore b/.solhintignore new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b22cedf --- /dev/null +++ b/Makefile @@ -0,0 +1,65 @@ +ifneq (,$(wildcard ./.env)) + include .env + export +endif + +.PHONY: all clean build lint slither test fmt coverage-check snapshot + +help: ## Print all targets and descriptions + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[.a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-25s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } END { printf "\n" }' $(MAKEFILE_LIST) + +all: ## Build, fmt, slither, check coverage, and snapshot gas + make clean && \ + make build && \ + make fmt && \ + make slither && \ + make coverage-check && \ + make snapshot + +clean: ## Clean the project + forge clean --force + +build: ## Build the contracts + forge build --force + +lint: ## Run lint + forge fmt --check && \ + solhint -c .solhint.json --max-warnings 0 "src/**/*.sol" && \ + solhint -c script/.solhint.json --max-warnings 0 "script/**/*.sol" && \ + solhint -c test/.solhint.json --max-warnings 0 "test/**/*.t.sol" + +fmt: ## Format the contracts + forge fmt && \ + solhint -c .solhint.json --max-warnings 0 "src/**/*.sol" && \ + solhint -c script/.solhint.json --max-warnings 0 "script/**/*.sol" && \ + solhint -c test/.solhint.json --max-warnings 0 "test/**/*.t.sol" + +slither: ## Run slither (requires 0.11.0) + slither . --include-paths "(src)" --fail-low --config-file slither.config.json + +test: ## Run tests + forge test --force --isolate -vvv --show-progress --gas-snapshot-check true + +coverage-summary: ## Run tests and generate coverage summary + forge coverage --no-match-coverage "(test)" --force --report summary + +coverage-lcov: ## Run tests and generate coverage lcov report + forge coverage --no-match-coverage "(test)" --force --report lcov + +COVERAGE_MIN := 100 +coverage-check: ## Check if test coverage is above the minimum + make coverage-summary | tee coverage.txt + @coverage=$$(grep "| Total" coverage.txt | awk '{print $$4}' | sed 's/%//'); \ + if [ -z "$$coverage" ]; then \ + echo "\n❌ Failed to extract coverage percentage.\n"; \ + exit 1; \ + elif [ $$(echo "$$coverage < $(COVERAGE_MIN)" | bc -l) -eq 1 ]; then \ + echo "\n❌ Current coverage of $$coverage% below the minimum of $(COVERAGE_MIN)%.\n"; \ + exit 1; \ + else \ + echo "\n✅ Current coverage of $$coverage% meets the minimum of $(COVERAGE_MIN)%.\n"; \ + fi + @rm coverage.txt + +snapshot: ## Create a snapshot + forge snapshot --force --isolate --desc --show-progress diff --git a/README.md b/README.md index 72ce9fd..3433ddc 100644 --- a/README.md +++ b/README.md @@ -6,31 +6,14 @@ This project is meant to be used as a templated during the creation of new Githu It will contain some useful configuration files and scripts, that can be used also with existing projects (manually copied). - ## Usage -### Build - -```shell -forge build -``` - -### Test - -```shell -forge test -``` - -### Format - -```shell -forge fmt -``` +### Make targets -### Gas Snapshots +To see all available make targets, run: ```shell -forge snapshot +make help ``` ### Deploy @@ -44,18 +27,24 @@ forge script script/Counter.s.sol:CounterScript --rpc-url --priva The following operations need to be performed after this repository has been created. - [ ] In GitHub repo settings: - - [ ] Add a new ruleset called "Protected branches" and include the following changes: - - Enforcement status: active - - Target branches: Include default branch - - Require linear history - - Require a pull request before merging - - Required approvals: 1 - - Allowed merge methods: Squash - - Block force pushes - - [ ] In General → Features → Pull requests: - - Select "Pull request title and description" in "Default commit message" option - - Unckeck "Allow merge commits" option - - Check "Allow auto-merge" option -- [ ] Run `forge install` to install the dependencies. This will create a new `foundry.lock` file which you should commit to the project + - [ ] Add a new ruleset called "Protected branches" and include the following changes: + - Enforcement status: active + - Target branches: Include default branch + - Require linear history + - Require a pull request before merging + - Required approvals: 1 + - Allowed merge methods: Squash + - Block force pushes + - [ ] In General → Features → Pull requests: + - Select "Pull request title and description" in "Default commit message" option + - Unckeck "Allow merge commits" option + - Check "Allow auto-merge" option + - [ ] Configure secrets in the repository settings (e.g. `ETH_RPC_URL`): + - In Settings → Secrets and variables → Actions → New repository secret +- [ ] Initialize the submodules with `git submodule update --init` +- [ ] Install the dependencies with `forge install`. This will create a new `foundry.lock` file which you should commit to the project - [ ] Make sure you use the [latest version of Solidity](https://github.com/argotorg/solidity/releases) by updating the `solc` version in `foundry.toml` -- [ ] Once all entries in this list are checked, delete this section from the readme \ No newline at end of file +- [ ] Install `solhint` globally with `npm i -g solhint` +- [ ] Install `slither` globally with `pipx install slither-analyzer==0.11.0`. This will create a new `slither.config.json` file which you should commit to the project +- [ ] Install the pre-commit hooks with `pre-commit install` +- [ ] Once all entries in this list are checked, delete this section from the readme diff --git a/foundry.toml b/foundry.toml index 751f7be..629cae5 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,11 +2,37 @@ src = "src" out = "out" libs = ["lib"] -solc = "0.8.33" # See latest release at: https://github.com/argotorg/solidity/releases +fs_permissions = [ + { access = "read-write", path = "./" }, +] + +# Compiler +auto_detect_solc = false +solc = "0.8.34" # See latest release at: https://github.com/argotorg/solidity/releases +via_ir = true +optimizer = true +optimizer_runs = 100_000 +evm_version = "cancun" + +# Fuzz +fuzz = { runs = 1_000 } +invariant = { runs = 100, depth = 20, fail_on_revert = true } + +[profile.ci] + fuzz = { runs = 10_000 } + invariant = { runs = 100, depth = 100, fail_on_revert = true } + verbosity = 3 # outputs stack trace for failed tests only (https://book.getfoundry.sh/reference/config/testing) [fmt] +bracket_spacing = true +int_types = "long" +line_length = 125 +multiline_func_header = "attributes_first" +number_underscore = "thousands" +quote_style = "double" +tab_width = 4 +wrap_comments = true sort_imports = true -[profile.ci] -deny = "warnings" # Why not always: sometimes you just want to code and see what comes out -fuzz.seed = '0' # It makes CI reproducible, but still on a local machine it tries different parameters and so we can see edge cases if needed. +[lint] + exclude_lints = ["asm-keccak256","unwrapped-modifier-logic","pascal-case-struct","mixed-case-function","unsafe-typecast","unsafe-cheatcode","unchecked-call","block-timestamp"] \ No newline at end of file diff --git a/script/.solhint.json b/script/.solhint.json new file mode 100644 index 0000000..d8a3dd8 --- /dev/null +++ b/script/.solhint.json @@ -0,0 +1,37 @@ +{ + "extends": "solhint:recommended", + "rules": { + "compiler-version": ["error", ">=0.8.34"], + "func-visibility": [ + "error", + { + "ignoreConstructors": true + } + ], + "max-line-length": ["error", 125], + "one-contract-per-file": "warn", + "named-parameters-mapping": "warn", + "func-param-name-mixedcase": "error", + "modifier-name-mixedcase": "error", + "ordering": "error", + "func-name-mixedcase": "error", + "private-vars-leading-underscore": [ + "off", + { + "strict": false + } + ], + "not-rely-on-time": "off", + "no-console": "off", + "no-unused-vars": "error", + "payable-fallback": "error", + "avoid-tx-origin": "error", + "no-empty-blocks": "off", + "use-natspec": "off", + "no-inline-assembly": "off", + "import-path-check": "off", + "gas-increment-by-one": "off", + "gas-small-strings": "off", + "function-max-lines": "off" + } +} diff --git a/script/shell/mine-salt.sh b/script/shell/mine-salt.sh new file mode 100755 index 0000000..ec23f86 --- /dev/null +++ b/script/shell/mine-salt.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Utility for mining CREATE2 salts for a compiled contract. +# Supports optional address prefix/suffix filters and arbitrary constructor arguments. +# +# It would ideally be used with https://github.com/Arachnid/deterministic-deployment-proxy +# +# Examples: +# +# # Mine an address starting with 0x0000 for a no-arg contract +# ./script/shell/mine-salt.sh \ +# --deployer 0x4e59b44847b379578588920cA78FbF26c0B4956C \ +# --contract src/MyContract.sol:MyContract \ +# --starts-with 0000 +# +# # Mine an address ending in 0xdead with a constructor that takes (address, uint256) +# ./script/shell/mine-salt.sh \ +# --deployer 0x4e59b44847b379578588920cA78FbF26c0B4956C \ +# --contract MyContract \ +# --ends-with dead \ +# --ctor-signature "constructor(address,uint256)" \ +# --ctor-arg 0x1111111111111111111111111111111111111111 \ +# --ctor-arg 42 +# +# # Same as above, but pass all constructor args after --ctor-args (must be last) +# ./script/shell/mine-salt.sh \ +# --deployer 0x4e59b44847b379578588920cA78FbF26c0B4956C \ +# --contract MyContract \ +# --starts-with beef --ends-with cafe \ +# --ctor-signature "constructor(address,uint256)" \ +# --ctor-args 0x1111111111111111111111111111111111111111 42 + +DEPLOYER="" +CONTRACT="" +STARTS_WITH="" +ENDS_WITH="" +CTOR_SIG="" +CTOR_ARGS=() + +usage() { + cat <>> Running CREATE2 address miner..." +echo "Deployer: $DEPLOYER" +echo "Contract: $CONTRACT" +echo "Starts with: ${STARTS_WITH:-}" +echo "Ends with: ${ENDS_WITH:-}" +if [[ -n "$CTOR_SIG" ]]; then + echo "Constructor: '$CTOR_SIG'" + echo "Arguments: ${CTOR_ARGS[*]:-}" +fi +echo "Init code hash: $INIT_CODE_HASH" +echo + +CMD=(cast create2 --deployer "$DEPLOYER" --init-code-hash "$INIT_CODE_HASH") + +[[ -n "$STARTS_WITH" ]] && CMD+=(--starts-with "$STARTS_WITH") +[[ -n "$ENDS_WITH" ]] && CMD+=(--ends-with "$ENDS_WITH") + +"${CMD[@]}" | awk '/Successfully found contract address/,0' +echo \ No newline at end of file diff --git a/slither.config.json b/slither.config.json new file mode 100644 index 0000000..344ca63 --- /dev/null +++ b/slither.config.json @@ -0,0 +1,5 @@ +{ + "detectors_to_exclude": "solc-version,timestamp,unused-return,naming-convention,low-level-calls,calls-loop,costly-loop,assembly-usage,immutable-states,different-pragma-directives-are-used,reentrancy-events,uninitialized-state-variables,incorrect-equality,too-many-digits,uninitialized-local", + "filter_paths": "(lib/|test/|script/)", + "compile_libraries": false +} diff --git a/test/.solhint.json b/test/.solhint.json new file mode 100644 index 0000000..6b2180e --- /dev/null +++ b/test/.solhint.json @@ -0,0 +1,47 @@ +{ + "extends": "solhint:recommended", + "rules": { + "compiler-version": ["error", ">=0.8.34"], + "func-visibility": [ + "error", + { + "ignoreConstructors": true + } + ], + "max-line-length": "off", + "var-name-mixedcase": "off", + "one-contract-per-file": "off", + "named-parameters-mapping": "warn", + "func-param-name-mixedcase": "error", + "modifier-name-mixedcase": "error", + "func-name-mixedcase": "off", + "foundry-test-functions": "warn", + "gas-custom-errors": "off", + "private-vars-leading-underscore": [ + "off", + { + "strict": false + } + ], + "no-console": "off", + "not-rely-on-time": "off", + "no-inline-assembly": "off", + "no-unused-vars": "error", + "payable-fallback": "off", + "no-complex-fallback": "off", + "avoid-tx-origin": "error", + "avoid-low-level-calls": "off", + "ordering": "off", + "reentrancy": "off", + "import-path-check": "off", + "use-natspec": "off", + "gas-strict-inequalities": "off", + "no-empty-blocks": "off", + "gas-indexed-events": "off", + "gas-increment-by-one": "off", + "gas-struct-packing": "off", + "function-max-lines": "off", + "gas-small-strings": "off", + "max-states-count": "off" + } +} From 571741e70e05189039fe100bd90c0074e9ce2fd8 Mon Sep 17 00:00:00 2001 From: igorroncevic <57319163+igorroncevic@users.noreply.github.com> Date: Tue, 5 May 2026 11:55:27 +0200 Subject: [PATCH 2/4] update --- .github/actions/setup/action.yml | 9 ++------- .github/workflows/ci.yml | 33 +++++++++++++++++++++++++++----- .vscode/settings.json | 6 ++++++ Makefile | 6 +++--- README.md | 3 +++ foundry.toml | 17 ++++++++++++++-- script/Counter.s.sol | 8 ++++---- src/Counter.sol | 10 +++++++++- test/Counter.t.sol | 6 +++--- 9 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 49bbd46..b21f54b 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,16 +1,11 @@ name: "Setup" -description: "Check out the repo and install Foundry" +description: "Install Foundry" runs: using: "composite" steps: - - name: "Check out the repo" - uses: "actions/checkout@v4" - with: - submodules: recursive - - name: "Install Foundry" - uses: "foundry-rs/foundry-toolchain@v1" + uses: "foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d" with: version: "stable" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11a2274..ddd49ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,12 @@ name: "CI" +permissions: {} + +# Uncomment to use secrets +# env: +# ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} +# ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} + on: workflow_dispatch: pull_request: @@ -7,14 +14,15 @@ on: branches: - "main" -env: - # Add more RPC URLs as needed - ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} - jobs: build: runs-on: "ubuntu-latest" steps: + - name: "Check out the repo" + uses: "actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5" + with: + submodules: recursive + - name: "Setup" uses: "./.github/actions/setup" @@ -30,6 +38,11 @@ jobs: runs-on: "ubuntu-latest" needs: build steps: + - name: "Check out the repo" + uses: "actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5" + with: + submodules: recursive + - name: "Setup" uses: "./.github/actions/setup" @@ -51,6 +64,11 @@ jobs: runs-on: ubuntu-latest needs: build steps: + - name: "Check out the repo" + uses: "actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5" + with: + submodules: recursive + - name: "Setup" uses: "./.github/actions/setup" @@ -58,7 +76,7 @@ jobs: run: forge build --build-info --skip test script - name: "Run slither analyzer" - uses: crytic/slither-action@v0.4.0 + uses: crytic/slither-action@f197989dea5b53e986d0f88c60a034ddd77ec9a8 with: ignore-compile: false slither-version: "0.11.0" @@ -73,6 +91,11 @@ jobs: runs-on: "ubuntu-latest" needs: build steps: + - name: "Check out the repo" + uses: "actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5" + with: + submodules: recursive + - name: "Setup" uses: "./.github/actions/setup" diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..753c424 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "solidity.compileUsingRemoteVersion": "v0.8.34+commit.80d5c536", + "[solidity]": { + "editor.defaultFormatter": "JuanBlanco.solidity" + } +} diff --git a/Makefile b/Makefile index b22cedf..4cc41e2 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ all: ## Build, fmt, slither, check coverage, and snapshot gas make snapshot clean: ## Clean the project - forge clean --force + forge clean build: ## Build the contracts forge build --force @@ -41,10 +41,10 @@ test: ## Run tests forge test --force --isolate -vvv --show-progress --gas-snapshot-check true coverage-summary: ## Run tests and generate coverage summary - forge coverage --no-match-coverage "(test)" --force --report summary + forge coverage --no-match-coverage "(test|script)" --force --report summary coverage-lcov: ## Run tests and generate coverage lcov report - forge coverage --no-match-coverage "(test)" --force --report lcov + forge coverage --no-match-coverage "(test|script)" --force --report lcov COVERAGE_MIN := 100 coverage-check: ## Check if test coverage is above the minimum diff --git a/README.md b/README.md index 3433ddc..e621bcf 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ make help ### Deploy +Add `--broadcast` to send the transaction. Add `--verify` to verify the contract using the Etherscan settings in `foundry.toml`. + ```shell forge script script/Counter.s.sol:CounterScript --rpc-url --private-key ``` @@ -41,6 +43,7 @@ The following operations need to be performed after this repository has been cre - Check "Allow auto-merge" option - [ ] Configure secrets in the repository settings (e.g. `ETH_RPC_URL`): - In Settings → Secrets and variables → Actions → New repository secret + - Uncomment the `env` section in `.github/workflows/ci.yml` to use secrets in the CI workflow - [ ] Initialize the submodules with `git submodule update --init` - [ ] Install the dependencies with `forge install`. This will create a new `foundry.lock` file which you should commit to the project - [ ] Make sure you use the [latest version of Solidity](https://github.com/argotorg/solidity/releases) by updating the `solc` version in `foundry.toml` diff --git a/foundry.toml b/foundry.toml index 629cae5..c6b2194 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,7 +3,7 @@ src = "src" out = "out" libs = ["lib"] fs_permissions = [ - { access = "read-write", path = "./" }, + { access = "read-write", path = "./" }, ] # Compiler @@ -14,6 +14,12 @@ optimizer = true optimizer_runs = 100_000 evm_version = "cancun" +# Keep deployment bytecode stable. This helps deterministic deployments because +# metadata changes (like compiler version) will not change the final bytecode hash. +# See: https://docs.soliditylang.org/en/latest/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode +bytecode_hash = "none" +cbor_metadata = false + # Fuzz fuzz = { runs = 1_000 } invariant = { runs = 100, depth = 20, fail_on_revert = true } @@ -35,4 +41,11 @@ wrap_comments = true sort_imports = true [lint] - exclude_lints = ["asm-keccak256","unwrapped-modifier-logic","pascal-case-struct","mixed-case-function","unsafe-typecast","unsafe-cheatcode","unchecked-call","block-timestamp"] \ No newline at end of file +exclude_lints = ["asm-keccak256","unwrapped-modifier-logic","pascal-case-struct","mixed-case-function","unsafe-typecast","unsafe-cheatcode","unchecked-call","block-timestamp"] + +[rpc_endpoints] +mainnet = "{ETH_RPC_URL}" + +# Note: as of Etherscan API v2, the API key is shared across all chains +[etherscan] +mainnet = { key = "${ETHERSCAN_API_KEY}", chain = 1 } diff --git a/script/Counter.s.sol b/script/Counter.s.sol index 3983ed4..793ce52 100644 --- a/script/Counter.s.sol +++ b/script/Counter.s.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity 0.8.34; -import {Counter} from "../src/Counter.sol"; -import {Script} from "forge-std/Script.sol"; +import { Counter } from "../src/Counter.sol"; +import { Script } from "forge-std/Script.sol"; contract CounterScript is Script { Counter public counter; - function setUp() public {} + function setUp() public { } function run() public { vm.startBroadcast(); diff --git a/src/Counter.sol b/src/Counter.sol index aded799..c233e33 100644 --- a/src/Counter.sol +++ b/src/Counter.sol @@ -1,13 +1,21 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity 0.8.34; +/// @title Counter +/// @author Nomev Labs +/// @notice This contract is a simple counter +/// @dev This contract is a simple counter contract Counter { + /// @notice The current number uint256 public number; + /// @notice Set the number + /// @param newNumber The new number function setNumber(uint256 newNumber) public { number = newNumber; } + /// @notice Increment the number function increment() public { number++; } diff --git a/test/Counter.t.sol b/test/Counter.t.sol index 8e2817e..66f7552 100644 --- a/test/Counter.t.sol +++ b/test/Counter.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity 0.8.34; -import {Counter} from "../src/Counter.sol"; -import {Test} from "forge-std/Test.sol"; +import { Counter } from "../src/Counter.sol"; +import { Test } from "forge-std/Test.sol"; contract CounterTest is Test { Counter public counter; From 33e4bf5885ffd291223d450194d41b71effe967d Mon Sep 17 00:00:00 2001 From: igorroncevic <57319163+igorroncevic@users.noreply.github.com> Date: Tue, 5 May 2026 12:07:11 +0200 Subject: [PATCH 3/4] update README.md --- README.md | 17 +++++++++++++++++ deployments.json | 14 ++++++++++++++ remappings.txt | 0 3 files changed, 31 insertions(+) create mode 100644 deployments.json create mode 100644 remappings.txt diff --git a/README.md b/README.md index e621bcf..1b67b6d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,23 @@ To see all available make targets, run: make help ``` +### Add dependencies + +```shell +forge install +``` + +### Set up remappings + +In `remappings.txt`, add the remappings for the dependencies, e.g.: + +``` +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +``` + +This will allow you to write shorter import paths in the contracts. + ### Deploy Add `--broadcast` to send the transaction. Add `--verify` to verify the contract using the Etherscan settings in `foundry.toml`. diff --git a/deployments.json b/deployments.json new file mode 100644 index 0000000..9e62aba --- /dev/null +++ b/deployments.json @@ -0,0 +1,14 @@ +{ + "ContractName_Proxy": { + "1": { + "address": "", + "transactionHash": "" + } + }, + "ContractName_Implementation": { + "1": { + "address": "", + "transactionHash": "" + } + } +} diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..e69de29 From 01a28c6bf1f8e097d4ac4d7af0833f9eb495d423 Mon Sep 17 00:00:00 2001 From: igorroncevic <57319163+igorroncevic@users.noreply.github.com> Date: Tue, 5 May 2026 13:53:12 +0200 Subject: [PATCH 4/4] update --- .github/workflows/ci.yml | 28 ++++------------------------ foundry.toml | 2 +- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddd49ec..6f67383 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: runs-on: "ubuntu-latest" steps: - name: "Check out the repo" - uses: "actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5" + uses: "actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd" with: submodules: recursive @@ -29,17 +29,12 @@ jobs: - name: "Build the contracts and print their size" run: "forge build --sizes --deny warnings" - - name: "Add build summary" - run: | - echo "## Build result" >> $GITHUB_STEP_SUMMARY - echo "✅ Passed" >> $GITHUB_STEP_SUMMARY - format: runs-on: "ubuntu-latest" needs: build steps: - name: "Check out the repo" - uses: "actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5" + uses: "actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd" with: submodules: recursive @@ -55,17 +50,12 @@ jobs: - name: "Lint the code" run: "make lint" - - name: "Add lint summary" - run: | - echo "## Lint result" >> $GITHUB_STEP_SUMMARY - echo "✅ Passed" >> $GITHUB_STEP_SUMMARY - slither: runs-on: ubuntu-latest needs: build steps: - name: "Check out the repo" - uses: "actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5" + uses: "actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd" with: submodules: recursive @@ -82,17 +72,12 @@ jobs: slither-version: "0.11.0" fail-on: "low" - - name: "Add slither summary" - run: | - echo "## Slither result" >> $GITHUB_STEP_SUMMARY - echo "✅ Passed" >> $GITHUB_STEP_SUMMARY - test: runs-on: "ubuntu-latest" needs: build steps: - name: "Check out the repo" - uses: "actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5" + uses: "actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd" with: submodules: recursive @@ -112,8 +97,3 @@ jobs: env: COVERAGE_MIN: 100 run: make coverage-check - - - name: "Add test summary" - run: | - echo "## Tests result" >> $GITHUB_STEP_SUMMARY - echo "✅ Passed" >> $GITHUB_STEP_SUMMARY diff --git a/foundry.toml b/foundry.toml index c6b2194..34832e9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -41,7 +41,7 @@ wrap_comments = true sort_imports = true [lint] -exclude_lints = ["asm-keccak256","unwrapped-modifier-logic","pascal-case-struct","mixed-case-function","unsafe-typecast","unsafe-cheatcode","unchecked-call","block-timestamp"] +exclude_lints = [] [rpc_endpoints] mainnet = "{ETH_RPC_URL}"