Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
########################################
## Keys and Addresses ##
########################################
PRIVATE_KEY=
DEPLOYER_ADDRESS=
Comment on lines +1 to +5
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't have env files with private keys. They will be committed and made public. Yes this happened.
It's so bad that I'd drop the file completely and leave each dev to manage env themselves just to avoid that they add the private key to it. Deployments are super rare anyway, it's ok to take a bit more time to export the secret key from somewhere. Even better, the private key isn't stored as a string but on dedicated wallet.


########################################
## RPC URLs ##
########################################
ETH_RPC_URL=
14 changes: 14 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: "Setup"
description: "Install Foundry"

runs:
using: "composite"
steps:
- name: "Install Foundry"
uses: "foundry-rs/foundry-toolchain@c7450ba673e133f5ee30098b3b54f444d3a2ca2d"
with:
version: "stable"

- name: "Show the Foundry config"
shell: "bash"
run: "forge --version && echo '\n' && forge config"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ubernit: in test instructions, nice to provide a link to check this https://github.com/cowprotocol/contracts-template/actions/runs/25370595345/job/74392802415#step:3:28

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that adds noise, that's why make help exists.

Comment on lines +12 to +14
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, I'm in favor of splitting this, the use of \n is weird and makes it a bit harder to search for the relevant log.

(I removed shell: "bash" because it seems unnecessary, considering that everywhere else we don't use it.)

Suggested change
- name: "Show the Foundry config"
shell: "bash"
run: "forge --version && echo '\n' && forge config"
- name: "Show the Foundry version"
run: "forge --version"
- name: "Show the Foundry config"
run: "forge config"
Image

99 changes: 99 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: "CI"

permissions: {}

Comment on lines +3 to +4
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary entry?

Suggested change
permissions: {}

# Uncomment to use secrets
# env:
# ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
# ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }}
Comment on lines +5 to +8
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These secrets will be seen by all jobs, and we don't necessarily trust all of them to keep the secret safe. We should move these envs to where they're needed (that is, testing).
Also, I don't think we've used ETHERSCAN_API_KEY before in CI.


on:
workflow_dispatch:
pull_request:
push:
branches:
- "main"
Comment on lines +14 to +15
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: let's have CI run on push, otherwise one first creates a PR and only then notices that CI is failing.

Suggested change
branches:
- "main"


jobs:
build:
runs-on: "ubuntu-latest"
steps:
- name: "Check out the repo"
uses: "actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd"
with:
submodules: recursive

- name: "Setup"
uses: "./.github/actions/setup"

- name: "Build the contracts and print their size"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the issue there? This is a very meaningful output especially if there are many contract which might go over the limit or get close to it. I'd keep it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no issue, just made it simpler for reviewers to check the output by providing a link to it.
Maybe, if its useful, consider writing in the summary

run: "forge build --sizes --deny warnings"

format:
runs-on: "ubuntu-latest"
needs: build
steps:
- name: "Check out the repo"
uses: "actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd"
with:
submodules: recursive

- name: "Setup"
uses: "./.github/actions/setup"

- name: "Install solhint"
run: "npm i -g solhint"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the good practice here? if we rely on npm for linting, shouldn't we have it in a package.json? (so we can run locally, and more importantly control the version)

I think @fedgiac might be oppinionated about the lintting and the addition of a package.json into the project (something we try to avoid, but if we can't get a good linting otherwise, I guess there's no way around?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add linting instructions locally. no?
Also, should the commit be blocked by the linter so it doesn't allow us to commit?

Copy link
Copy Markdown
Author

@igorroncevic igorroncevic May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the good practice here?

I've always installed Solhint globally to avoid package.json for a single package.

control the version

We could hardcode the version both locally and in the CI, good point!

We should add linting instructions locally. no?

make help or simply make shows the user what to run to do it.

should the commit be blocked by the linter so it doesn't allow us to commit?

pre-commit helps with this, although devs can always choose to use --no-verify to skip that. That's why we lint in the CI too.


- name: "Print solhint version"
run: "solhint --version"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is relevant, should it be in the summary?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's relevant, especially in case a new version releases and locally lint passes (because of the old version), but doesn't on CI.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then why not writing in the summary?


- name: "Lint the code"
run: "make lint"

slither:
runs-on: ubuntu-latest
needs: build
steps:
- name: "Check out the repo"
uses: "actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd"
with:
submodules: recursive

- 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@f197989dea5b53e986d0f88c60a034ddd77ec9a8
with:
ignore-compile: false
slither-version: "0.11.0"
fail-on: "low"

test:
runs-on: "ubuntu-latest"
needs: build
steps:
- name: "Check out the repo"
uses: "actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd"
with:
submodules: recursive

- 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
Comment on lines +87 to +91
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the fuzz seed affect the RPC allowance?


- name: "Run the tests"
run: "FOUNDRY_PROFILE=ci forge test --force --isolate -vvv --show-progress --gas-snapshot-check true"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • --force: do we need it? Isn't it ran on a fresh state anyway?
  • -isolate: this has repercussions on how tests are executed. If we keep it (I like it) it should be in the configs, so that there are no surprises when testing locally works but CI fails. There's no need to have this outside of the configs.
  • --show-progress: I'd remove it for simplicity, it's not like we're really going to track the progress live in CI.
  • --gas-snapshot-check: what does it do? Right now there's no gas snapshot, you'd expect CI to fail but there's no error. If I run locally and I edit the snapshot file, I see no difference in the output.

update: I see this matches make test. If that's the intention, we should run make test and not duplicate the command. Comments above still mostly apply to that.


- name: "Check test coverage"
env:
COVERAGE_MIN: 100
run: make coverage-check
38 changes: 0 additions & 38 deletions .github/workflows/test.yml

This file was deleted.

14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Compiler files
cache/
out/
coverage

# Ignores development broadcast logs
!/broadcast
Expand All @@ -12,3 +13,16 @@ docs/

# Dotenv file
.env

# Agents
.claude
.agents

# Miscellaneous
*.log
.DS_Store
.pnp.*
lcov.info
.gas-snapshot
.cursorignore
coverage.txt
37 changes: 37 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how this tool works, but this looks like a hidden source of supply-chain attacks: it takes code from a random GitHub repo and executes it on each commit.
The benefit is avoiding \r\n I suppose? I'd say it isn't worth the risk.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all of this should be a pre-push more than a pre-commit, otherwise it makes committing too slow.

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
55 changes: 55 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -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",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you have some context on your lint picks? Specially the overrides to disable some rules?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just something I'm used to. Most of them are good practices anyways!

Btw not all are overrides, just explicit settings in case defaults ever change.

"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"
Comment on lines +3 to +53
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are too many entries here and I think most of them aren't necessary.
The choice of what to set on and off seems arbitrary.
My suggestion is to keep it simple and only include the things we know we absolutely want.
This would be, imho: use-natspec, "compiler-version": ["error", "^0.8"] (not sure if this does what I think though, the point is using the exact string "^0.8", see other comment).

Similar comment for the other two files, but there I expect turning things off will be more common.

We can always add and remove entries to this at a later point. Let's try to keep things simple as much as we can.

}
}
Empty file added .solhintignore
Empty file.
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"solidity.compileUsingRemoteVersion": "v0.8.34+commit.80d5c536",
"[solidity]": {
"editor.defaultFormatter": "JuanBlanco.solidity"
}
}
65 changes: 65 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
ifneq (,$(wildcard ./.env))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not strongly opposed, but there's more modern tools today that are also frendlier for other platforms like windows.

What's your oppinion on
https://just.systems/

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just is fine, but I've preferred Makefile. I can migrate to Just if others want it!

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<target>\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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following thre README leads to failure (missing dependency solhint). Ideally we don't rely on global packages, adds friction to someone entering the project, and more importantly, they are error proned (each person can have a different version).

❯ make all
make clean && \
        make build && \
        make fmt && \
        make slither && \
        make coverage-check && \
        make snapshot
forge clean
forge build --force
[⠊] Compiling...
[⠢] Compiling 23 files with Solc 0.8.34
[⠆] Solc 0.8.34 finished in 1.20s
Compiler run successful!
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"
/bin/sh: solhint: command not found
make[1]: *** [fmt] Error 127
make: *** [all] Error 2

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I make it a dev dependency, install it with npm i -D?

Slither needs to be global tho, as it's installed with pip.

make clean && \
make build && \
make fmt && \
make slither && \
make coverage-check && \
make snapshot

clean: ## Clean the project
forge clean

build: ## Build the contracts
forge build --force
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, it's faster to use the cache on day-to-day operations. For large contracts, rebuilding can take many seconds.

Same comment for all occurrences of --force here.

Suggested change
forge build --force
forge build


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"
Comment on lines +33 to +35
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this part of fmt as well? Shouldn't it look different from lint?


slither: ## Run slither (requires 0.11.0)
slither . --include-paths "(src)" --fail-low --config-file slither.config.json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we use --include-paths "(src)", on the config file we use "filter_paths": "(lib/|test/|script/)".

I think we should pick only one of the two and not having both whitelist and blacklists.


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|script)" --force --report summary
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't doing what we expect it to do: the command would ignores files like ContestPlayer.sol and DescriptiveResults.sol. Same comment below.


coverage-lcov: ## Run tests and generate coverage lcov report
forge coverage --no-match-coverage "(test|script)" --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
Loading