Problem
MaintenancePool.last_withdraw_at is initialized to 0 when a pool is created in deposit(), and only withdraw() ever updates it. reclaim_deposit gates on:
let now = env.ledger().timestamp();
if now < pool.last_withdraw_at + INACTIVITY_WINDOW { // 0 + 90 days
return Err(Error::InactivityWindowNotElapsed);
}
For any pool that hasn't had a withdrawal yet, this compares the current ledger time with Unix epoch + 90 days (1970-04-01). On any real network, now is decades past that, so the check always passes. A sponsor can deposit and call reclaim_deposit in the next transaction. The "90-day inactivity" guarantee that #42 and the README describe doesn't apply to new pools.
This undermines the pool's purpose. Maintainers and the oracle plan rewards around a funded pool, but the funds can be pulled at any moment until the first withdraw. And with the separate "reclaim never marks the deposit as consumed" issue, a fresh pool can be drained immediately.
The test suite doesn't catch this because Soroban's test ledger starts at timestamp 0. #346's boundary test advances time from 0, so "0 + window" and "created_at + window" give the same result there.
Suggested fix
Measure inactivity from the most recent activity:
let last_activity = pool.last_withdraw_at.max(pool.created_at);
// or per-deposit: .max(deposit.timestamp)
if now < last_activity.saturating_add(INACTIVITY_WINDOW) { ... }
- Using
deposit.timestamp as well stops a late deposit into a long-idle pool from being instantly reclaimable.
- Add a test that first moves the ledger to a realistic timestamp, such as
1_700_000_000, and then creates and deposits: an immediate reclaim must fail.
Related: #42, #346.
Problem
MaintenancePool.last_withdraw_atis initialized to0when a pool is created indeposit(), and onlywithdraw()ever updates it.reclaim_depositgates on:For any pool that hasn't had a withdrawal yet, this compares the current ledger time with Unix epoch + 90 days (1970-04-01). On any real network,
nowis decades past that, so the check always passes. A sponsor can deposit and callreclaim_depositin the next transaction. The "90-day inactivity" guarantee that #42 and the README describe doesn't apply to new pools.This undermines the pool's purpose. Maintainers and the oracle plan rewards around a funded pool, but the funds can be pulled at any moment until the first
withdraw. And with the separate "reclaim never marks the deposit as consumed" issue, a fresh pool can be drained immediately.The test suite doesn't catch this because Soroban's test ledger starts at timestamp
0. #346's boundary test advances time from0, so "0 + window" and "created_at + window" give the same result there.Suggested fix
Measure inactivity from the most recent activity:
deposit.timestampas well stops a late deposit into a long-idle pool from being instantly reclaimable.1_700_000_000, and then creates and deposits: an immediate reclaim must fail.Related: #42, #346.