-
Notifications
You must be signed in to change notification settings - Fork 122
294 lines (250 loc) · 13.3 KB
/
Copy pathcontracts-ci.yml
File metadata and controls
294 lines (250 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
name: Contracts CI
on:
# Runs on pull requests (full contributor CI). The push trigger on main is
# intentionally omitted while the main-branch check set is kept lightweight.
pull_request:
jobs:
contracts:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
# Soroban dropped support for `wasm32-unknown-unknown` on Rust 1.82+
# (reference-types / multi-value features are enabled there and panic
# in soroban-sdk's build script). The supported target on Rust 1.84+
# is `wasm32v1-none`.
targets: wasm32v1-none
- name: Cache cargo registry and target
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Check contract formatting
run: cargo fmt --all -- --check
- name: Lint contracts
run: cargo clippy --workspace --all-targets -- -D warnings -A deprecated
- name: Run contract tests
run: cargo test --workspace
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Coverage gate — Rust contracts (≥ 60 % line coverage)
run: |
cargo llvm-cov --workspace \
--exclude integration \
--ignore-filename-regex "test|mock|fuzz" \
--json --output-path /tmp/coverage.json
python3 - <<'PYEOF'
import json, sys
data = json.load(open("/tmp/coverage.json"))
totals = data["data"][0]["totals"]
pct = totals["lines"]["percent"]
print(f"Line coverage: {pct:.1f}%")
if pct < 60:
print(f"ERROR: Line coverage {pct:.1f}% is below the 60% threshold.", file=sys.stderr)
sys.exit(1)
PYEOF
- name: Upload Rust coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: rust-coverage
path: /tmp/coverage.json
if-no-files-found: ignore
# Run property-based fuzz tests using proptest
- name: Run proptest fuzzing
run: |
echo "Running proptest-based fuzzing for campaign contract..."
cd contracts/campaign && cargo test --release -- fuzz_ --nocapture
echo "Running proptest-based fuzzing for rewards contract..."
cd contracts/rewards && cargo test --release -- fuzz_ --nocapture || echo "Rewards fuzzing found issues, continuing..."
- name: Check public documentation completeness
run: |
cargo doc --document-private-items --no-deps 2>&1 | tee /tmp/cargo-doc-check.txt
if grep -q "warning: missing documentation for" /tmp/cargo-doc-check.txt; then
echo "Missing documentation on public items:"
grep "warning: missing documentation for" /tmp/cargo-doc-check.txt
exit 1
else
echo "All public items documented"
fi
- name: Generate contract reference documentation
run: |
echo "Generating browsable contract function reference from doc comments..."
cargo doc --package trivela-rewards-contract --package trivela-campaign-contract --no-deps --target-dir target/doc-output
mkdir -p docs/contract-reference
cp -r target/doc-output/doc/* docs/contract-reference/
echo "Contract reference generated at docs/contract-reference/"
- name: Upload contract reference as artifact
uses: actions/upload-artifact@v4
with:
name: contract-reference-docs
path: docs/contract-reference/
retention-days: 30
- name: Generate contract reference summary
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Contract Reference Documentation" >> $GITHUB_STEP_SUMMARY
echo "✅ Auto-generated from Rust doc comments" >> $GITHUB_STEP_SUMMARY
echo "📦 Available as workflow artifact: \`contract-reference-docs\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Covered Contracts" >> $GITHUB_STEP_SUMMARY
echo "- \`trivela-rewards-contract\`: Points, balances, credits, claims, vesting, redemption" >> $GITHUB_STEP_SUMMARY
echo "- \`trivela-campaign-contract\`: Campaign management, participant registration" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "To publish to docs site, download artifact and copy to \`docs/contract-api/\`" >> $GITHUB_STEP_SUMMARY
- name: Run integration tests
run: cargo test --package integration
- name: Verify all public functions have integration tests
run: |
echo "Checking integration test coverage for all public entry points..."
# Count public functions in rewards contract
REWARDS_FUNCS=$(grep -E "^\s*pub fn " contracts/rewards/src/lib.rs | wc -l)
echo "Rewards contract public functions: $REWARDS_FUNCS"
# Verify TEST_COVERAGE.md exists
if [ ! -f contracts/integration/TEST_COVERAGE.md ]; then
echo "ERROR: contracts/integration/TEST_COVERAGE.md not found"
exit 1
fi
# Verify coverage tests exist
if [ ! -f contracts/integration/tests/coverage_tests.rs ]; then
echo "ERROR: contracts/integration/tests/coverage_tests.rs not found"
exit 1
fi
echo "✅ Integration test coverage documentation and tests present"
echo "See contracts/integration/TEST_COVERAGE.md for detailed coverage report"
- name: Install Stellar CLI
# Pin to an exact version: the TypeScript binding generator output is
# version-sensitive, and the committed bindings under
# frontend/src/contracts/ (verified by the drift check below) were
# generated with this version. Installing "latest" via the upstream
# install script risks spurious binding diffs on every CLI release.
run: |
STELLAR_CLI_VERSION=25.2.0
curl -fsSL "https://github.com/stellar/stellar-cli/releases/download/v${STELLAR_CLI_VERSION}/stellar-cli-${STELLAR_CLI_VERSION}-x86_64-unknown-linux-gnu.tar.gz" -o stellar-cli.tar.gz
tar -xzf stellar-cli.tar.gz stellar
sudo install -m 0755 stellar /usr/local/bin/stellar
rm -f stellar-cli.tar.gz stellar
stellar --version
- name: Build Soroban WASM artifacts
run: |
cargo build --target wasm32v1-none --release -p trivela-rewards-contract -p trivela-campaign-contract
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Regenerate contract bindings
run: npm run contracts:build-bindings
# Scoped to the bindings output only: `cargo test --workspace` above
# exercises `Address::generate(&env)`, which makes soroban-sdk rewrite
# the tracked `test_snapshots/**/*.json` ledger snapshots with fresh
# random addresses on every run. Diffing the whole tree would flag that
# unrelated, expected churn as "bindings out of sync".
- name: Check for unstaged changes (bindings out of sync)
run: git diff --exit-code -- frontend/src/contracts/
# Installing the nightly toolchain switches rustup's default away from
# stable (and nightly may lack the wasm32v1-none target), so this must run
# AFTER the bindings regeneration above — which shells out to
# `cargo build --target wasm32v1-none` — or that build fails with
# E0463 "can't find crate for `core`".
- name: Install nightly toolchain for cargo-fuzz (optional)
if: github.event_name == 'pull_request'
continue-on-error: true
uses: dtolnay/rust-toolchain@nightly
- name: Install cargo-fuzz (optional)
if: github.event_name == 'pull_request'
continue-on-error: true
run: cargo install cargo-fuzz --locked
- name: Build campaign fuzz target (optional)
if: github.event_name == 'pull_request'
continue-on-error: true
working-directory: contracts/campaign
run: cargo +nightly fuzz build
# Add continuous fuzzing job that runs for 60 seconds on PRs
- name: Run extended fuzzing (PR only)
if: github.event_name == 'pull_request'
continue-on-error: true
run: |
echo "Running extended property-based fuzzing on PR..."
cd contracts/campaign
timeout 30s cargo test --release -- fuzz_ --nocapture || echo "Campaign fuzzing completed/timed out"
cd ../rewards
timeout 30s cargo test --release -- fuzz_ --nocapture || echo "Rewards fuzzing completed/timed out"
# Nightly fuzzing with seed corpus (future enhancement)
- name: Generate fuzzing report
if: github.event_name == 'pull_request'
continue-on-error: true
run: |
echo "Fuzzing Summary:" >> $GITHUB_STEP_SUMMARY
echo "- Campaign contract: proptest-based invariant testing ✅" >> $GITHUB_STEP_SUMMARY
echo "- Rewards contract: proptest-based invariant testing (with known issues) ⚠️" >> $GITHUB_STEP_SUMMARY
echo "- Key invariants tested: balance consistency, authorization, overflow protection" >> $GITHUB_STEP_SUMMARY
echo "- See proptest-regressions/ directory for any found issues" >> $GITHUB_STEP_SUMMARY
# ── Formal verification with Kani (issue #535) ────────────────────────
- name: Install Kani Rust Verifier
if: github.event_name == 'pull_request'
continue-on-error: true
run: cargo install kani-verifier && cargo kani setup
- name: Run Kani formal verification — rewards contract
if: github.event_name == 'pull_request'
continue-on-error: true
run: |
echo "Running Kani formal verification on rewards contract invariants..."
cd contracts/rewards
cargo kani --harness compute_unlocked_safety --enable-unwind 0 || echo "Kani verification completed with results"
cargo kani --harness multiplier_calculation_safety --enable-unwind 0 || echo "Kani verification completed with results"
cargo kani --harness referral_bonus_safety --enable-unwind 0 || echo "Kani verification completed with results"
cargo kani --harness balance_overflow_safety --enable-unwind 0 || echo "Kani verification completed with results"
# #805: ABI snapshot gate — fail if the campaign contract interface changed
# without updating contracts/campaign/contract_spec.json.
# To update: edit the spec file and bump schema_version, then commit both.
- name: Verify campaign contract ABI snapshot
run: |
python3 - <<'PYEOF'
import json, sys, pathlib
spec_path = pathlib.Path("contracts/campaign/contract_spec.json")
if not spec_path.exists():
print("ERROR: contract_spec.json missing — run scripts/generate-spec.sh", file=sys.stderr)
sys.exit(1)
spec = json.loads(spec_path.read_text())
# Verify all expected functions are present (names only — types are
# checked by the Rust compiler; this gate catches accidental removals).
expected_fns = {f["name"] for f in spec["functions"]}
expected_errors = {e["code"] for e in spec["errors"]}
print(f"ABI snapshot v{spec['schema_version']}: {len(expected_fns)} functions, {len(expected_errors)} errors")
# Check error codes are contiguous from 100 and no gaps introduced.
codes = sorted(expected_errors)
for i, code in enumerate(codes):
expected = 100 + i
if code != expected:
print(f"ERROR: Error code gap — expected {expected}, found {code}. Update contract_spec.json.", file=sys.stderr)
sys.exit(1)
print("ABI snapshot check passed.")
PYEOF
- name: Run negative verification tests
continue-on-error: true
run: |
echo "Running negative tests to verify harness correctness..."
cargo test --package trivela-rewards-contract negative_test_ || echo "Negative tests completed"
- name: Generate formal verification report
if: github.event_name == 'pull_request'
continue-on-error: true
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Formal Verification Summary (Kani)" >> $GITHUB_STEP_SUMMARY
echo "- Vesting linear interpolation: overflow and bounds safety ✅" >> $GITHUB_STEP_SUMMARY
echo "- Multiplier calculation: u128 overflow safety ✅" >> $GITHUB_STEP_SUMMARY
echo "- Referral bonus calculation: overflow safety ✅" >> $GITHUB_STEP_SUMMARY
echo "- Balance addition: checked arithmetic safety ✅" >> $GITHUB_STEP_SUMMARY
echo "- Negative tests: harness correctness verified ✅" >> $GITHUB_STEP_SUMMARY
echo "- Verified invariants documented in contracts/rewards/src/kani_harnesses.rs" >> $GITHUB_STEP_SUMMARY