Skip to content

Soroban Escrow: Fix Storage TTL — Prevent Fund Loss on Entry Expiry #97

Description

@0xdevcollins

Severity: CRITICAL — Funds at Risk

Problem

Soroban persistent storage entries have a TTL (time-to-live). If a lock entry expires before the timelock does, it gets garbage collected and the funds become unrecoverable — permanently lost.

The current HTLC contract uses:
```rust
env.storage().persistent().set(&DataKey::Lock(lock_id.clone()), &LockEntry { ... });
```
But never calls extend_ttl(), so entries will expire after the Soroban default TTL (~30 days on mainnet).

A payment with a 24-hour timelock that gets created near a TTL boundary could lose funds.

Fix Required

Add TTL extension in every function that touches a lock entry:

```rust
// After every persistent().set() or get(), extend to cover timelock + buffer
let ttl_needed = entry.timelock
.saturating_sub(env.ledger().timestamp())
.saturating_add(BUFFER_LEDGERS); // e.g. 17280 ledgers (~1 day buffer)

env.storage().persistent().extend_ttl(
&DataKey::Lock(lock_id.clone()),
ttl_needed,
ttl_needed,
);
```

Apply this to:

  • lock() — extend after initial set
  • withdraw() — extend before and after state change
  • refund() — extend before and after state change
  • get_lock() — extend on read so queries don't cause expiry

Also Fix in Settlement Contract

The SettlementInfo entries have the same vulnerability.

Acceptance Criteria

  • All persistent storage entries extend TTL to cover timelock + 1 day buffer
  • TTL calculation is based on ledger sequence, not wall-clock time
  • Existing tests still pass
  • Add test: verify lock entry survives simulated TTL pressure

Metadata

Metadata

Assignees

No one assigned

    Labels

    blockchainSmart contracts and chain integrationssecuritySecurity fix or hardening

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions