mirror of
https://github.com/luxfi/proofs.git
synced 2026-10-30 15:21:05 +00:00
The lean/ library (112 files) had ~110 type-check errors across 52 files (the
prior "green" status was a grep artifact). It now verifies at 0 errors under
the project's strict setting: `lake env lean -D autoImplicit=false`, all 112
files.
Fixed:
- Quasar/Consensus: replaced `by native_decide` with `by decide` (kernel-checked
rather than compiler-trusted).
- Vault/KeyIsolation, Bridge/{YieldVault,OmnichainRouter,FillIntegrity},
Storage/{ZapDB,Replicate}: PRF/string-encoding lemmas, division-bound vault
accounting, renamed/removed deprecated List lemmas, noncomputable.
- Crypto (Pulsar/Magnetar/Corona/CGGMP21/MLKEM/FROST/FHE/P3Q): missing local
dependency oleans compiled in dependency order; theorem→noncomputable def for
Type-valued re-exports; deriving Inhabited for opaque artifact types; dead
Mathlib imports removed.
- DeFi (AMM/staking/fees/flashloan/HFT/router) + Protocol + GPU + Warp + Trust +
Compute + Network + Build: Nat-division/AMM-invariant proofs via
Nat.div_le_of_le_mul / Nat.div_mul_le_self; omega in-scope invariants;
record-projection opacity; dead tactics; stale Nat.Basic import → Nat.Defs;
BFT quorum theorems tightened to n=3f+1.
Integrity: no sorry/admit/native_decide remaining (verified); no
proof-by-assumption axiom cheats. Statement changes only tightened false draft
theorems to true invariants. Remaining axioms are trusted-base only (opaque
crypto groups/pairings, RLWE/MLWE/MSIS hardness, FIPS 203/204/205 correctness,
VSS/Paillier, protocol modeling).
Co-authored-by: Hanzo Dev <[email protected]>
207 lines
9.2 KiB
Lean4
207 lines
9.2 KiB
Lean4
/-
|
||
YieldVault Formal Properties
|
||
|
||
ERC-4626 vault with virtual share offset to prevent first-depositor
|
||
donation attacks. Strategies are approved per-operation (no infinite
|
||
approvals). Yield reports are monotonic.
|
||
|
||
Maps to:
|
||
- contracts/bridge/YieldVault.sol: deposit/withdraw/yield logic
|
||
- contracts/bridge/strategies/: yield strategy adapters
|
||
|
||
Properties:
|
||
- Virtual offset protects first depositor
|
||
- Share accounting conservation (pricePerShare invariant)
|
||
- No infinite approvals (per-operation approve/reset pattern)
|
||
- Yield monotonicity (totalUnderlyingAssets only increases)
|
||
-/
|
||
|
||
import Mathlib.Data.Nat.Defs
|
||
import Mathlib.Tactic
|
||
|
||
namespace Bridge.YieldVault
|
||
|
||
-- ═══════════════════════════════════════════════════════════════
|
||
-- TYPES
|
||
-- ═══════════════════════════════════════════════════════════════
|
||
|
||
/-- Vault state tracking shares and underlying assets -/
|
||
structure VaultState where
|
||
totalShares : Nat -- total supply of vault shares
|
||
totalAssets : Nat -- total underlying assets
|
||
virtualShares : Nat -- constant offset (default: 1e8)
|
||
virtualAssets : Nat -- constant offset (default: 1)
|
||
deriving DecidableEq, Repr
|
||
|
||
/-- Strategy approval state -/
|
||
inductive ApprovalState where
|
||
| none : ApprovalState -- no active approval
|
||
| approved : Nat → ApprovalState -- approved for specific amount
|
||
deriving DecidableEq, Repr
|
||
|
||
/-- Yield report from a strategy -/
|
||
structure YieldReport where
|
||
strategyAddress : Nat
|
||
gain : Nat
|
||
loss : Nat -- capped at 0 in practice
|
||
|
||
-- ═══════════════════════════════════════════════════════════════
|
||
-- CORE FUNCTIONS
|
||
-- ═══════════════════════════════════════════════════════════════
|
||
|
||
/-- Effective total shares including virtual offset -/
|
||
def effectiveShares (v : VaultState) : Nat :=
|
||
v.totalShares + v.virtualShares
|
||
|
||
/-- Effective total assets including virtual offset -/
|
||
def effectiveAssets (v : VaultState) : Nat :=
|
||
v.totalAssets + v.virtualAssets
|
||
|
||
/-- Convert assets to shares: shares = assets * effectiveShares / effectiveAssets -/
|
||
def assetsToShares (v : VaultState) (assets : Nat) : Nat :=
|
||
assets * effectiveShares v / effectiveAssets v
|
||
|
||
/-- Convert shares to assets: assets = shares * effectiveAssets / effectiveShares -/
|
||
def sharesToAssets (v : VaultState) (shares : Nat) : Nat :=
|
||
shares * effectiveAssets v / effectiveShares v
|
||
|
||
/-- Deposit: mint shares proportional to deposit amount -/
|
||
def deposit (v : VaultState) (assets : Nat) : VaultState × Nat :=
|
||
let shares := assetsToShares v assets
|
||
({ v with totalShares := v.totalShares + shares,
|
||
totalAssets := v.totalAssets + assets }, shares)
|
||
|
||
/-- Withdraw: burn shares, return proportional assets -/
|
||
def withdraw (v : VaultState) (shares : Nat) : Option (VaultState × Nat) :=
|
||
if shares ≤ v.totalShares then
|
||
let assets := sharesToAssets v shares
|
||
some ({ v with totalShares := v.totalShares - shares,
|
||
totalAssets := v.totalAssets - assets }, assets)
|
||
else none
|
||
|
||
-- ═══════════════════════════════════════════════════════════════
|
||
-- THEOREMS
|
||
-- ═══════════════════════════════════════════════════════════════
|
||
|
||
/-- VIRTUAL OFFSET PROTECTION: With VIRTUAL_SHARES = 1e8, a first-depositor
|
||
donation attack yields NO profit — the attacker's redeemable assets are
|
||
bounded by what they contributed (the 1 wei deposit plus the `donation`).
|
||
|
||
Attack: deposit 1 wei, donate D tokens, redeem.
|
||
Without offset (totalShares = 0): the first depositor controls
|
||
price-per-share and can grief later depositors.
|
||
With the virtual offset, redeem(shares1) on the post-donation state
|
||
returns at most 1 + D — the attacker cannot extract more than they put in.
|
||
|
||
STATEMENT CHANGED: the original bound `(1 + donation)/(1 + virtualShares) + 1`
|
||
was FALSE — e.g. donation = 2·1e8 gives LHS = 100000001 but that RHS = 2.
|
||
The redeem math `shares1 * effAssets / effShares` with shares1 = 1e8 yields
|
||
`1e8·(donation+2)/2e8 ≈ donation/2`, which exceeds `donation/(1+1e8)`.
|
||
The true, non-trivial invariant is donation-attack-resistance:
|
||
redeemable ≤ contributed = 1 + donation (no free money for the attacker). -/
|
||
theorem virtual_offset_protection (v : VaultState)
|
||
(h_empty : v.totalShares = 0)
|
||
(h_no_assets : v.totalAssets = 0)
|
||
(h_virtual : v.virtualShares = 100000000) -- 1e8
|
||
(h_vassets : v.virtualAssets = 1)
|
||
(donation : Nat) :
|
||
-- After depositing 1 wei and donating `donation` to vault:
|
||
let (v1, shares1) := deposit v 1
|
||
let v2 := { v1 with totalAssets := v1.totalAssets + donation }
|
||
-- Attacker's redeemable assets bounded by what they contributed
|
||
sharesToAssets v2 shares1 ≤ 1 + donation := by
|
||
simp only [deposit, assetsToShares, sharesToAssets, effectiveShares, effectiveAssets,
|
||
h_empty, h_no_assets, h_virtual, h_vassets]
|
||
norm_num
|
||
apply Nat.div_le_of_le_mul
|
||
ring_nf
|
||
omega
|
||
|
||
/-- SHARE ACCOUNTING CONSERVATION: The price per share relationship holds.
|
||
totalSupply * pricePerShare ≈ totalAssets (within integer rounding).
|
||
Specifically: sharesToAssets(totalShares) ≤ totalAssets. -/
|
||
theorem share_accounting_conservation (v : VaultState)
|
||
(_h_pos : effectiveShares v > 0) :
|
||
sharesToAssets v v.totalShares ≤ v.totalAssets + v.virtualAssets := by
|
||
simp only [sharesToAssets, effectiveAssets, effectiveShares]
|
||
apply Nat.div_le_of_le_mul
|
||
exact Nat.mul_le_mul_right _ (by omega)
|
||
|
||
/-- Deposit then withdraw returns at most what was deposited (no free money) -/
|
||
theorem no_free_money (v : VaultState)
|
||
(assets : Nat)
|
||
(_h_pos : effectiveShares v > 0)
|
||
(_h_apos : effectiveAssets v > 0) :
|
||
let (v', shares) := deposit v assets
|
||
sharesToAssets v' shares ≤ assets := by
|
||
set S := v.totalShares
|
||
set A := v.totalAssets
|
||
set vS := v.virtualShares
|
||
set vA := v.virtualAssets
|
||
simp only [deposit, assetsToShares, sharesToAssets, effectiveShares, effectiveAssets]
|
||
-- shares = assets * (S+vS)/(A+vA); goal: shares*(A+assets+vA)/(S+shares+vS) ≤ assets
|
||
set sh := assets * (S + vS) / (A + vA) with hsh
|
||
-- key: division rounds down, so sh * (A+vA) ≤ assets * (S+vS)
|
||
have hkey : sh * (A + vA) ≤ assets * (S + vS) := by
|
||
rw [hsh]; exact Nat.div_mul_le_self _ _
|
||
apply Nat.div_le_of_le_mul
|
||
nlinarith [hkey, Nat.mul_comm sh assets, Nat.mul_comm assets sh]
|
||
|
||
/-- NO INFINITE APPROVALS: The approve-deposit-reset pattern ensures
|
||
strategies never hold persistent approvals.
|
||
|
||
Pattern: approve(strategy, amount) → strategy.deposit(amount) → approve(strategy, 0)
|
||
Each operation is: none → approved(amount) → none. -/
|
||
def approveStrategy (_ : Nat) (amount : Nat) : ApprovalState :=
|
||
ApprovalState.approved amount
|
||
|
||
def resetApproval (_ : Nat) : ApprovalState :=
|
||
ApprovalState.none
|
||
|
||
def executeStrategyDeposit (approval : ApprovalState) (amount : Nat) :
|
||
Option ApprovalState :=
|
||
match approval with
|
||
| ApprovalState.approved limit =>
|
||
if amount ≤ limit then some (ApprovalState.none) else none
|
||
| ApprovalState.none => none
|
||
|
||
theorem no_infinite_approvals (strategy : Nat) (amount : Nat) :
|
||
let approval := approveStrategy strategy amount
|
||
∀ result, executeStrategyDeposit approval amount = some result →
|
||
result = ApprovalState.none := by
|
||
simp [approveStrategy, executeStrategyDeposit]
|
||
|
||
/-- Approval resets to none after operation -/
|
||
theorem approval_lifecycle (strategy : Nat) (_amount : Nat) :
|
||
resetApproval strategy = ApprovalState.none := by
|
||
simp [resetApproval]
|
||
|
||
/-- YIELD MONOTONICITY: processYieldReport only increases totalAssets
|
||
when gain > loss. In practice, loss is always 0 for healthy strategies. -/
|
||
def processYieldReport (v : VaultState) (r : YieldReport) : VaultState :=
|
||
{ v with totalAssets := v.totalAssets + r.gain - r.loss }
|
||
|
||
theorem yield_monotonicity (v : VaultState) (r : YieldReport)
|
||
(h : r.gain ≥ r.loss) :
|
||
(processYieldReport v r).totalAssets ≥ v.totalAssets := by
|
||
simp [processYieldReport]; omega
|
||
|
||
/-- Healthy strategy (no loss) strictly increases assets when gain > 0 -/
|
||
theorem healthy_yield_increases (v : VaultState) (r : YieldReport)
|
||
(h_no_loss : r.loss = 0) (h_gain : r.gain > 0) :
|
||
(processYieldReport v r).totalAssets > v.totalAssets := by
|
||
simp [processYieldReport, h_no_loss]; omega
|
||
|
||
/-- Virtual offset is never zero (prevents division by zero) -/
|
||
theorem virtual_shares_positive (v : VaultState)
|
||
(h : v.virtualShares = 100000000) :
|
||
effectiveShares v > 0 := by
|
||
simp [effectiveShares, h]
|
||
|
||
theorem virtual_assets_positive (v : VaultState)
|
||
(h : v.virtualAssets = 1) :
|
||
effectiveAssets v > 0 := by
|
||
simp [effectiveAssets, h]
|
||
|
||
end Bridge.YieldVault
|