Skip to content

Commit 7eb0fca

Browse files
committed
ci: add reusable bootstrap and reusable foundry test workflows
1 parent 595a5b0 commit 7eb0fca

File tree

4 files changed

+213
-14
lines changed

4 files changed

+213
-14
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: "Regression tests"
2+
description: "Checkout and run regression tests for community-staking-module"
3+
4+
inputs:
5+
rpc_url:
6+
description: "RPC URL injected as RPC_URL env for fork tests."
7+
required: true
8+
repository:
9+
description: "Repository to checkout (owner/repo)."
10+
required: false
11+
default: "lidofinance/community-staking-module"
12+
ref:
13+
description: "Git ref of the repository to test."
14+
required: false
15+
default: "main"
16+
path:
17+
description: "Path to checkout the repository."
18+
required: false
19+
default: "community-staking-module"
20+
21+
runs:
22+
using: "composite"
23+
steps:
24+
- name: Checkout community-staking-module
25+
uses: actions/checkout@v4
26+
with:
27+
repository: ${{ inputs.repository }}
28+
ref: ${{ inputs.ref }}
29+
path: ${{ inputs.path }}
30+
31+
- name: Set env vars
32+
shell: bash
33+
working-directory: ${{ inputs.path }}
34+
run: |
35+
echo "FOUNDRY_VERSION=$(head .foundryref)" >> "$GITHUB_ENV"
36+
echo "JUST_TAG=1.24.0" >> "$GITHUB_ENV"
37+
38+
- name: Cache just
39+
id: just-cache
40+
uses: actions/cache@v4
41+
with:
42+
path: ~/.cargo/bin/
43+
key: cargobin-${{ runner.os }}-just-${{ env.JUST_TAG }}
44+
45+
- name: Install just
46+
shell: bash
47+
run: |
48+
if ! command -v just >/dev/null 2>&1; then
49+
cargo install "just@${JUST_TAG}"
50+
fi
51+
52+
- name: Install Foundry
53+
uses: foundry-rs/foundry-toolchain@v1
54+
with:
55+
version: ${{ env.FOUNDRY_VERSION }}
56+
57+
- name: Install node
58+
uses: actions/setup-node@v4
59+
with:
60+
node-version-file: "${{ inputs.path }}/.nvmrc"
61+
cache: yarn
62+
cache-dependency-path: "${{ inputs.path }}/**/yarn.lock"
63+
64+
- name: Install dependencies
65+
shell: bash
66+
working-directory: ${{ inputs.path }}
67+
run: just deps
68+
69+
- name: Build
70+
shell: bash
71+
working-directory: ${{ inputs.path }}
72+
run: just build
73+
74+
- name: Run regression tests
75+
shell: bash
76+
working-directory: ${{ inputs.path }}
77+
run: just test-integration
78+
env:
79+
RPC_URL: ${{ inputs.rpc_url }}
80+
CHAIN: mainnet
81+
DEPLOY_CONFIG: ./artifacts/mainnet/deploy-mainnet.json
82+
FOUNDRY_PROFILE: ci

.github/workflows/regular-mainnet.yml

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,23 @@ jobs:
4646
run: cargo install "just@$JUST_TAG"
4747
if: steps.get-cache.outputs.cache-hit != 'true'
4848

49-
test:
50-
name: Integration & Invariant tests
49+
regression-tests:
50+
name: Regression tests
5151
runs-on: ubuntu-latest
5252
needs: bootstrap
5353
steps:
5454
- uses: actions/checkout@v4
5555
with:
5656
persist-credentials: false
5757

58-
- name: Set FOUNDRY_VERSION
59-
run: echo "FOUNDRY_VERSION=$(head .foundryref)" >> "$GITHUB_ENV"
60-
6158
- uses: actions/cache@v4
6259
with:
6360
path: ${{ needs.bootstrap.outputs.cache-path }}
6461
key: ${{ needs.bootstrap.outputs.cache-key }}
6562

63+
- name: Set FOUNDRY_VERSION
64+
run: echo "FOUNDRY_VERSION=$(head .foundryref)" >> "$GITHUB_ENV"
65+
6666
- name: Install Foundry
6767
uses: foundry-rs/foundry-toolchain@v1
6868
with:
@@ -78,17 +78,22 @@ jobs:
7878
- name: Install dependencies
7979
run: just deps
8080

81-
# TODO: Remove `continue-on-error` after making the contracts fit.
82-
- name: Build
83-
run: just build --sizes
84-
continue-on-error: true
85-
86-
- name: Run post-vote tests
87-
run: just test-post-voting
81+
- name: Start mainnet fork
8882
env:
89-
CHAIN: mainnet
90-
DEPLOY_CONFIG: ./artifacts/mainnet/deploy-mainnet.json
9183
RPC_URL: ${{ secrets.RPC_URL_MAINNET }}
84+
run: just make-fork &
85+
86+
- name: Run regression tests
87+
uses: ./.github/actions/regression-tests
88+
with:
89+
repository: ${{ github.repository }}
90+
ref: v2.0 # Run the stable v2.0 release tests
91+
path: community-staking-module-v2
92+
rpc_url: http://127.0.0.1:8545
93+
94+
- name: Kill fork
95+
if: always()
96+
run: just kill-fork
9297

9398
- name: Echo embeds to the env variable
9499
if: ${{ failure() && (github.event_name == 'schedule' || inputs.notify) }}

AGENTS.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Repository Guidelines
2+
3+
## Project Structure & Module Organization
4+
5+
- `src/`: Solidity contracts (Solc 0.8.24).
6+
- `test/`: Forge tests (`*.t.sol`), plus `test/fork/*` for fork/integration and deployment tests.
7+
- `script/`: Forge scripts (deploy/verify, per-chain variants).
8+
- `artifacts/`, `broadcast/`, `out/`, `cache/`: build, deploy, and fork outputs.
9+
- `lib/`, `node_modules/`: dependencies; see `remappings.txt`.
10+
- `docs/`, `digest/`: documentation and design notes.
11+
12+
## Build, Test, and Development Commands
13+
14+
- `just deps`: install production deps; `just deps-dev`: dev deps + husky.
15+
- `just`: clean, deps, build, and run all tests.
16+
- `just build`: Forge build (skips tests/scripts); `forge build` works too.
17+
- `just test-unit`: unit tests only; `just test-all`: unit + fork suites.
18+
- `just test-local`: spins up anvil fork, deploys, runs deployment+integration tests.
19+
- `just coverage` | `just coverage-lcov`: coverage (LCOV saved; see `lcov.html`).
20+
- Linting: `yarn lint:check` (prettier + solhint), `yarn lint:fix`, `yarn lint:solhint`.
21+
22+
## Coding Style & Naming Conventions
23+
24+
- Formatting: Prettier + `prettier-plugin-solidity` (Solidity `printWidth=80`, `tabWidth=4`, spaces only).
25+
- Linting: Solhint (`.solhint.json`) with `solhint:recommended` and `solhint-plugin-lido-csm`.
26+
- Versions: enforce `pragma solidity ^0.8.24` (`compiler-version` rule).
27+
- Naming: contracts/libraries `CamelCase` (e.g., `CSModule`, `AssetRecovererLib`), interfaces `IName` (rule: `interface-starts-with-i`).
28+
- Conventions: prefer custom errors, calldata parameters, and struct packing (gas rules). Immutable vars styled as constants.
29+
30+
## Testing Guidelines
31+
32+
- Framework: Foundry/Forge with fuzzing (`fuzz.runs=256`).
33+
- Structure: unit tests in `test/*.t.sol`; fork suites under `test/fork/*` (deployment/integration).
34+
- Run: `just test-unit` for fast cycles; `CHAIN`/`RPC_URL` required for fork tests. Example: `export CHAIN=holesky && export RPC_URL=<https-url>`.
35+
- Coverage: `just coverage-lcov` produces LCOV output (commit if relevant).
36+
37+
## Commit & Pull Request Guidelines
38+
39+
- Commits: Conventional Commits. Examples: `feat: add validator key rotation`, `fix: resolve bond calculation overflow`.
40+
- PRs: clear description, linked issues, rationale, test coverage notes, storage layout impacts (if any), and gas impact (attach `GAS.md` when updated). Ensure CI green and `yarn lint:check` passes.
41+
42+
## Security & Configuration Tips
43+
44+
- Never commit keys; use `.env` from `.env.sample`. For forks: `ANVIL_IP_ADDR`, `ANVIL_PORT`, `RPC_URL`, `CHAIN`.
45+
- Pin Foundry to version in `.foundryref`. Use `just make-fork`/`just kill-fork` to manage local forks.

script/dummy.sol

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.0;
3+
4+
import { Script } from "forge-std/Script.sol";
5+
6+
import { DeploymentFixtures } from "../test/helpers/Fixtures.sol";
7+
import { Utilities } from "../test/helpers/Utilities.sol";
8+
9+
// --- Interface says "view" ---
10+
interface IFakeView {
11+
function readAndMaybeBump() external view returns (uint256);
12+
}
13+
14+
// --- Contract that "pretends" to be view but actually mutates ---
15+
contract FakeView {
16+
uint256 public counter;
17+
18+
// NOTE: Implementation is NOT marked "view" and it increments state.
19+
// This compiles because "view/pure" isn't part of the ABI; however,
20+
// it will REVERT when invoked via STATICCALL (i.e., through a view-typed call).
21+
function readAndMaybeBump() external returns (uint256) {
22+
counter += 1; // state change
23+
return counter;
24+
}
25+
}
26+
27+
// --- Caller demonstrating both paths ---
28+
contract Caller {
29+
// 1) Call via the *view-typed* interface.
30+
// Solidity uses STATICCALL => any state change in callee will revert.
31+
function tryViaInterface(address target) external returns (uint256) {
32+
// This will revert in FakeView.readAndMaybeBump() when it tries to SSTORE.
33+
return IFakeView(target).readAndMaybeBump();
34+
}
35+
36+
// 2) Bypass the static context with a low-level CALL.
37+
// Now the callee can mutate state even though the interface *claims* it's view.
38+
function bypassAndMutate(address target) external returns (uint256 result) {
39+
// Same function selector as IFakeView.readAndMaybeBump()
40+
(bool ok, bytes memory data) = target.call(
41+
abi.encodeWithSelector(IFakeView.readAndMaybeBump.selector)
42+
);
43+
require(ok, "low-level call failed");
44+
result = abi.decode(data, (uint256));
45+
}
46+
47+
// Convenience helper to read FakeView.counter after calling bypassAndMutate
48+
function readCounter(address target) external view returns (uint256) {
49+
return FakeView(target).counter();
50+
}
51+
}
52+
53+
contract dummy is Script, DeploymentFixtures, Utilities {
54+
address private deployer;
55+
uint256 private pk;
56+
57+
function run() external {
58+
vm.startBroadcast();
59+
(, deployer, ) = vm.readCallers();
60+
61+
FakeView fakeView = new FakeView();
62+
Caller caller = new Caller();
63+
caller.tryViaInterface(address(fakeView));
64+
65+
vm.stopBroadcast();
66+
}
67+
}

0 commit comments

Comments
 (0)