← covergoal

CoverGoal · attack console · devnet

Try to cheat it.

Three ways a cranker might try to steal a settlement — each one built as a real settle_policy transaction and simulated against the live devnet program. Nothing is signed or sent; the program runs and hands back its own logs. Every attack dies on a guard.

replay it yourself

$ cd keeper && npx tsx scripts/attack-suite.ts

program
Go7jpi…XMhoNv
oracle
6pW64g…5wyP2J
on-chain time
2026-07-19T13:25:06.000Z
verdict
3 / 3 rejected

Both armed final policies (fixture 18257739) are Active and pre-finality (settlement opens 23:30 UTC). The finality gate is therefore the outermost live wall: the program refuses to read a proof before full-time. The fixture-binding and Merkle-reconciliation walls are inner guards that only run once settlement opens; they are documented from source in each attack. TxLINE serves no finished-fixture proof pre-kickoff, so proofs here are inert placeholders — sound, because every attack is aborted before the proof body is read.

attack 1

Settle the wrong match

Rejected by program

What a cheater would gain. Bring a genuine Merkle proof from a DIFFERENT finished match (Vietnam v Myanmar, 18143850) and cash it against the final's policy — claim the pot off an unrelated result.

simulateTransaction · logs
Program ComputeBudget111111111111111111111111111111 invoke [1]
Program ComputeBudget111111111111111111111111111111 success
Program Go7jpiE1kHksssFBNsq1vWroh4ViuqS1F2ctpmXMhoNv invoke [1]
Program log: Instruction: SettlePolicy
Program log: AnchorError thrown in programs/covergoal/src/instructions/policy.rs:196. Error Code: FinalityWindowOpen. Error Number: 6004. Error Message: Finality window has not elapsed yet.
Program Go7jpiE1kHksssFBNsq1vWroh4ViuqS1F2ctpmXMhoNv consumed 20831 of 1399850 compute units
Program Go7jpiE1kHksssFBNsq1vWroh4ViuqS1F2ctpmXMhoNv failed: custom program error: 0x1774
custom error 6004 · FinalityWindowOpenrs:196. Error Code: FinalityWindowOpen. Error Number: 6004. Error Message: Finality window has not elapsed yet.

guard · A real settle_policy transaction whose proof carries fixture_id 18143850, submitted against the final's policy (fixture 18257739). The program never trusts the fixture the proof claims — it compares proof.fixture_summary.fixture_id to the Policy's OWN stored fixture_id (policy.rs line 200-203), a value no cranker can set. Right now, pre-kickoff, the foreign proof does not even get that far: the finality gate (line 196) refuses to read ANY proof until the match is final, so this is the line the runtime returns. Two independent walls, one foreign proof, zero ways through.

attack 2

Crank it before the whistle

Rejected by program

What a cheater would gain. Crank settlement now — mid-match, or on a rumoured 'ghost goal' the feed hasn't confirmed — and drain the 300 USDC pot before the result is real.

simulateTransaction · logs
Program ComputeBudget111111111111111111111111111111 invoke [1]
Program ComputeBudget111111111111111111111111111111 success
Program Go7jpiE1kHksssFBNsq1vWroh4ViuqS1F2ctpmXMhoNv invoke [1]
Program log: Instruction: SettlePolicy
Program log: AnchorError thrown in programs/covergoal/src/instructions/policy.rs:196. Error Code: FinalityWindowOpen. Error Number: 6004. Error Message: Finality window has not elapsed yet.
Program Go7jpiE1kHksssFBNsq1vWroh4ViuqS1F2ctpmXMhoNv consumed 20831 of 1399850 compute units
Program Go7jpiE1kHksssFBNsq1vWroh4ViuqS1F2ctpmXMhoNv failed: custom program error: 0x1774
custom error 6004 · FinalityWindowOpenrs:196. Error Code: FinalityWindowOpen. Error Number: 6004. Error Message: Finality window has not elapsed yet.

guard · A real settle_policy transaction against the armed policy 'Free round if Spain score tonight', submitted before the finality deadline. The proof is well-formed and points at the correct fixture; the only thing wrong is the clock. settle_policy's first act is require!(clock.unix_timestamp >= policy.finality_deadline) — the finality window (match end + 30 min for VAR / late corrections) has not elapsed, so the program aborts before touching the vault. This is the wall standing in front of both armed policies at this very moment.

attack 3

Forge the oracle's Merkle root

Rejected by program

What a cheater would gain. Point settlement at your OWN account as the scores-root and write whatever Merkle root makes your doctored proof verify — manufacture any outcome you like.

simulateTransaction · logs
Program ComputeBudget111111111111111111111111111111 invoke [1]
Program ComputeBudget111111111111111111111111111111 success
Program Go7jpiE1kHksssFBNsq1vWroh4ViuqS1F2ctpmXMhoNv invoke [1]
Program log: Instruction: SettlePolicy
Program log: AnchorError caused by account: daily_scores_merkle_roots. Error Code: ProofInvalid. Error Number: 6006. Error Message: Stat proof did not satisfy the txoracle validation.
Program log: Left:
Program log: 11111111111111111111111111111111
Program log: Right:
Program log: 6pW64gN1s2uqjHkn1unFeEjAwJkPGHoppGvS715wyP2J
Program Go7jpiE1kHksssFBNsq1vWroh4ViuqS1F2ctpmXMhoNv consumed 20751 of 1399850 compute units
Program Go7jpiE1kHksssFBNsq1vWroh4ViuqS1F2ctpmXMhoNv failed: custom program error: 0x1776
custom error 6006 · ProofInvalidError Code: ProofInvalid. Error Number: 6006. Error Message: Stat proof did not satisfy the txoracle validation.

guard · A real settle_policy transaction carrying a tampered proof (one byte of a Merkle leaf flipped) AND an attacker-controlled account as daily_scores_merkle_roots instead of TxODDS's real root PDA. A forged proof is only useful if you also control the root it reconciles to — so this attack tries to swap the root account. The program pins that account to the oracle: #[account(owner = txoracle::ID @ ProofInvalid)]. Not owned by txoracle → rejected, before the proof is read. Two further walls back this up and cannot be reached from here: inside txoracle.validate_stat a byte-flipped proof fails Merkle reconciliation (InvalidSubTreeProof / InvalidMainTreeProof); and the SettleProof struct has NO threshold, op or comparison field at all — settle_policy derives the predicate from Policy.stat_spec on-chain (policy.rs line 206-225), so a cranker cannot weaken 'Spain goals > 0' into 'Spain goals > -1' no matter what they submit.