HIGH - Causes resource exhaustion and contract DoS
Detects loops that verify a caller-controlled, unbounded number of proofs in a single transaction. Proof verification is expensive; unbounded batches can exceed Soroban's CPU instruction budget, making the contract unusable.
// ❌ BAD: Unbounded batch verification
pub fn batch_claim(env: Env, proofs: Vec<Proof>, inputs: Vec<Vec<u64>>) {
// No length check - caller can send 1000 proofs!
for (i, proof) in proofs.iter().enumerate() {
verify_zk_proof(&env, &proof, &inputs[i]);
process_claim(&env, &inputs[i]);
}
// Exceeds CPU budget, reverts, wastes gas
}Impact: Even legitimate users cannot use the function if batch size isn't capped.
// ✅ GOOD: Bounded with maximum batch size
const MAX_BATCH_SIZE: usize = 10;
pub fn batch_claim(env: Env, proofs: Vec<Proof>, inputs: Vec<Vec<u64>>) {
if proofs.len() > MAX_BATCH_SIZE {
panic!("Batch size exceeds maximum of {}", MAX_BATCH_SIZE);
}
for (i, proof) in proofs.iter().enumerate() {
verify_zk_proof(&env, &proof, &inputs[i]);
process_claim(&env, &inputs[i]);
}
}- Availability: Contract becomes unusable if every call exhausts resources
- Cost: Users waste fees on reverting transactions
- DoS: Attacker can intentionally trigger resource exhaustion
- Degraded UX: Legitimate batch operations fail unpredictably
- Find loops over collections (
for,while,.iter()) - Check if loop body calls proof-verification functions
- Analyze collection source:
- From function parameter → potentially unbounded
- Fixed-size array → bounded
- With
.take(N)→ bounded
- Check for explicit length validation
- Flag unbounded verification loops
Based on Soroban resource budgets:
- Conservative: 5-10 proofs per transaction
- Moderate: 10-20 proofs (measure actual CPU usage)
- Document: Clearly state max batch size in comments
- S021: Unbounded loops (general pattern)
- G004: Resource consumption checks
- #1192, #1194: ZK infrastructure
- #1223: Test fixture
See contracts/fixtures/finding-codes/z009_unbounded_verify_loop.rs