Skip to content

Commit 1c805d1

Browse files
authored
[FEAT] Add CI checks for cofhe-contracts (#53)
* [FEAT] Add CI checks: storage layout, contract size, solhint, slither, gas report Add comprehensive CI pipeline for cofhe-contracts with 5 parallel jobs: - Storage layout snapshot validation to catch breaking upgrade changes - Contract size enforcement (24KB EVM limit) - Solhint linting with project-specific rule overrides - Slither static analysis (medium+ severity) - Gas usage reporting uploaded as CI artifact * [FIX] Fix Slither medium-severity findings in TaskManager - Initialize `combined` variable explicitly (`bytes memory combined = ""`) - Add slither-disable comments for false-positive unused-return on ECDSA.tryRecover tuple destructuring and getDecryptResultSafe forwarding - Exclude low-severity false positives from Slither config (trusted contract calls in loops, reentrancy-events, missing-zero-check on intentionally-nullable signers, timestamp, events-maths) * [TEST] Add storage variable to TaskManager (should fail storage-layout CI) This commit intentionally adds a new storage variable to break the storage layout snapshot check. Expected CI failure: storage-layout job. * Revert "[TEST] Add storage variable to TaskManager (should fail storage-layout CI)" This reverts commit 99c1957. * [FIX] Use upgrade-compatibility check instead of strict snapshot equality Replace exact JSON comparison with OZ's getStorageUpgradeReport which understands UUPS upgrade rules: appending variables is safe, inserting or reordering is breaking. Stores raw StorageLayout in snapshot for direct use by the compatibility checker.
1 parent 0452908 commit 1c805d1

12 files changed

Lines changed: 1381 additions & 4 deletions

File tree

.github/workflows/checks.yml

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
name: Checks
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- master
7+
push:
8+
branches:
9+
- master
10+
11+
env:
12+
WORKING_DIR: contracts/internal/host-chain
13+
KEY: "0x0000000000000000000000000000000000000000000000000000000000000001"
14+
KEY2: "0x0000000000000000000000000000000000000000000000000000000000000002"
15+
AGGREGATOR_KEY: "0x0000000000000000000000000000000000000000000000000000000000000003"
16+
17+
jobs:
18+
lint-solhint:
19+
name: Solhint Lint
20+
runs-on: ubuntu-latest
21+
defaults:
22+
run:
23+
working-directory: contracts/internal/host-chain
24+
steps:
25+
- name: Checkout repository
26+
uses: actions/checkout@v4
27+
28+
- name: Setup Node.js
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version: "20"
32+
33+
- name: Install pnpm
34+
uses: pnpm/action-setup@v2
35+
with:
36+
version: 9
37+
38+
- name: Get pnpm store directory
39+
shell: bash
40+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
41+
42+
- name: Setup pnpm cache
43+
uses: actions/cache@v4
44+
with:
45+
path: ${{ env.STORE_PATH }}
46+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('contracts/internal/host-chain/pnpm-lock.yaml') }}
47+
restore-keys: |
48+
${{ runner.os }}-pnpm-store-
49+
50+
- name: Install dependencies
51+
run: pnpm install
52+
53+
- name: Run Solhint
54+
run: pnpm lint:sol
55+
56+
storage-layout:
57+
name: Storage Layout Check
58+
runs-on: ubuntu-latest
59+
defaults:
60+
run:
61+
working-directory: contracts/internal/host-chain
62+
steps:
63+
- name: Checkout repository
64+
uses: actions/checkout@v4
65+
66+
- name: Setup Node.js
67+
uses: actions/setup-node@v4
68+
with:
69+
node-version: "20"
70+
71+
- name: Install pnpm
72+
uses: pnpm/action-setup@v2
73+
with:
74+
version: 9
75+
76+
- name: Get pnpm store directory
77+
shell: bash
78+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
79+
80+
- name: Setup pnpm cache
81+
uses: actions/cache@v4
82+
with:
83+
path: ${{ env.STORE_PATH }}
84+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('contracts/internal/host-chain/pnpm-lock.yaml') }}
85+
restore-keys: |
86+
${{ runner.os }}-pnpm-store-
87+
88+
- name: Install dependencies
89+
run: pnpm install
90+
91+
- name: Compile contracts
92+
run: pnpm compile
93+
94+
- name: Check storage layout
95+
run: pnpm storage-layout:check
96+
97+
contract-size:
98+
name: Contract Size Check
99+
runs-on: ubuntu-latest
100+
defaults:
101+
run:
102+
working-directory: contracts/internal/host-chain
103+
steps:
104+
- name: Checkout repository
105+
uses: actions/checkout@v4
106+
107+
- name: Setup Node.js
108+
uses: actions/setup-node@v4
109+
with:
110+
node-version: "20"
111+
112+
- name: Install pnpm
113+
uses: pnpm/action-setup@v2
114+
with:
115+
version: 9
116+
117+
- name: Get pnpm store directory
118+
shell: bash
119+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
120+
121+
- name: Setup pnpm cache
122+
uses: actions/cache@v4
123+
with:
124+
path: ${{ env.STORE_PATH }}
125+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('contracts/internal/host-chain/pnpm-lock.yaml') }}
126+
restore-keys: |
127+
${{ runner.os }}-pnpm-store-
128+
129+
- name: Install dependencies
130+
run: pnpm install
131+
132+
- name: Compile contracts
133+
run: pnpm compile
134+
135+
- name: Check contract sizes
136+
run: pnpm check:size
137+
138+
slither:
139+
name: Slither Analysis
140+
runs-on: ubuntu-latest
141+
defaults:
142+
run:
143+
working-directory: contracts/internal/host-chain
144+
steps:
145+
- name: Checkout repository
146+
uses: actions/checkout@v4
147+
148+
- name: Setup Node.js
149+
uses: actions/setup-node@v4
150+
with:
151+
node-version: "20"
152+
153+
- name: Install pnpm
154+
uses: pnpm/action-setup@v2
155+
with:
156+
version: 9
157+
158+
- name: Get pnpm store directory
159+
shell: bash
160+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
161+
162+
- name: Setup pnpm cache
163+
uses: actions/cache@v4
164+
with:
165+
path: ${{ env.STORE_PATH }}
166+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('contracts/internal/host-chain/pnpm-lock.yaml') }}
167+
restore-keys: |
168+
${{ runner.os }}-pnpm-store-
169+
170+
- name: Install dependencies
171+
run: pnpm install
172+
173+
- name: Compile contracts
174+
run: pnpm compile
175+
176+
- name: Run Slither
177+
uses: crytic/slither-action@v0.4.0
178+
with:
179+
target: contracts/internal/host-chain
180+
solc-version: "0.8.25"
181+
slither-config: contracts/internal/host-chain/slither.config.json
182+
fail-on: medium
183+
184+
gas-report:
185+
name: Gas Report
186+
runs-on: ubuntu-latest
187+
defaults:
188+
run:
189+
working-directory: contracts/internal/host-chain
190+
steps:
191+
- name: Checkout repository
192+
uses: actions/checkout@v4
193+
194+
- name: Setup Node.js
195+
uses: actions/setup-node@v4
196+
with:
197+
node-version: "20"
198+
199+
- name: Install pnpm
200+
uses: pnpm/action-setup@v2
201+
with:
202+
version: 9
203+
204+
- name: Get pnpm store directory
205+
shell: bash
206+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
207+
208+
- name: Setup pnpm cache
209+
uses: actions/cache@v4
210+
with:
211+
path: ${{ env.STORE_PATH }}
212+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('contracts/internal/host-chain/pnpm-lock.yaml') }}
213+
restore-keys: |
214+
${{ runner.os }}-pnpm-store-
215+
216+
- name: Install dependencies
217+
run: pnpm install
218+
219+
- name: Run tests with gas reporting
220+
run: pnpm test
221+
env:
222+
REPORT_GAS: "true"
223+
GAS_REPORT_FILE: gas-report.txt
224+
225+
- name: Upload gas report
226+
uses: actions/upload-artifact@v4
227+
if: always()
228+
with:
229+
name: gas-report
230+
path: contracts/internal/host-chain/gas-report.txt
231+
retention-days: 30
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "solhint:recommended",
3+
"rules": {
4+
"no-empty-blocks": "off",
5+
"no-inline-assembly": "off",
6+
"one-contract-per-file": "off",
7+
"func-name-mixedcase": "off",
8+
"const-name-snakecase": "off",
9+
"compiler-version": ["error", ">=0.8.19"],
10+
"func-visibility": ["warn", { "ignoreConstructors": true }],
11+
"no-unused-import": "warn"
12+
}
13+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
contracts/tests/
3+
contracts/detereministic-tm/

contracts/internal/host-chain/contracts/TaskManager.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ library TMCommon {
101101
uint256[] memory inputs,
102102
FunctionId functionId
103103
) internal pure returns (uint256) {
104-
bytes memory combined;
104+
bytes memory combined = "";
105105
bool isTriviallyEncrypted = (functionId == FunctionId.trivialEncrypt);
106106
for (uint8 i = 0; i < inputs.length; i++) {
107107
combined = bytes.concat(combined, uint256ToBytes32(inputs[i]));
@@ -240,7 +240,6 @@ contract TaskManager is ITaskManager, Initializable, UUPSUpgradeable, Ownable2St
240240
// When set to address(0), signature verification is skipped (debug mode)
241241
address public decryptResultSigner;
242242

243-
244243
modifier onlyAggregator() {
245244
if (!aggregators[msg.sender]) {
246245
revert OnlyAggregatorAllowed(msg.sender);
@@ -632,6 +631,7 @@ contract TaskManager is ITaskManager, Initializable, UUPSUpgradeable, Ownable2St
632631
}
633632

634633
bytes32 messageHash = _computeDecryptResultHash(ctHash, result);
634+
// slither-disable-next-line unused-return
635635
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(messageHash, signature);
636636

637637
if (err != ECDSA.RecoverError.NoError || recovered == address(0)) {
@@ -681,6 +681,7 @@ contract TaskManager is ITaskManager, Initializable, UUPSUpgradeable, Ownable2St
681681
emit ProtocolNotification(ctHash, operation, errorMessage);
682682
}
683683

684+
// slither-disable-next-line unused-return
684685
function getDecryptResultSafe(uint256 ctHash) external view returns (uint256, bool) {
685686
return plaintextsStorage.getResult(ctHash);
686687
}

contracts/internal/host-chain/hardhat.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ const config: HardhatUserConfig = {
129129
outDir: "types",
130130
target: "ethers-v6",
131131
},
132+
gasReporter: {
133+
enabled: process.env.REPORT_GAS === "true",
134+
outputFile: process.env.GAS_REPORT_FILE || undefined,
135+
noColors: !!process.env.GAS_REPORT_FILE,
136+
},
132137
};
133138

134139
export default config;

contracts/internal/host-chain/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
"task:deploy": "hardhat deploy",
1414
"deploy:deterministicTM": "hardhat compile && hardhat task:deployDeterministicTM",
1515
"task:upgradeTM": "hardhat task:upgradeTM",
16-
"deploy:sepolia": "hardhat deploy --network sepolia"
16+
"deploy:sepolia": "hardhat deploy --network sepolia",
17+
"storage-layout:generate": "hardhat task:storage-layout --network hardhat",
18+
"storage-layout:check": "hardhat task:storage-layout --network hardhat --check",
19+
"check:size": "hardhat task:check-contract-size --network hardhat",
20+
"lint:sol": "solhint 'contracts/**/*.sol'"
1721
},
1822
"author": "Fhenix",
1923
"license": "MIT",
@@ -49,6 +53,7 @@
4953
"hardhat": "^2.28.0",
5054
"hardhat-deploy": "^0.11.45",
5155
"hardhat-gas-reporter": "^1.0.10",
56+
"solhint": "^5.0.0",
5257
"mocha": "^10.8.2",
5358
"rimraf": "^5.0.10",
5459
"solidity-coverage": "^0.8.17",

0 commit comments

Comments
 (0)