Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
70d5b6e
kani: make settle_warmup full model branch-accurate
aeyakovenko Feb 23, 2026
700b856
fix: liquidation path must reset warmup slope per spec §5.4
aeyakovenko Feb 27, 2026
dc2d67f
fix(kani): strengthen i5 warmup assertion, clarify i8 equity comments
aeyakovenko Mar 4, 2026
4a7edee
Add fees_earned_total to Account for LP reward tracking
aeyakovenko Mar 5, 2026
d1312ed
fix(kani): close MM<=equity<IM gap in withdrawal margin proof
aeyakovenko Mar 6, 2026
da085a0
enforce maintenance_margin_bps < initial_margin_bps in constructor
aeyakovenko Mar 6, 2026
85d0b28
fix(kani): explicit three-region withdrawal margin assertions
aeyakovenko Mar 6, 2026
9bc0fab
feat: implement v9.4 spec — lazy A/K ADL + wide arithmetic + deferred…
aeyakovenko Mar 13, 2026
36381d6
feat(kani): add 37 A/K mechanism proof harnesses in tests/ak.rs
aeyakovenko Mar 14, 2026
1f0892c
fix(kani): strengthen 11 weak/unit-test ak.rs proofs per audit
aeyakovenko Mar 14, 2026
aa85ab1
docs: update spec to v9.5
aeyakovenko Mar 14, 2026
f65ec5d
feat: implement spec v9.5 changes
aeyakovenko Mar 14, 2026
8823e96
Update spec.md
aeyakovenko Mar 14, 2026
294b591
fix: spec v9.5 compliance — epoch snaps, OI assertions, version string
aeyakovenko Mar 14, 2026
1ab3574
fix: 7 engine safety fixes + 26 new Kani proofs (65 total)
aeyakovenko Mar 15, 2026
ce88753
fix(kani): convert engine proofs to small-model, fix unwind bounds
aeyakovenko Mar 15, 2026
c669d8a
feat(kani): add 16 real-engine integration proofs (Tier 11)
aeyakovenko Mar 15, 2026
9c55eef
fix(kani): rewrite stale t5_23 to dynamic dust bound, add t6_26b nonz…
aeyakovenko Mar 15, 2026
c393329
fix: v10.0 — 4 engine correctness fixes + proof suite updates
aeyakovenko Mar 15, 2026
7d9603e
fix: enqueue_adl global A truncation dust must feed phantom_dust_bound
aeyakovenko Mar 15, 2026
b5d0b95
docs: spec v10.0 — add global A truncation dust to enqueue_adl §5.6
aeyakovenko Mar 15, 2026
ff559c9
spec
aeyakovenko Mar 16, 2026
af8cb23
feat: implement spec v10.5 changes — payer-driven funding, fused ADL,…
aeyakovenko Mar 16, 2026
857a102
fix: proof suite — false theorem, weak assertions, inductive dust bound
aeyakovenko Mar 16, 2026
ce12aac
fix: 5 safety issues — error propagation, overflow guards, clamp-not-…
aeyakovenko Mar 16, 2026
6bb68ee
fix: proof suite audit — cross-multiplied invariant, dimensional fix,…
aeyakovenko Mar 16, 2026
b65a14a
fix: 5 implementation issues — error propagation, self-trade, same-sl…
aeyakovenko Mar 16, 2026
c480367
fix(proofs): strengthen t1_8/t1_9 bounds, t11_52 restart path, t13_54…
aeyakovenko Mar 16, 2026
2c1029f
fix: run end-of-instruction resets unconditionally in liquidate_at_or…
aeyakovenko Mar 16, 2026
c8a5f0a
refactor: reorganize proof suite into 6 topic files + add 20 unit tests
aeyakovenko Mar 17, 2026
4777cf5
fix: 3 critical issues — withdraw haircut inflation, funding rate DoS…
aeyakovenko Mar 17, 2026
558e87a
fix(proofs): 5 liveness proof issues — unreachable state, missing ass…
aeyakovenko Mar 17, 2026
1afa183
fix(proofs): strengthen t1_8/t1_9 bounds, t11_52 restart path, t13_54…
aeyakovenko Mar 17, 2026
95d4937
fix(proofs): eliminate vacuous proofs, strengthen assertions, add mis…
aeyakovenko Mar 17, 2026
f7d2de8
fix: 3 safety issues — GC dust predicate, fee seniority, min liquidat…
aeyakovenko Mar 17, 2026
d114bf2
docs: rewrite README — H keeps exits fair, A/K keeps overhang clearin…
aeyakovenko Mar 17, 2026
212e198
docs: remove proof stats from README
aeyakovenko Mar 17, 2026
d09d36d
docs: simplify README to high-level math only
aeyakovenko Mar 17, 2026
d64bab9
docs: alternative to ADL queues, not ADL itself (A/K is an ADL)
aeyakovenko Mar 17, 2026
ddd59c9
fix(proofs): 7 proof issues — forbidden input, weak assertions, missi…
aeyakovenko Mar 17, 2026
1a3b60b
docs: add fairness exactness caveat for H vs A/K
aeyakovenko Mar 17, 2026
9d962d7
fix(proofs): add min_liquidation_abs safety + trading loss seniority …
aeyakovenko Mar 17, 2026
c7f8f68
fix: reject fee_credits == i128::MIN per spec §2.1 + proof
aeyakovenko Mar 17, 2026
173e6ca
fix(proofs): replace contradictory i128::MIN robustness test with rej…
aeyakovenko Mar 17, 2026
b124b27
feat: migrate to native 128-bit architecture per spec v11.5
aeyakovenko Mar 17, 2026
10312f5
spec
aeyakovenko Mar 17, 2026
f4930c2
fix: spec v11.9 compliance audit — 10 normative fixes
aeyakovenko Mar 17, 2026
60d3629
fix(proofs): 4 proof audit fixes per v11.9 spec review
aeyakovenko Mar 17, 2026
fed1be7
chore: reduce LIQ_BUDGET_PER_CRANK to 64, make free_slot pub
aeyakovenko Mar 17, 2026
04762c8
fix: remove redundant touch_account_full from liquidation per spec §9.4
aeyakovenko Mar 18, 2026
82425cd
spec
aeyakovenko Mar 18, 2026
afacca8
spec
aeyakovenko Mar 18, 2026
b246b12
fix: v11.12 spec compliance — engine + 3 proof fixes
aeyakovenko Mar 18, 2026
0afb13a
fix(proofs): 9 proof fixes for base-10 scaling + spec compliance
aeyakovenko Mar 18, 2026
b283f16
feat: two-phase keeper barrier wave (spec addendum A2)
aeyakovenko Mar 18, 2026
1d078bb
feat: v11.21 spec compliance — off-chain shortlist keeper, split equi…
aeyakovenko Mar 19, 2026
ff71199
fix(proofs): update 4 stale proofs for v11.21 compliance
aeyakovenko Mar 20, 2026
640055a
fix: 3 spec compliance bugs — insurance-first ADL, liq-side reset, ex…
aeyakovenko Mar 20, 2026
b261865
fix(proofs): strengthen 8 weak proofs per audit
aeyakovenko Mar 20, 2026
3b4f384
fix: 4 audit findings — bilateral fee, risk-reducing proof, ADL pipel…
aeyakovenko Mar 20, 2026
ce8512b
fix(proofs): eliminate vacuous/tautological proofs, strengthen assert…
aeyakovenko Mar 20, 2026
f832aca
fix: 3 security audit findings + TDD proofs
aeyakovenko Mar 20, 2026
9feedae
Update spec.md
aeyakovenko Mar 19, 2026
aadea01
fix: v11.26 spec compliance — flat-close guard, fee-neutral risk-redu…
aeyakovenko Mar 20, 2026
7565a1a
feat: add MIN_NONZERO_MM_REQ / MIN_NONZERO_IM_REQ margin floors (spec…
aeyakovenko Mar 20, 2026
13a22d3
fix: GC reclaims flat-dust accounts below MIN_INITIAL_DEPOSIT (spec §…
aeyakovenko Mar 21, 2026
a7e2fff
docs: fix README haircut formula to use PNL_matured_pos_tot
aeyakovenko Mar 21, 2026
17d7104
fix(readme): correct haircut formula to use matured/released PnL
aeyakovenko Mar 21, 2026
b958b47
chore: update spec version reference from v11.5 to v11.26
aeyakovenko Mar 21, 2026
499b58b
feat(proofs): add 11 proofs for uncovered spec §12 test properties
aeyakovenko Mar 21, 2026
19db9bb
fix: 3 audit findings — fee sweep PnL breach, LP GC bypass, exact IM
aeyakovenko Mar 21, 2026
d33b291
fix(audit): 3 more findings — funding rate clamp, equity overflow, de…
aeyakovenko Mar 21, 2026
030b2b2
fix(audit): prevent i128::MIN negate panic in checked_u128_mul_i128 a…
aeyakovenko Mar 22, 2026
48f4452
fix(audit): 6 structural integrity fixes — atomicity, freelist, TVL b…
aeyakovenko Mar 22, 2026
d1e3ba1
fix(proofs): strengthen 6 weak/vacuous proofs found by self-audit
aeyakovenko Mar 22, 2026
ff0b25c
fix(audit): enforce fee_credits <= 0 invariant + add reclaim_empty_ac…
aeyakovenko Mar 22, 2026
ad49ad1
spec
aeyakovenko Mar 23, 2026
7830f1d
feat: v11.31 spec compliance — zero-rate funding, bilateral OI, parti…
aeyakovenko Mar 23, 2026
b576d65
docs: proof strength audit for v11.31 proofs
aeyakovenko Mar 23, 2026
a97a416
fix(proofs): upgrade 4 WEAK + 5 UNIT TEST proofs to STRONG per audit
aeyakovenko Mar 23, 2026
3db8d07
fix(audit): 4 external audit fixes + 8 Kani proofs
aeyakovenko Mar 23, 2026
94c3679
fix(audit): 3 more audit fixes — keeper griefing, missing-account saf…
aeyakovenko Mar 24, 2026
f6dd879
cleaup
aeyakovenko Mar 24, 2026
9705213
fix(audit): 4 fixes from third audit round + pre-flight partial hint …
aeyakovenko Mar 24, 2026
3e2ea1d
fix(audit): 4 fixes from fourth audit round + 8 Kani proofs
aeyakovenko Mar 24, 2026
a08a6fe
fix(audit): remove crank freshness gate from withdraw/trade + GC §2.6…
aeyakovenko Mar 24, 2026
893c8da
fix(audit): reduce internal method visibility + add insurance_floor t…
aeyakovenko Mar 24, 2026
b82d6ba
fix(proofs): 11 proof fixes — 2 broken + 9 gap-filling Kani proofs
aeyakovenko Mar 24, 2026
e4be4db
fix(audit): set_owner guard + stronger pre-flight proof with oracle s…
aeyakovenko Mar 25, 2026
b64ea60
feat: add close_account_resolved for frozen/resolved market path
aeyakovenko Mar 25, 2026
091163e
feat: enable maintenance fees (spec §8.2)
aeyakovenko Mar 25, 2026
6b6a0a2
fix: stale comment + close_account_resolved stale count decrement
aeyakovenko Mar 25, 2026
a4c4077
chore: update spec version references to v11.31
aeyakovenko Mar 25, 2026
cee584a
refactor: AccountKind enum → u8 to eliminate zc UB class
aeyakovenko Mar 27, 2026
03e37a9
fix: disable maintenance fee realization + add lifecycle method
aeyakovenko Mar 27, 2026
4b84390
feat: wire funding rate into engine K-coefficient transfers
aeyakovenko Mar 27, 2026
58066af
revert: restore zero-rate funding per spec v11.31 §4.12
aeyakovenko Mar 27, 2026
cd0b3df
feat: live premium-based funding (spec v12.0.2)
aeyakovenko Mar 28, 2026
a55fe45
fix(proofs): complete funding proof coverage gaps
aeyakovenko Mar 28, 2026
a5d2ff5
fix(tests): non-vacuous funding integration tests
aeyakovenko Mar 28, 2026
39ae652
fix: 4 spec violations from line-by-line audit
aeyakovenko Mar 28, 2026
1f60022
fix: abs_size undefined variable in execute_trade
aeyakovenko Mar 28, 2026
0403700
fix: remove GC from keeper_crank + deposit_fee_credits slot advance
aeyakovenko Mar 28, 2026
be77a9d
fix: 6 spec violations from re-audit
aeyakovenko Mar 28, 2026
6c963dd
fix: 3 issues from final re-audit
aeyakovenko Mar 28, 2026
8181322
chore: make accrue_market_to public
aeyakovenko Mar 30, 2026
c636dac
feat: force_close_resolved — settles K-pair PnL + zeros position
aeyakovenko Apr 3, 2026
a9b5017
test: comprehensive force_close_resolved test suite
aeyakovenko Apr 3, 2026
fa3ec3c
spec
aeyakovenko Apr 3, 2026
e357d43
feat: enable recurring maintenance fees per updated spec §8.2
aeyakovenko Apr 3, 2026
a874acd
fix: charge_fee_to_insurance drops excess + recompute_r_last returns …
aeyakovenko Apr 3, 2026
79c0c92
fix: MAX_PROTOCOL_FEE_ABS raised to 10^36 per spec v12.1.0
aeyakovenko Apr 3, 2026
55013a8
fix: validate_keeper_hint models capped fee + remove GC fee realization
aeyakovenko Apr 3, 2026
2430112
fix(proofs): substantiveness audit — 6 vacuity fixes + 1 proof correc…
aeyakovenko Apr 3, 2026
a5197e4
fix: 3 non-minor issues + version refs updated to v12.1.0
aeyakovenko Apr 3, 2026
0082064
chore: fix kani audit script + fuzz feature dep + remove examples
aeyakovenko Apr 3, 2026
acd4361
fix(fuzz): account_count scans full slab, not max_accounts
aeyakovenko Apr 3, 2026
d6cbe0e
fix: per-side actual fee in margin enforcement + stale comment
aeyakovenko Apr 3, 2026
5d53701
fix: force_close_resolved OI decrement + corrupt a_basis rejection
aeyakovenko Apr 4, 2026
2598a93
fix: 4 non-minor issues from review pass
aeyakovenko Apr 4, 2026
d94d064
fix: 6 issues — funding_rate preflight, scratch K, deposit ghost, fees
aeyakovenko Apr 4, 2026
03737b1
fix: force_close_resolved fee ordering matches touch_account_full
aeyakovenko Apr 4, 2026
c7e0cb0
fix: 3 broken proofs + address reviewer's remaining issues
aeyakovenko Apr 4, 2026
fb90d8d
fix: all 37 failing/timing-out Kani proofs now pass
aeyakovenko Apr 4, 2026
f12a651
fix: phantom dust accounting + insurance_floor dedup
aeyakovenko Apr 4, 2026
6f85a89
fix: LP fee tracking + remove dead liquidation_buffer_bps
aeyakovenko Apr 4, 2026
4e7566e
refactor: suffix non-atomic functions with _not_atomic
aeyakovenko Apr 4, 2026
5bb71a3
fix: atomicity contract + public add_user/add_lp
aeyakovenko Apr 4, 2026
2338184
fix: 5 issues — LP fees, force_close slot, add_user/lp, cleanup
aeyakovenko Apr 4, 2026
0521252
fix: 7 spec compliance issues from review
aeyakovenko Apr 5, 2026
60bc843
fix: 3 issues — fee_debt_sweep overflow, GC fee ordering, settle_losses
aeyakovenko Apr 5, 2026
7575941
fix: force_close_resolved OI decrement + liveness test
aeyakovenko Apr 5, 2026
1ad0ec7
fix: force_close_resolved bilateral OI decrement (critical liveness)
aeyakovenko Apr 5, 2026
d9a09ff
fix: 2 proof issues — force_close slot mismatch + vacuity cover
aeyakovenko Apr 5, 2026
733ed6d
fix: 2 timeout proofs — constrain symbolic ranges
aeyakovenko Apr 5, 2026
a3e7e88
test: add property 31 explicit coverage
aeyakovenko Apr 6, 2026
d968fe5
merge: sync upstream aeyakovenko/percolator v12.1 (136 commits)
dcccrypto Apr 6, 2026
5f1f549
fix: remove duplicate impl block — merge conflict cleanup
dcccrypto Apr 6, 2026
c3c720b
fix: enforce min_initial_deposit > 0 in RiskParams::validate()
dcccrypto Apr 6, 2026
dd8e946
fix: correct unilateral OI decrement in force_close_resolved_not_atomic
dcccrypto Apr 6, 2026
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test = [] # Use MAX_ACCOUNTS=64 (~0.17 SOL rent)
small = [] # Use MAX_ACCOUNTS=256 (~0.68 SOL rent)
medium = [] # Use MAX_ACCOUNTS=1024 (~2.7 SOL rent)
# No feature = MAX_ACCOUNTS=4096 (~6.9 SOL rent)
fuzz = [] # Enable fuzzing tests
fuzz = ["test"] # Enable fuzzing tests (includes test feature for test_visible helpers)

[profile.release]
lto = "fat"
Expand Down
406 changes: 394 additions & 12 deletions scripts/proof-strength-audit-results.md

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions scripts/run_kani_full_audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ cd /home/anatoly/percolator
OUTFILE="/home/anatoly/percolator/kani_audit_full.tsv"
echo -e "proof\ttime_s\tstatus" > "$OUTFILE"

PROOFS=$(grep -A5 'kani::proof' tests/kani.rs | grep '^\s*fn ' | sed 's/.*fn \([a-z_0-9]*\).*/\1/' | sort)
# Collect all proof harness names from all proof files
PROOFS=$(grep -rh '#\[kani::proof\]' tests/proofs_*.rs -A 3 | grep '^\s*fn ' | sed 's/.*fn \([a-z_0-9]*\).*/\1/' | sort -u)
Comment on lines +9 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

current=$(
  grep -rh '#\[kani::proof\]' tests/proofs_*.rs -A 3 \
    | grep '^[[:space:]]*fn ' \
    | sed -E 's/.*fn[[:space:]]+([A-Za-z0-9_]+).*/\1/' \
    | sort -u
)

expected=$(
python - <<'PY'
import pathlib, re
proofs = set()
for path in pathlib.Path("tests").glob("proofs_*.rs"):
    lines = path.read_text().splitlines()
    i = 0
    while i < len(lines):
        if "#[kani::proof]" in lines[i]:
            j = i + 1
            while j < len(lines) and re.match(r'^\s*#\[', lines[j]):
                j += 1
            if j < len(lines):
                m = re.match(r'^\s*fn\s+([A-Za-z0-9_]+)\s*\(', lines[j])
                if m:
                    proofs.add(m.group(1))
            i = j
        else:
            i += 1
for name in sorted(proofs):
    print(name)
PY
)

echo "Harnesses missed by current discovery:"
comm -13 <(printf '%s\n' "$current") <(printf '%s\n' "$expected")

Repository: dcccrypto/percolator

Length of output: 163


🏁 Script executed:

# Check if the script file exists and examine lines 9-10
if [ -f scripts/run_kani_full_audit.sh ]; then
  echo "=== File exists ===" 
  wc -l scripts/run_kani_full_audit.sh
  echo "=== Lines 1-15 ==="
  head -15 scripts/run_kani_full_audit.sh | cat -n
else
  echo "File does not exist"
fi

# Check what proof files exist
echo "=== Proof files in tests/ ==="
fd 'proofs_.*\.rs$' tests/ 2>/dev/null || find tests -name 'proofs_*.rs' 2>/dev/null || echo "No proofs files found"

Repository: dcccrypto/percolator

Length of output: 945


🏁 Script executed:

# Sample harnesses from proofs_invariants.rs and proofs_audit.rs
echo "=== proofs_invariants.rs - first 100 lines ==="
head -100 tests/proofs_invariants.rs | cat -n

echo ""
echo "=== Check for #[kani::proof] patterns in proofs_invariants.rs ==="
grep -n '#\[kani' tests/proofs_invariants.rs | head -30

Repository: dcccrypto/percolator

Length of output: 5050


🏁 Script executed:

echo "=== Check for should_panic in proofs_audit.rs ==="
grep -n '#\[kani' tests/proofs_audit.rs | head -30

echo ""
echo "=== Sample harness with should_panic ==="
grep -B2 -A6 'should_panic' tests/proofs_audit.rs | head -30

Repository: dcccrypto/percolator

Length of output: 1594


🏁 Script executed:

echo "=== Find exact line numbers for should_panic harness ==="
grep -n "proof_config_rejects_oversized_max_accounts" tests/proofs_audit.rs

echo ""
echo "=== Show context around that harness ==="
sed -n '330,340p' tests/proofs_audit.rs | cat -n

echo ""
echo "=== Run the actual grep discovery command ==="
PROOFS=$(grep -rh '#\[kani::proof\]' tests/proofs_*.rs -A 3 | grep '^\s*fn ' | sed 's/.*fn \([a-z_0-9]*\).*/\1/' | sort -u)
echo "Discovered harnesses (showing only config_rejects ones):"
echo "$PROOFS" | grep "config_rejects" || echo "(none found with config_rejects)"

echo ""
echo "=== List all proof_* and config_* functions ==="
grep -h '^\s*fn ' tests/proofs_*.rs | sed 's/.*fn \([a-z_0-9]*\).*/\1/' | grep -E "(proof_|config_)" | sort

Repository: dcccrypto/percolator

Length of output: 7551


Harness discovery silently skips #[kani::should_panic] proofs.

The grep -A 3 pattern captures the matched line plus 3 lines after it (4 lines total). Harnesses with four attributes—#[kani::proof], #[kani::unwind], #[kani::solver], and #[kani::should_panic]—place the fn declaration at position +4, which is outside the capture range. This causes all config validation proofs (e.g., proof_config_rejects_oversized_max_accounts, proof_config_rejects_invalid_bps) to be silently dropped from the audit run.

Use an attribute-aware extraction that skips all #[…] lines following #[kani::proof]:

Suggested fix
- PROOFS=$(grep -rh '#\[kani::proof\]' tests/proofs_*.rs -A 3 | grep '^\s*fn ' | sed 's/.*fn \([a-z_0-9]*\).*/\1/' | sort -u)
+ PROOFS=$(
+   awk '
+     /#\[kani::proof\]/ { in_proof = 1; next }
+     in_proof && /^[[:space:]]*#\[/ { next }
+     in_proof && match($0, /^[[:space:]]*fn[[:space:]]+([A-Za-z0-9_]+)/, m) {
+       print m[1]
+       in_proof = 0
+     }
+   ' tests/proofs_*.rs | sort -u
+ )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Collect all proof harness names from all proof files
PROOFS=$(grep -rh '#\[kani::proof\]' tests/proofs_*.rs -A 3 | grep '^\s*fn ' | sed 's/.*fn \([a-z_0-9]*\).*/\1/' | sort -u)
# Collect all proof harness names from all proof files
PROOFS=$(
awk '
/#\[kani::proof\]/ { in_proof = 1; next }
in_proof && /^[[:space:]]*#\[/ { next }
in_proof && match($0, /^[[:space:]]*fn[[:space:]]+([A-Za-z0-9_]+)/, m) {
print m[1]
in_proof = 0
}
' tests/proofs_*.rs | sort -u
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/run_kani_full_audit.sh` around lines 9 - 10, The PROOFS extraction
uses grep -A 3 which misses functions when four attribute lines follow
#[kani::proof]; update the extraction so it skips any number of attribute lines
after #[kani::proof] and then captures the following fn name (e.g., replace the
current pipeline that uses grep -A 3 | grep '^\s*fn ' | sed ... with a single
attribute-aware command). Implement this by scanning files matching
tests/proofs_*.rs for lines with '#[kani::proof]' and then advancing past all
subsequent lines that match '^\s*#\[' until you hit a line matching '^\s*fn '
and extract the function name into PROOFS (use awk, perl, or grep -P with an
appropriate regex to achieve this).


TOTAL=$(echo "$PROOFS" | wc -l)
COUNT=0
Expand All @@ -18,7 +19,7 @@ for proof in $PROOFS; do
echo "[$COUNT/$TOTAL] Running: $proof"
START=$(date +%s)

if timeout 600 cargo kani --harness "$proof" --output-format terse 2>&1 | tail -3; then
if timeout 600 cargo kani --tests --harness "$proof" --output-format terse 2>&1 | tail -3; then
STATUS="PASS"
PASS=$((PASS + 1))
else
Expand Down
2,194 changes: 1,747 additions & 447 deletions spec.md

Large diffs are not rendered by default.

3,948 changes: 2,768 additions & 1,180 deletions src/percolator.rs

Large diffs are not rendered by default.

150 changes: 52 additions & 98 deletions src/wide_math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,12 @@ impl U256 {
/// Create from low 128 bits and high 128 bits.
#[inline]
pub const fn new(lo: u128, hi: u128) -> Self {
Self([lo as u64, (lo >> 64) as u64, hi as u64, (hi >> 64) as u64])
Self([
lo as u64,
(lo >> 64) as u64,
hi as u64,
(hi >> 64) as u64,
])
}

#[inline]
Expand Down Expand Up @@ -867,7 +872,12 @@ impl I256 {
}

fn from_lo_hi(lo: u128, hi: u128) -> Self {
Self([lo as u64, (lo >> 64) as u64, hi as u64, (hi >> 64) as u64])
Self([
lo as u64,
(lo >> 64) as u64,
hi as u64,
(hi >> 64) as u64,
])
}

fn as_raw_u256(self) -> U256 {
Expand Down Expand Up @@ -943,17 +953,18 @@ fn widening_mul_u128(a: u128, b: u128) -> (u128, u128) {
let b_lo = b as u64 as u128;
let b_hi = (b >> 64) as u64 as u128;

let ll = a_lo * b_lo; // 0..2^128
let lh = a_lo * b_hi; // 0..2^128
let hl = a_hi * b_lo; // 0..2^128
let hh = a_hi * b_hi; // 0..2^128
let ll = a_lo * b_lo; // 0..2^128
let lh = a_lo * b_hi; // 0..2^128
let hl = a_hi * b_lo; // 0..2^128
let hh = a_hi * b_hi; // 0..2^128

// Accumulate:
// result = ll + (lh + hl) << 64 + hh << 128
let (mid, mid_carry) = lh.overflowing_add(hl); // mid_carry means +2^128

let (lo, lo_carry) = ll.overflowing_add(mid << 64);
let hi = hh + (mid >> 64) + ((mid_carry as u128) << 64) + (lo_carry as u128);
let hi = hh + (mid >> 64) + ((mid_carry as u128) << 64)
+ (lo_carry as u128);
// lo_carry is at most 1, captured in hi

(lo, hi)
Expand Down Expand Up @@ -1100,7 +1111,6 @@ impl U512 {
}

/// Shift left by `bits`. Saturates to zero if bits >= 512.
#[allow(clippy::needless_range_loop)]
fn shl_u512(self, bits: u32) -> Self {
if bits >= 512 {
return Self::ZERO;
Expand All @@ -1122,7 +1132,6 @@ impl U512 {
}

/// Shift right by `bits`.
#[allow(clippy::needless_range_loop)]
fn shr_u512(self, bits: u32) -> Self {
if bits >= 512 {
return Self::ZERO;
Expand All @@ -1144,7 +1153,6 @@ impl U512 {
}

/// Subtract rhs from self. Assumes self >= rhs.
#[allow(clippy::needless_range_loop)]
fn sub_u512(self, rhs: Self) -> Self {
let mut result = [0u128; 4];
let mut borrow = false;
Expand Down Expand Up @@ -1250,10 +1258,7 @@ impl U512 {
/// Computes floor(n / d) where d > 0. Uses truncation toward zero, then
/// adjusts: if n < 0 and there is a non-zero remainder, subtract 1.
pub fn floor_div_signed_conservative(n: I256, d: U256) -> I256 {
assert!(
!d.is_zero(),
"floor_div_signed_conservative: zero denominator"
);
assert!(!d.is_zero(), "floor_div_signed_conservative: zero denominator");

if n.is_zero() {
return I256::ZERO;
Expand Down Expand Up @@ -1288,8 +1293,7 @@ pub fn floor_div_signed_conservative(n: I256, d: U256) -> I256 {
let q_final = if r.is_zero() {
q
} else {
q.checked_add(U256::ONE)
.expect("floor_div quotient overflow")
q.checked_add(U256::ONE).expect("floor_div quotient overflow")
};

// Negate q_final to get the negative I256 result.
Expand All @@ -1303,26 +1307,27 @@ pub fn floor_div_signed_conservative(n: I256, d: U256) -> I256 {
}
}

/// i128 version of floor_div_signed_conservative for compact code paths.
/// Native i128 floor division: floor(n / d) for positive d, rounding toward
/// negative infinity. Mirrors `floor_div_signed_conservative` but uses native
/// i128/u128 arithmetic for the funding-term computation (spec §5.4).
pub fn floor_div_signed_conservative_i128(n: i128, d: u128) -> i128 {
assert!(
d != 0,
"floor_div_signed_conservative_i128: zero denominator"
);
assert!(d != 0, "floor_div_signed_conservative_i128: zero denominator");

if n == 0 {
return 0;
}

if n > 0 {
// Non-negative: floor = truncation
(n as u128 / d) as i128
} else {
// Negative: floor(n/d) = -(|n| / d) - (if |n| % d != 0 then 1 else 0)
let abs_n = n.unsigned_abs();
let q = abs_n / d;
let r = abs_n % d;
let q_final = if r != 0 { q + 1 } else { q };
assert!(
q_final <= i128::MAX as u128,
"floor_div_signed_conservative_i128: result out of range"
);
assert!(q_final <= i128::MAX as u128,
"floor_div_signed_conservative_i128: result out of range");
-(q_final as i128)
Comment on lines 1313 to 1331

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle the exact i128::MIN result instead of rejecting it.

floor_div_signed_conservative_i128(i128::MIN, 1) and wide_signed_mul_div_floor_from_k_pair(1, 0, i128::MIN, 1) should both return i128::MIN. The current <= i128::MAX guard rejects that valid boundary, and the following negation would overflow if it were reached.

Suggested fix
 pub fn floor_div_signed_conservative_i128(n: i128, d: u128) -> i128 {
@@
-        let q_final = if r != 0 { q + 1 } else { q };
-        assert!(q_final <= i128::MAX as u128,
-            "floor_div_signed_conservative_i128: result out of range");
-        -(q_final as i128)
+        let q_final = if r != 0 { q + 1 } else { q };
+        if q_final == (1u128 << 127) {
+            i128::MIN
+        } else {
+            assert!(q_final <= i128::MAX as u128,
+                "floor_div_signed_conservative_i128: result out of range");
+            -(q_final as i128)
+        }
     }
 }
@@
     if d.is_negative() {
@@
-        let mag_u128 = mag.try_into_u128().expect("mag exceeds u128");
-        assert!(mag_u128 <= i128::MAX as u128, "wide_signed_mul_div_floor_from_k_pair: mag > i128::MAX");
-        -(mag_u128 as i128)
+        let mag_u128 = mag.try_into_u128().expect("mag exceeds u128");
+        if mag_u128 == (1u128 << 127) {
+            i128::MIN
+        } else {
+            assert!(mag_u128 <= i128::MAX as u128, "wide_signed_mul_div_floor_from_k_pair: mag > i128::MAX");
+            -(mag_u128 as i128)
+        }
     } else {

Also applies to: 1498-1526

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/wide_math.rs` around lines 1313 - 1331, The function
floor_div_signed_conservative_i128 currently rejects the valid boundary case
i128::MIN because it asserts q_final <= i128::MAX and then negates, which would
overflow; change the negative branch to handle q_final specially: if q_final ==
(i128::MAX as u128) + 1 then return i128::MIN directly, otherwise assert q_final
<= i128::MAX and return -(q_final as i128). Apply the same pattern to the
corresponding logic in wide_signed_mul_div_floor_from_k_pair so the i128::MIN
result is accepted and not negated into overflow.

}
}
Expand Down Expand Up @@ -1352,10 +1357,7 @@ pub fn mul_div_floor_u256(a: U256, b: U256, d: U256) -> U256 {
/// Like mul_div_floor_u256 but also returns the remainder.
/// Returns (floor(a * b / d), (a * b) mod d).
pub fn mul_div_floor_u256_with_rem(a: U256, b: U256, d: U256) -> (U256, U256) {
assert!(
!d.is_zero(),
"mul_div_floor_u256_with_rem: zero denominator"
);
assert!(!d.is_zero(), "mul_div_floor_u256_with_rem: zero denominator");
let product = U512::mul_u256(a, b);
product.div_rem_by_u256(d)
}
Expand Down Expand Up @@ -1416,21 +1418,15 @@ pub fn fee_debt_u128_checked(fee_credits: i128) -> u128 {
/// Uses the sign of `k_diff`. Computes `abs_basis * abs(k_diff)` as U512,
/// then applies floor_div_signed_conservative logic.
pub fn wide_signed_mul_div_floor(abs_basis: U256, k_diff: I256, denominator: U256) -> I256 {
assert!(
!denominator.is_zero(),
"wide_signed_mul_div_floor: zero denominator"
);
assert!(!denominator.is_zero(), "wide_signed_mul_div_floor: zero denominator");

if k_diff.is_zero() || abs_basis.is_zero() {
return I256::ZERO;
}

let negative = k_diff.is_negative();
let abs_k = if negative {
assert!(
k_diff != I256::MIN,
"wide_signed_mul_div_floor: k_diff == I256::MIN"
);
assert!(k_diff != I256::MIN, "wide_signed_mul_div_floor: k_diff == I256::MIN");
k_diff.abs_u256()
} else {
k_diff.abs_u256()
Expand All @@ -1448,15 +1444,13 @@ pub fn wide_signed_mul_div_floor(abs_basis: U256, k_diff: I256, denominator: U25
let q_final = if r.is_zero() {
q
} else {
q.checked_add(U256::ONE)
.expect("wide_signed_mul_div_floor quotient overflow")
q.checked_add(U256::ONE).expect("wide_signed_mul_div_floor quotient overflow")
};
if q_final.is_zero() {
I256::ZERO
} else {
let qi = I256::from_raw_u256(q_final);
qi.checked_neg()
.expect("wide_signed_mul_div_floor result out of I256 range")
qi.checked_neg().expect("wide_signed_mul_div_floor result out of I256 range")
}
}
}
Expand All @@ -1473,7 +1467,7 @@ impl I256 {
}

// ============================================================================
// §4.8 v11.5 Native 128-bit Arithmetic Helpers
// §4.8 v11.31 Native 128-bit Arithmetic Helpers
// ============================================================================

/// Native multiply-divide floor. Product a*b must not overflow u128. Panics on d==0.
Expand All @@ -1488,48 +1482,33 @@ pub fn mul_div_ceil_u128(a: u128, b: u128, d: u128) -> u128 {
assert!(d > 0, "mul_div_ceil_u128: division by zero");
let p = a.checked_mul(b).expect("mul_div_ceil_u128: a*b overflow");
let q = p / d;
if !p.is_multiple_of(d) {
q + 1
} else {
q
}
if p % d != 0 { q + 1 } else { q }
}

/// Exact wide multiply-divide floor using U256 intermediate.
/// Used for haircut paths where a*b can exceed u128::MAX.
pub fn wide_mul_div_floor_u128(a: u128, b: u128, d: u128) -> u128 {
assert!(d > 0, "wide_mul_div_floor_u128: division by zero");
let result = mul_div_floor_u256(U256::from_u128(a), U256::from_u128(b), U256::from_u128(d));
result
.try_into_u128()
.expect("wide_mul_div_floor_u128: result exceeds u128")
result.try_into_u128().expect("wide_mul_div_floor_u128: result exceeds u128")
}

/// Safe K-difference settlement (spec §4.8 lines 720-732).
/// Computes K-difference in wide intermediate, then multiplies and divides.
pub fn wide_signed_mul_div_floor_from_k_pair(
abs_basis: u128,
k_now: i128,
k_then: i128,
den: u128,
) -> i128 {
pub fn wide_signed_mul_div_floor_from_k_pair(abs_basis: u128, k_then: i128, k_now: i128, den: u128) -> i128 {
assert!(den > 0, "wide_signed_mul_div_floor_from_k_pair: den == 0");
// Compute d = k_now - k_then in wide signed to avoid i128 overflow
// Compute d = k_now - k_then in wide signed to avoid i128 overflow (spec §4.8)
let k_now_wide = I256::from_i128(k_now);
let k_then_wide = I256::from_i128(k_then);
let d = k_now_wide
.checked_sub(k_then_wide)
.expect("K-diff overflow in wide");
let d = k_now_wide.checked_sub(k_then_wide).expect("K-diff overflow in wide");
if d.is_zero() || abs_basis == 0 {
return 0i128;
}
let abs_d = d.abs_u256();
let abs_basis_u256 = U256::from_u128(abs_basis);
let den_u256 = U256::from_u128(den);
// p = abs_basis * abs(d), exact wide product
let p = abs_basis_u256
.checked_mul(abs_d)
.expect("wide product overflow");
let p = abs_basis_u256.checked_mul(abs_d).expect("wide product overflow");
let (q, rem) = div_rem_u256(p, den_u256);
if d.is_negative() {
// mag = q + 1 if r != 0 else q
Expand All @@ -1539,17 +1518,11 @@ pub fn wide_signed_mul_div_floor_from_k_pair(
q
};
let mag_u128 = mag.try_into_u128().expect("mag exceeds u128");
assert!(
mag_u128 <= i128::MAX as u128,
"wide_signed_mul_div_floor_from_k_pair: mag > i128::MAX"
);
assert!(mag_u128 <= i128::MAX as u128, "wide_signed_mul_div_floor_from_k_pair: mag > i128::MAX");
-(mag_u128 as i128)
} else {
let q_u128 = q.try_into_u128().expect("quotient exceeds u128");
assert!(
q_u128 <= i128::MAX as u128,
"wide_signed_mul_div_floor_from_k_pair: q > i128::MAX"
);
assert!(q_u128 <= i128::MAX as u128, "wide_signed_mul_div_floor_from_k_pair: q > i128::MAX");
q_u128 as i128
}
}
Expand All @@ -1560,15 +1533,8 @@ pub struct OverI128Magnitude;

/// ADL delta_K representability check.
/// Returns Ok(v) if the ceil result fits in i128 magnitude, Err otherwise.
pub fn wide_mul_div_ceil_u128_or_over_i128max(
a: u128,
b: u128,
d: u128,
) -> core::result::Result<u128, OverI128Magnitude> {
assert!(
d > 0,
"wide_mul_div_ceil_u128_or_over_i128max: division by zero"
);
pub fn wide_mul_div_ceil_u128_or_over_i128max(a: u128, b: u128, d: u128) -> core::result::Result<u128, OverI128Magnitude> {
assert!(d > 0, "wide_mul_div_ceil_u128_or_over_i128max: division by zero");
let result = mul_div_ceil_u256(U256::from_u128(a), U256::from_u128(b), U256::from_u128(d));
match result.try_into_u128() {
Some(v) if v <= i128::MAX as u128 => Ok(v),
Expand All @@ -1582,7 +1548,7 @@ pub fn saturating_mul_u128_u64(a: u128, b: u64) -> u128 {
return 0;
}
let b128 = b as u128;
a.saturating_mul(b128)
a.checked_mul(b128).unwrap_or(u128::MAX)
}

// ============================================================================
Expand Down Expand Up @@ -1681,7 +1647,7 @@ mod tests {
fn test_u256_mul_overflow() {
let a = U256::new(0, 1); // 2^128
let b = U256::new(0, 1); // 2^128
// Product would be 2^256, which overflows.
// Product would be 2^256, which overflows.
assert_eq!(a.checked_mul(b), None);
}

Expand Down Expand Up @@ -1883,15 +1849,9 @@ mod tests {
#[test]
fn test_ceil_div_positive() {
// ceil(7 / 3) = 3
assert_eq!(
ceil_div_positive_checked(U256::from_u128(7), U256::from_u128(3)),
U256::from_u128(3)
);
assert_eq!(ceil_div_positive_checked(U256::from_u128(7), U256::from_u128(3)), U256::from_u128(3));
// ceil(6 / 3) = 2
assert_eq!(
ceil_div_positive_checked(U256::from_u128(6), U256::from_u128(3)),
U256::from_u128(2)
);
assert_eq!(ceil_div_positive_checked(U256::from_u128(6), U256::from_u128(3)), U256::from_u128(2));
}

// --- saturating_mul_u256_u64 ---
Expand Down Expand Up @@ -2022,14 +1982,8 @@ mod tests {
#[test]
fn test_mul_div_max() {
// MAX * 1 / 1 = MAX
assert_eq!(
mul_div_floor_u256(U256::MAX, U256::ONE, U256::ONE),
U256::MAX
);
assert_eq!(mul_div_floor_u256(U256::MAX, U256::ONE, U256::ONE), U256::MAX);
// 1 * 1 / 1 = 1
assert_eq!(
mul_div_floor_u256(U256::ONE, U256::ONE, U256::ONE),
U256::ONE
);
assert_eq!(mul_div_floor_u256(U256::ONE, U256::ONE, U256::ONE), U256::ONE);
}
}
Loading
Loading