agents/skills/soroban/zero-state-return/SKILL.md
Trigger Always inject into Arithmetic agent (extends existing ZERO_STATE_ECONOMICS) - Purpose Check protocol return-to-zero state, not just initial zero state
npx skillsauth add plamentsv/plamen zero-state-returnInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Trigger: Always inject into Arithmetic agent (extends existing ZERO_STATE_ECONOMICS) Purpose: Check protocol return-to-zero state, not just initial zero state Finding prefix:
[ZS-N]Rules referenced: R5, R10, R13
ZERO_STATE_ECONOMICS checks initial zero state. This skill EXTENDS it to cover:
After normal operations, can the protocol return to:
| State | Trigger | Check |
|-------|---------|-------|
| total_shares == 0 (i128) | All users burned shares / withdrew | Recreates first-depositor conditions? |
| total_deposited == 0 | All funds withdrawn | Residual rewards or time-decay state in Persistent storage? |
| Empty Instance storage keys | All operational state cleared | Can protocol still function on next call? |
| Zero liquidity | All LP positions closed | What happens to accumulated fees / ratio snapshots? |
When LP/share supply returns to zero, check for:
total_shares = 0?total_shares = 0?total_shares = 0?Does re-entering zero state recreate first-depositor attack conditions?
| Scenario | Initial State | Return-to-Zero State | Same Vulnerability? | |----------|---------------|---------------------|---------------------| | First depositor attack | total_shares=0, total_value=0 | total_shares=0, total_value=X (residual) | WORSE if residual > 0 | | Exchange rate manipulation | No shares exist | No shares, but token balance > 0 | YES + amplified | | Donation attack | Clean state | Dirty state (residual, unsolicited dust) | YES + pre-seeded |
Key question: Does first-depositor protection apply only on initial initialize() or also on return-to-zero re-deposits?
Soroban-specific: If protection is if total_shares == 0 { require(amount >= MIN_FIRST_DEPOSIT) }, does it fire on return-to-zero? Is MIN_FIRST_DEPOSIT in Instance storage (accessible) or hardcoded?
Check for admin functions that can force zero state:
emergency_withdraw() — clears ALL tracked state (totals, accumulators, snapshots)?close_vault() — what Persistent/Instance keys persist after calling?migrate() — old contract retains residual token balances?force_deallocate() — creates accounting mismatch between state and token balance?Soroban-specific: Instance storage has no single "close all" operation. Verify each DataKey variant is explicitly cleared during reset, not just main accounting keys.
## Zero-State Return Analysis for [Contract / Vault]
### Can protocol return to zero state?
- [ ] All users can withdraw / burn shares (no locked funds)
- [ ] total_shares (i128) can reach exactly 0
### What persists when total_shares = 0?
- [ ] Accrued rewards / time-decay state: [amount / none]
- [ ] Protocol fees / ratio snapshots: [amount / none / resets]
- [ ] Token balance dust: [yes / no]
- [ ] Pending operations in Persistent storage: [list / none]
- [ ] Sub-contract allocations: [zeroed / residual]
### Re-entry vulnerability?
- [ ] Initial zero state protected: [yes / no / how]
- [ ] Return-to-zero state protected: [yes / no / how]
- [ ] Same protection mechanism: [yes / no]
### Exchange rate at return-to-zero:
- [ ] Formula: [show calculation using i128 values]
- [ ] With residual X: [panic? wrong value?]
- [ ] Can attacker inflate rate before re-entry: [yes / no]
For each state variable used in arithmetic or control flow, check its initial value before any user interaction:
e.storage().instance().get::<_, i128>(&DataKey::X) returns None if never written. unwrap() panics; unwrap_or(0) silently treats as 0.initialize() already set dependent keys?None. Confusion between "key present with value 0" and "key absent" can cause bugs.// Pattern 1: Guard covers initial zero only — what about return-to-zero?
if vault.total_shares == 0i128 {
return Ok(1_000_000i128); // 1:1 rate
}
// QUESTION: What if total_shares returns to 0 but token balance > 0?
// Pattern 2: i128 exchange rate with division by zero
let rate = vault.total_value
.checked_div(vault.total_shares)
.ok_or(Error::DivisionByZero)?;
// QUESTION: What if total_value > 0 and total_shares = 0?
// Pattern 3: First deposit protection
if vault.total_shares == 0i128 {
require!(deposit_amount >= MIN_FIRST_DEPOSIT, Error::DepositTooSmall);
}
// QUESTION: Does this fire for RE-deposits after full exit?
// Pattern 4: Time-decay state
let unlocked = vault.decay_amount * elapsed_ledgers / DECAY_DURATION_LEDGERS;
// QUESTION: Does decay_amount persist when total_shares = 0?
**ID**: [ZS-N]
**Severity**: [typically HIGH if funds extractable]
**Location**: src/{file}.rs:LineN
**Title**: Return-to-zero state allows [attack] due to [residual state in storage]
**Description**:
- Protocol can return to total_shares=0 via [mechanism]
- When this happens, [storage key] retains value of [amount] (i128)
- A new depositor can [exploit path]
**Impact**: [Fund extraction / exchange rate manipulation / unfair distribution]
**PoC Scenario**:
1. Users deposit and earn rewards
2. All users withdraw, total_shares = 0i128
3. Residual state remains: {DataKey::DecayAmount} = X in Instance storage
4. Attacker deposits minimum amount
5. Attacker claims X rewards via inflated share-to-value ratio
This skill does NOT replace ZERO_STATE_ECONOMICS. It EXTENDS it:
| Check | ZERO_STATE_ECONOMICS | ZERO_STATE_RETURN | |-------|---------------------|-------------------| | Initial zero state / first depositor | YES | — | | Return to zero | — | YES | | Residual assets (time-decay, fees, dust) | — | YES | | Re-entry vulnerability | — | YES | | i128 division-by-zero guards | YES (partial) | YES (both initial and return paths) | | Missing storage key defaults | — | YES |
When applying ZERO_STATE_ECONOMICS, ALSO apply ZERO_STATE_RETURN.
development
Prepare Solidity projects for a security audit — test coverage, test quality, NatSpec docs, code hygiene, dependency health, best-practice enforcement, deployment readiness, and project documentation checks. Generates a scored Audit Readiness Report and optionally runs static analysis. Trigger on: "prepare for audit", "audit readiness", "pre-audit check", "audit prep", "NatSpec check", or any request to review a Solidity codebase before a security review.
development
Launch the Plamen deterministic Web3 security audit pipeline
development
Run the Plamen smart-contract audit wizard in Codex
testing
Launch the Plamen deterministic L1 infrastructure audit pipeline