Skip to content

Require a timelock for emergency surplus withdrawal (#968) - #1134

Open
Ada-Girly881 wants to merge 1 commit into
Pulsefy:mainfrom
Ada-Girly881:feat/968-surplus-withdrawal-timelock
Open

Ada-Girly881 wants to merge 1 commit into
Pulsefy:mainfrom
Ada-Girly881:feat/968-surplus-withdrawal-timelock

Conversation

@Ada-Girly881

Copy link
Copy Markdown

Summary

withdraw_surplus let the admin move unallocated surplus funds out of the
escrow contract in a single transaction. A compromised admin key could
therefore drain surplus funds instantly, with no observation window — and
the existing two-step admin transfer doesn't help, since the withdrawal
itself was never a multi-step action.

This PR replaces the single-step withdrawal with a propose → execute
timelock flow:

  • propose_surplus_withdrawal(to, amount, token) — admin-only, validates
    the amount/token/available surplus and records a pending proposal with an
    unlock_time set to now + delay. Overwrites any existing proposal.
  • execute_surplus_withdrawal() — admin-only, transfers funds to the
    proposed recipient once unlock_time has passed. Re-validates available
    surplus at execution time (not just at proposal time), since balances can
    shift while a proposal is pending.
  • cancel_surplus_withdrawal() — admin-only, discards a pending proposal
    without moving funds.
  • get_pending_surplus_withdrawal() — returns the outstanding proposal, if
    any.
  • get_surplus_withdrawal_delay() / set_surplus_withdrawal_delay(seconds)
    — the delay is admin-configurable (defaults to 1 day via
    DEFAULT_SURPLUS_WITHDRAWAL_DELAY).

New errors

  • TimelockNotElapsed (26) — execute_surplus_withdrawal called before
    unlock_time.
  • NoPendingWithdrawal (27) — cancel/execute called with nothing pending.

New events

  • SurplusWithdrawalProposed
  • SurplusWithdrawalCancelled
  • SurplusWithdrawnEvent (now emitted by execute_surplus_withdrawal
    instead of the removed withdraw_surplus; payload unchanged)

Storage

  • KEY_SURPLUS_WITHDRAWAL_DELAY — configurable delay in seconds.
  • KEY_PENDING_SURPLUS_WITHDRAWAL — the single outstanding proposal, if any.

Acceptance criteria

  • Surplus withdrawal is a propose-then-execute flow with a configurable delay
  • A pending withdrawal is queryable and cancellable by the admin
  • Executing before the delay elapses fails with a distinct error (TimelockNotElapsed)
  • Proposal, cancellation, and execution each emit events
  • Tests cover the full lifecycle including boundary timing (one second
    before/exactly at unlock_time, re-validation of surplus at execution
    time, overwrite semantics, and the configurable-delay path)

Files changed

  • app/onchain/contracts/aid_escrow/src/lib.rs — replaces withdraw_surplus
    with the propose/cancel/execute/delay functions, new error variants, and
    new events.
  • app/onchain/contracts/aid_escrow/src/keys.rs — new storage key constants.
  • app/onchain/contracts/aid_escrow/tests/withdraw_surplus.rs — rewritten
    for the full propose/cancel/execute lifecycle, including boundary timing.
  • app/onchain/contracts/aid_escrow/tests/events.rs — new event assertions
    for propose/cancel, updated execute-path assertion.
  • app/onchain/contracts/aid_escrow/tests/pause_controls.rs — pause checks
    now exercise propose_surplus_withdrawal.
  • app/onchain/contracts/aid_escrow/tests/property_based_invariants.rs
    fuzz harness collapses propose+execute (zero delay in the fixture) to keep
    exercising the same accounting invariants.
  • app/onchain/contracts/aid_escrow/tests/storage_keys.rs,
    src/keys.rs (test module) — collision tests cover the two new keys.
  • app/onchain/contracts/aid_escrow/tests/error_codes.rs — pins the two new
    error codes (and backfills previously-missing distributor codes for
    contiguity).
  • app/onchain/contracts/aid_escrow/README.md, EVENTS.md,
    STORAGE_KEYS.md — documentation for the new functions, events, and keys.
  • app/backend/src/onchain/utils/soroban-error.mapper.ts — maps the two new
    contract error codes.

Compatibility notes

withdraw_surplus is removed; no other code in this repo (backend adapter,
frontend, mobile) called it, so this is not a breaking change for any
in-repo caller. The Testnet contract will need to be redeployed to pick up
the new entrypoints.

Closes #968

@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@Ada-Girly881 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Cedarich

Copy link
Copy Markdown
Contributor

Kindly fix CI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Require a Timelock for Emergency Surplus Withdrawal

2 participants