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:
Also Fix in Settlement Contract
The SettlementInfo entries have the same vulnerability.
Acceptance Criteria
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 setwithdraw()— extend before and after state changerefund()— extend before and after state changeget_lock()— extend on read so queries don't cause expiryAlso Fix in Settlement Contract
The
SettlementInfoentries have the same vulnerability.Acceptance Criteria