16 - Testing & Akzeptanzkriterien¶
Teststruktur, Szenarien, Failure Modes, Akzeptanzkriterien
Teststruktur¶
contracts/test/
├── commit_20456.js # Settlement Commit Tests
├── claim_20456.js # Claim Verification Tests
├── check_charity_state.js # Charity Vault Audit
├── test_G_wallet_swap.js # Lost Wallet Recovery
├── WORK/
│ ├── test_B_bonus_tickets_resilient.js # Bonus-Tip Berechnung
│ ├── test_C_mit_Affiliate.js # Affiliate Flow
│ ├── test_E_check_claim_test.js # Claim Flow
│ ├── test_E3_affiliate_claim.js # Affiliate Claims
│ ├── test_F_check_emergency_claim_block.js # Emergency Mode
│ └── ...
Test-Framework¶
| Tool | Version | Zweck |
|---|---|---|
| Hardhat | Latest | Test-Runner + Network |
| ethers.js | v6 | Contract Interaction |
| Chai | Latest | Assertions |
Test-Szenarien¶
Test A: Basis Ticket-Kauf¶
Setup: - 3 Spieler (Alice, Bob, Charlie) - Pool 1 (0.50 USDT pro Tip) - Je 5 Tips pro Spieler
Ablauf:
1. USDT Mint + Approve an ReserveVault
2. BuyPermit signieren (EIP-712)
3. Operator submitted buyTicketFor()
4. Ticket-Verifizierung
Erwarteter State:
tickets[0].player == Alice
tickets[0].tipCount == 5
tickets[0].bonusTipCount == 3 (5 Tips → 3 Bonus)
tickets[0].rngNumbers.length == 6
potAcc[1] > 0
feeAcc[dayId] > 0
rolloverAcc[1] > 0
jackpotReserveAcc[1] > 0
ReserveVault.balance == 3 × (5 × 0.50 + gasAmount)
Test B: Merkle Settlement & Claim¶
Setup: - Tickets von Test A - Off-chain Gewinn-Berechnung simuliert - Merkle Tree mit 2 Gewinnern (Alice: 1.50 USDT, Bob: 0.75 USDT)
Ablauf:
1. Merkle Tree bauen
2. commitMerkleSettlement() aufrufen
3. Alice claimed direkt via PrizeVault.claimMerkle()
4. Bob claimed via ClaimRouter.claimAllFor()
Erwarteter State:
daySettled[dayId] == true
merkleRootByDay[dayId] != 0
claimed[dayId][Alice] == true
claimed[dayId][Bob] == true
Alice.usdtBalance += 1.50 USDT
Bob.usdtBalance += 0.75 USDT - gasCost
potAcc[1] < vorher (Payout abgezogen)
Negativtests: - Double-Claim → revert "Already claimed" - Falscher Proof → revert "Invalid proof" - Falscher Amount → revert "Invalid proof" - Claim für anderen Tag → revert "Invalid proof"
Test C: No-Winner-Day¶
Setup: - Tickets von Tag X, aber keine Gewinner
Ablauf:
1. Dummy Merkle Leaf bauen (address(0), amount 0)
2. commitMerkleSettlement() mit Zero-Payouts
3. Prüfe dass nächster Tag unblocked ist
Erwarteter State:
daySettled[dayId] == true
merkleRootByDay[dayId] != 0 (Dummy Root)
potAcc[pool] == unchanged (kein Payout)
rolloverAcc[pool] == recalculated
// Nächster Tag funktioniert:
settlement.daySettled(dayId) == true
→ buyTicketFor() für dayId+1 funktioniert
Test D: Emergency Mode¶
Setup: - Aktive Claims ausstehend - Funds im ReserveVault
Ablauf: 1. SAFE aktiviert Emergency Mode 2. Versuche payPrize → REVERT 3. Versuche payFee → REVERT 4. Versuche commitMerkleSettlement → REVERT (weil payCharity reveriert) 5. SAFE führt ownerWithdraw aus → ERFOLG 6. SAFE deaktiviert Emergency Mode 7. Claims funktionieren wieder
Erwarteter State:
// Emergency an:
reserveVault.emergencyMode() == true
payPrize() → revert
payFee() → revert
ownerWithdraw() → success
// Emergency aus:
reserveVault.emergencyMode() == false
payPrize() → success
Test E: Lost Wallet Recovery¶
Setup: - Alice hat Gewinn auf Day X - Alice verliert Zugang zu ihrer Wallet - Neue Wallet: Alice_new
Ablauf:
1. Operator ruft adminReassignWallet(Alice, Alice_new, caseId) auf
2. Alice_new claimed mit Original-Proof (wallet = Alice)
3. Payout geht an Alice_new
Erwarteter State:
Negativtests: - Zweites Reassignment → revert "Already reassigned" - Claim mit paidTo = Alice → revert "Invalid paidTo" (muss Alice_new sein)
Test F: Affiliate Flow¶
Setup: - Alice wird von Referrer eingeladen - Alice kauft Ticket mit affiliate = Referrer - Settlement mit Affiliate-Provisionen
Ablauf:
1. Alice kauft Ticket → affiliateOf[Alice] = Referrer
2. Settlement mit affiliateRoot + affiliateTotal
3. Referrer claimed Provision via AffiliateVault
Erwarteter State:
affiliateOf[Alice] == Referrer
affiliateMerkleRootByDay[dayId] != 0
claimed[dayId][Referrer] == true (in AffiliateVault)
Referrer.usdtBalance += commission
Failure Mode Matrix¶
| ID | Failure | Impact | Detection | Recovery | Prevention |
|---|---|---|---|---|---|
| F1 | Operator Key verloren | Hoch | Manuelle Erkennung | SAFE setzt neuen Operator | Key-Management, HSM |
| F2 | VRF Timeout | Mittel | Monitoring | prevrandao Fallback, manueller Seed-Push | Chainlink SLA, LINK Balance |
| F3 | Settlement Backend down | Hoch | Monitoring | forceMarkDaySettled, Backend Restart | Redundanz, Auto-Restart |
| F4 | USDT Transfer fehlschlägt | Hoch | TX Revert | Debug Transfer, Approval prüfen | Pre-Check im Backend |
| F5 | Merkle Root falsch | Kritisch | Spieler können nicht claimen | Nicht rückgängig machbar! | Backend-Validierung, Tests |
| F6 | ReserveVault underfunded | Kritisch | Balance-Monitoring | SAFE deposited Funds | Invariant-Checks |
| F7 | Nonce Collision | Niedrig | TX Revert | Neues Nonce generieren | 16 Bytes Random |
| F8 | Chainlink Sub leer | Mittel | Chainlink Dashboard | LINK nachfüllen | Automatisches Monitoring |
| F9 | Gas Spike | Mittel | Hohe Kosten | Warten, Gas Limit anpassen | L2 (niedrige Gas-Kosten) |
| F10 | No-Winner Deadlock | Mittel | Settlement Guard blockiert | Dummy Merkle / forceSettle | Automatischer Dummy-Merkle |
Akzeptanzkriterien¶
Funktionale Kriterien¶
| # | Kriterium | Test |
|---|---|---|
| AK-1 | Ticket-Kauf via EIP-712 funktioniert | Test A |
| AK-2 | Settlement committiert atomare Ergebnisse | Test B |
| AK-3 | Merkle Claims funktionieren (direkt + batch) | Test B |
| AK-4 | Double-Claims werden verhindert | Test B (Negativtest) |
| AK-5 | No-Winner-Days blocken nicht den nächsten Tag | Test C |
| AK-6 | Emergency Mode blockiert alle Payouts | Test D |
| AK-7 | Lost Wallet Recovery funktioniert einmalig | Test E |
| AK-8 | Affiliate-Provisionen werden korrekt verteilt | Test F |
Sicherheitskriterien¶
| # | Kriterium | Verifikation |
|---|---|---|
| SK-1 | Nur ReserveVault hält USDT | Balance-Check aller Contracts |
| SK-2 | Fee Splits sind immutable | Versuch, Constants zu ändern |
| SK-3 | Merkle Roots sind immutable | Versuch, Root zu überschreiben |
| SK-4 | Nonces können nicht wiederverwendet werden | Replay-Versuch |
| SK-5 | Reentrancy ist nicht möglich | Reentrancy-Test |
| SK-6 | Operator kann keine Funds abziehen | Access Control Prüfung |
| SK-7 | Settlement ist sequentiell | Versuch, Tags zu überspringen |
Ökonomische Kriterien¶
| # | Kriterium | Verifikation |
|---|---|---|
| EK-1 | Fee Split: 67.5% + 27.5% + 5% = 100% | Mathematischer Beweis |
| EK-2 | Rollover-Formel erhält Gesamtmasse | Conservation-of-Mass Beweis |
| EK-3 | Jackpot-Reserve wird korrekt akkumuliert/ausgezahlt | Multi-Day Test |
| EK-4 | ReserveVault Balance >= alle Verpflichtungen | Invariant-Check nach jedem Settlement |
Test-Ausführung¶
# Alle Tests
cd contracts
npx hardhat test
# Einzelner Test
npx hardhat test test/WORK/test_B_bonus_tickets_resilient.js
# Mit Gas-Report
npx hardhat test --gas-reporter
# Auf Arbitrum Sepolia (Fork)
npx hardhat test --network hardhat --fork https://sepolia-rollup.arbitrum.io/rpc
Audit-Hinweise¶
Kritischster Test
Test B (Merkle Settlement & Claim) ist der wichtigste Test. Er deckt den gesamten Flow von Ticket-Kauf bis Gewinn-Auszahlung ab. Jeder Bug hier ist ein potenzieller Exploit.
Failure Mode F5
Ein falscher Merkle Root (F5) ist der einzige Fehler, der nicht rückgängig gemacht werden kann. Alle anderen Failure Modes haben Recovery-Pfade. Die Backend-Validierung vor dem Settlement-Commit ist daher audit-kritisch.