# Collateral double-pull and missing accounting

Collateral moved but recorded once, or recorded but not moved, in deposit and seize paths

_Area: DeFi accounting and math bugs. Seen in 3 disclosed reports; latest 2025-05-22. Updated 2026-11-01._

## How it works
Collateral accounting assumes that every movement of tokens and every update to the books happen together, exactly once. The bug appears when a refactor or a new code path breaks that pairing: tokens are pulled twice while only one deposit is recorded [E1](https://bugbountylibrary.com/r/sherlock%3A2024-02-tapioca%3AM-31.md), a returned struct field is read but never set so a computed amount silently becomes zero [E2](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AH-8.md), or a balance is credited without registering the asset in the list that later sums it [E3](https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AM-9.md). In each case the ledger and the actual token position diverge, and downstream math trusts the wrong one.

## Where it shows up
- Helper or swap functions whose transfer destination changed (to sender vs. to user) while callers still pull from the user [E1](https://bugbountylibrary.com/r/sherlock%3A2024-02-tapioca%3AM-31.md).
- Library functions returning structs where the caller reads fields the library never assigns [E2](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AH-8.md).
- Enumerable asset lists (userSuppliedAssets) updated separately from per-asset balance mappings [E3](https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AM-9.md).
- Deposit, seize, reward, or transfer-in paths that credit a balance without a matching registry or transfer call.

## How to review
For each path that moves collateral, trace both sides: the token transfer(s) and the bookkeeping write(s). The invariant: for every deposit or seize, tokens moved equals balance recorded, exactly once, and every credited asset appears in any list used to compute liquidity. Check that every struct field a caller reads is assigned by the producer [E2](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AH-8.md), and that any balance credit also registers the asset [E3](https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AM-9.md). When a transfer destination changes, re-audit every downstream call that also pulls tokens [E1](https://bugbountylibrary.com/r/sherlock%3A2024-02-tapioca%3AM-31.md).

## Variants seen
- Double-pull: swapped collateral sent to the market, then _addCollateral pulls the same shares from the user again [E1](https://bugbountylibrary.com/r/sherlock%3A2024-02-tapioca%3AM-31.md).
- Uninitialized return field: remainingCollateral defaults to zero, so all NFT collateral is moved into bucketTokenIds and frozen [E2](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AH-8.md).
- Missing registry entry: seized lToken credited to totalInvestment but absent from userSuppliedAssets, so redeem sees zero collateral and reverts [E3](https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AM-9.md).

## Impact and severity
Users are silently double-charged [E1](https://bugbountylibrary.com/r/sherlock%3A2024-02-tapioca%3AM-31.md), collateral is permanently frozen [E2](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AH-8.md), or seized rewards are trapped and unredeemable [E3](https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AM-9.md). Severity is high where funds are locked or lost; it worsens when the divergence is silent and only surfaces at withdrawal or liquidation time.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2024-02-tapioca%3AM-31.md
- E2: https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AH-8.md
- E3: https://bugbountylibrary.com/r/sherlock%3A2025-05-lend-audit-contest%3AM-9.md
