LLMSkillHub
mirroredCopied from PlamenTSV/plamen under MIT. PlamenTSV has not claimed this page, and is not affiliated with LLM SkillHub.

plamentsv / zero-state-return

Trigger Pattern Vault/pool/first-depositor pattern detected - Inject Into Depth-edge-case

0
SKILL.md
11.3 KB

ZEROSTATERETURN Skill

Trigger Pattern: Vault/pool/first-depositor pattern detected
Inject Into: Depth-edge-case
Purpose: Analyze zero-state transitions in Aptos Move protocols -- initial zero state, return to zero after operations, residual assets, and re-entry vulnerabilities

Overview

This skill covers BOTH initial zero state AND return-to-zero-state analysis:

  • Protocol initialization and first deposit conditions
  • Protocol returning to zero after normal operations
  • Residual assets when supply returns to zero
  • Re-entry vulnerabilities after full exit

1. Identify Zero-State Transitions

Find all vault/pool/staking mechanisms and their zero-state boundaries:

StateResource / VariableZero ConditionTriggerCode Location
Total shares{resource.total_supply}== 0All users withdrew/burned{module:line}
Total assets{resource.total_assets}== 0No funds deposited{module:line}
Pool liquidity{resource.reserves}Both reserves == 0All LP withdrawn{module:line}
Staking pool{resource.total_staked}== 0All unstaked{module:line}

For each state: what is the protocol behavior when this condition is true?

2. First Depositor Analysis

Can the first depositor manipulate share price?

2a. Share Minting Formula at Zero State

ProtocolFormulaWhen totalShares == 0First Deposit Behavior
{name}shares = amount * totalShares / totalAssets{special case?}{describe}

Classic first depositor attack on Aptos:

  1. First depositor deposits minimal amount (e.g., 1 unit)
  2. Attacker directly deposits tokens to the protocol's FungibleStore (unsolicited -- bypasses accounting)
  3. Exchange rate inflates: totalAssets increases but totalShares stays at 1
  4. Next depositor receives 0 shares due to rounding (their deposit amount < inflated share price)
  5. First depositor withdraws, capturing the second depositor's funds

Checks:

  • [ ] Is there a minimum first deposit requirement?
  • [ ] Does the protocol use virtual shares/assets (e.g., add 1 to both numerator and denominator)?
  • [ ] Is there a dead shares mechanism (burn initial shares to zero address)?
  • [ ] Can unsolicited deposits to the protocol's store inflate totalAssets?
  • [ ] Does the protocol use internal accounting (resistant) or direct balance queries (vulnerable)?

2b. First Deposit Protection Mechanisms

ProtectionPresent?ImplementationBypass Possible?
Minimum first depositYES/NO{code ref}{analysis}
Virtual shares/assets offsetYES/NO{code ref}{analysis}
Dead shares (initial mint to zero)YES/NO{code ref}{analysis}
Internal accounting (not balance-based)YES/NO{code ref}{analysis}
Decimal offset in share calculationYES/NO{code ref}{analysis}

3. Return to Zero Analysis

After normal operations, can the protocol return to zero state?

3a. Return-to-Zero Scenarios

ScenarioTriggerResidual State AfterRe-entry Safe?
All shares redeemedLast user withdraws{what remains?}YES/NO
Emergency withdrawAdmin drains{what remains?}YES/NO
All stakers unstakeLast unstake{what remains?}YES/NO
Pool fully drainedAll LP removed{what remains?}YES/NO

3b. Can Total Shares Reach Exactly Zero?

Trace the withdrawal/burn path:

  • Can the last user withdraw ALL their shares? (no minimum balance lock?)
  • Does the protocol enforce a minimum share amount that prevents reaching zero?
  • If dead shares exist, totalShares never reaches 0 -- is this protection consistent?

4. Residual Asset Check

When supply returns to zero, check for stranded value:

4a. Accrued Rewards

Reward SourcePersists When totalShares = 0?Claimable By Next Depositor?Amount Bounded?
{reward_source}YES/NOYES/NO{max amount or UNBOUNDED}

If rewards persist AND next depositor can claim -> FINDING (severity based on amount).

4b. Unclaimed Fees

Fee TypePersists When totalShares = 0?Captured By Next Depositor?Reconciliation Mechanism?
{fee_type}YES/NOYES/NO{mechanism or NONE}

4c. Dust Balances

  • Can dust (sub-unit amounts) remain in FungibleStore after all withdrawals?
  • Does dust affect exchange rate calculations on re-entry? (e.g., totalAssets = 1 wei, totalShares = 0)
  • Does the protocol handle totalAssets > 0 AND totalShares == 0 explicitly?

4d. Pending Operations

  • Are there pending withdrawals/claims that persist after zero state?
  • What happens to in-flight multi-step operations when supply hits zero?
  • Are there resources or objects that reference the pool/vault state that become orphaned?

5. Re-Entry Vulnerability Analysis

Does re-entering zero state recreate first-depositor attack conditions?

ScenarioInitial StateReturn-to-Zero StateSame Vulnerability?
First depositor attacktotalSupply=0, totalAssets=0totalSupply=0, totalAssets=X (residual)WORSE if residual > 0
Exchange rate manipulationNo shares existNo shares, but balance existsYES + amplified
Donation attackClean stateDirty stateYES + pre-seeded

Key question: Is the first-deposit protection (from Section 2b) applied ONLY on initial deployment, or does it also trigger when totalShares returns to 0?

Trace the share minting code:

// Pattern: Protection covers initial AND return-to-zero
if (total_shares == 0) {
    // First deposit logic with protection
}

// vs Pattern: Protection only on first-ever deposit
if (!initialized) {
    // Protection here
} else if (total_shares == 0) {
    // NO protection -- vulnerable on return-to-zero
}

5b. Default/Uninitialized State Values

For each state field used in arithmetic or control flow, check its initial value before any user interaction:

  • Default zero: Move initializes struct fields to their declared defaults (typically 0 for integers, @0x0 for addresses). If a function uses last_timestamp, start_time, or last_update in subtraction or division BEFORE it has ever been set, the result may be unexpected (e.g., timestamp::now_seconds() - 0 = enormous elapsed time, or division by a value derived from 0).
  • First-call path: Trace the FIRST invocation of each state-modifying function. Does it assume a prior call already initialized dependent fields?
  • Check: For each field read in a function, is there a code path where that field still holds its default value (0, @0x0, false)? If yes, does the function behave correctly with that default?

6. Empty Pool Edge Cases

6a. Division by Zero

ExpressionWhen totalShares = 0BehaviorImpact
amount * totalShares / totalAssets0 / totalAssetsReturns 0{impact}
amount * totalAssets / totalSharesamount * X / 0ABORT{DoS, broken withdrawal}
rewards / totalSharesrewards / 0ABORT{reward distribution broken}

For each division: is there a zero-check guard? If not, what transaction aborts?

6b. Zero-Amount Operations at Zero State

OperationAt Zero StateResultExpected?
deposit(0) at totalShares=0{behavior}{shares issued?}{analysis}
withdraw(0) at totalShares=0{behavior}{aborts?}{analysis}
claim_rewards() at totalShares=0{behavior}{rewards distributed?}{analysis}

7. Protocol Reset Functions

Check for admin functions that can force zero state:

FunctionAccess ControlClears All State?Residual After Reset
{emergencywithdrawfn}{who}YES/NO{what remains}
{rescuetokensfn}{who}YES/NO{what remains}
{pause + drain_fn}{who}YES/NO{what remains}
{migrate_fn}{who}YES/NO{what remains in old module}

For each: what state persists after the "reset"? Can it be exploited?

Instantiation Parameters

{CONTRACTS}              -- Move modules containing vault/pool logic
{SHARE_VARIABLES}        -- Variables tracking total shares/supply
{ASSET_VARIABLES}        -- Variables tracking total assets/deposits
{SHARE_MINT_FORMULA}     -- Share calculation formula at deposit
{FIRST_DEPOSIT_GUARDS}   -- Existing first-deposit protections

Finding Template

**ID**: [ZS-N]
**Severity**: [typically HIGH if funds extractable, MEDIUM if DoS]
**Step Execution**: checkmark1,2,3,4,5,6,7 | x(reasons) | ?(uncertain)
**Rules Applied**: [R4:Y, R10:Y, R11:Y]
**Location**: module::function:LineN
**Title**: [Zero-state type] allows [attack] due to [residual state / missing protection]
**Description**:
- Protocol can reach totalShares=0 via [mechanism]
- When this happens, [state variable] retains value of [amount]
- A new depositor can [exploit path]
**Impact**: [Fund extraction / exchange rate manipulation / DoS]

Output Schema

FieldRequiredDescription
zerostatetransitionsyesAll paths to zero state
firstdepositoranalysisyesFirst deposit attack assessment
residual_assetsyesWhat persists after zero state
reentryvulnerabilityyesWhether return-to-zero recreates first-depositor conditions
edge_casesyesDivision by zero and zero-amount operations
findingyesCONFIRMED / REFUTED / CONTESTED
evidenceyesCode locations with line numbers
step_executionyesStatus for each step

Step Execution Checklist (MANDATORY)

SectionRequiredCompleted?Notes
1. Identify Zero-State TransitionsYESY/x/?
2. First Depositor AnalysisYESY/x/?Including 2a formula + 2b protections
3. Return to Zero AnalysisYESY/x/?Including 3a scenarios + 3b exact zero trace
4. Residual Asset CheckYESY/x/?All sub-checks: 4a rewards, 4b fees, 4c dust, 4d pending
5. Re-Entry Vulnerability AnalysisYESY/x/?Compare initial vs return-to-zero protections
6. Empty Pool Edge CasesYESY/x/?Division by zero + zero-amount ops
7. Protocol Reset FunctionsIF admin reset existsY/x(N/A)/?

Cross-Reference Markers

After Section 2 (First Depositor): Cross-reference with TOKEN_FLOW_TRACING.md Section 5 for unsolicited deposit vectors that amplify first-depositor attacks.

After Section 4 (Residual Assets): If residual rewards/fees found, cross-reference with ECONOMIC_DESIGN_AUDIT.md for whether fee/reward accumulation is bounded.

After Section 5 (Re-Entry): If return-to-zero is possible AND first-deposit protection is initial-only -> FINDING (minimum Medium, upgrade to High if unsolicited deposits can amplify).