Skip to content

feat(proposal): mandatory timelock between approval and execution (issue #93) - #194

Open
merciiiqode wants to merge 5 commits into
ASTROIDX556:mainfrom
merciiiqode:issue93astroid-contract
Open

feat(proposal): mandatory timelock between approval and execution (issue #93)#194
merciiiqode wants to merge 5 commits into
ASTROIDX556:mainfrom
merciiiqode:issue93astroid-contract

Conversation

@merciiiqode

Copy link
Copy Markdown

Closes #93

Summary

Adds a mandatory minimum timelock between a proposal's approval and its
execution in contracts/proposal, so a sudden governance takeover cannot
execute freshly-approved proposals before the delay gives honest members time
to act.

What changed

  • Proposal.approved_at — the ledger timestamp is recorded exactly once,
    when the threshold is reached and the proposal transitions to Approved
    (0 until then, and preserved through the rest of its life).
  • Configured delay in contract stateinitialize now takes the
    per-proposal timelock (seconds) and stores it under a new instance
    Timelock key; 0 disables the delay. Stored on-chain, applied to every
    proposal.
  • Validation in execute — after the state gate and before dependency
    resolution, execution refuses with TimelockNotExpired until
    env.ledger().timestamp() >= approved_at + timelock (computed with checked
    math). Reuses the protocol-wide timelock constant via the shared
    require_time_reached helper, and the ordering ensures a premature attempt
    is reported as a scheduling error rather than a (still accurate) dependency
    failure.
  • Compatibility — the lock gates only execute; approval, rejection,
    cancellation, expiry, fail, close, and the dependency/completion views
    are untouched.

Acceptance criteria

  • Timelock validation in the proposal execution function (execute)
  • Distinct deterministic error constant for premature attempts
    (TimelockNotExpired = 91)
  • Unit tests: execution fails before the timelock and succeeds after
  • Existing state transitions remain compatible (approve/reject/cancel/
    expire/fail/close and views still work, zero timelock = old behaviour)

Verification

  • cargo test -p astroid-proposal — 34 tests pass (5 new)
  • cargo test --workspace — 347 tests pass
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo fmt --all --check — clean

Note: branches back to the accumulated repair/feature branch (#189/#192) because
upstream main doesn't compile; merge those first.

…cel flows

The repo was committed in a broken mid-merge state that did not compile
(duplicated error enum variants beyond the XDR limit, merged functions,
unclosed delimiters, stale helper signatures). Restore it so
`cargo test --workspace`, `cargo fmt` and clippy all pass, and finish
the escrow work this branch was opened for (issue ASTROIDX556#80).

shared:
- rewrite Error enum as exactly 50 #[contracterror] variants; add
  GraceActive / EscrowNotExpired / EscrowAlreadySettled / ReserveViolation
- fix safe-arithmetic test expectations (Overflow for i128::MIN-1; mul
  round-trips below i128::MAX)

escrow (issue ASTROIDX556#80):
- implement expiration deadline handling: post-deadline/grace refunds
  (refund, reclaim, refund_timelock, expire) return ESCROW_EXPIRED and
  graceful errors, plus sender-only clawback cancel with EscrowNotExpired /
  GraceActive / EscrowAlreadySettled transitions into Cancelled
- reconstruct multi-asset escrow (create, timelock, scheduled, milestones,
  override release, vesting/claiming views, structured events)
- reconcile tests to new cancel semantics; fix stale helper signatures

policy:
- un-merge whitelist/blocklist/category functions corrupted by the merge;
  repair check_transfer gates (recipient whitelist, dedupe blacklist checks)
- update register_policy helper to current 9-arg signature

wallet:
- Restore lost batch_execute (atomic sub-calls); sub-calls target external
  contracts (Soroban forbids self re-entry), gate on wallet Active state,
  surface callee errors via try_invoke_contract
- Fix Val (RawVal rename), ContractCall arg encoding, set_reserve_ratio auth

multisig: TooManySigners -> InvalidThreshold; budget/treasury: clippy fixes
…tion status

Issue ASTROIDX556#82 already had the core chaining machinery (prerequisite ids in
storage, acyclicity checks, ensure_dependencies_met enforcing the executed
state before execute). This completes the acceptance criteria that were
still missing:

- Emit structured events whenever a dependency chain is validated:
  ("proposal", "dep_ok") when every prerequisite has executed, and
  ("proposal", "dep_fail") with the id of the first unmet prerequisite
  when execution is blocked.
- Add an is_executed view exposing has_executed() (Executed | Closed) so
  downstream contracts can query a proposal's completion status directly.
- Tests: blocked execution emits dep_fail (no dep_ok), satisfied chains
  emit dep_ok, end-to-end completion via is_executed including a Failed
  proposal never counting as executed.

Closes ASTROIDX556#82
Adds a validated batch path to the wallet alongside the existing raw
batch_execute. Each BatchAction carries the external sub-call plus the
metadata needed to gate it: policy envelope id (asset/recipient/amount are
checked against the wired policy contract) and budget envelope id (consumed
from the wired budget contract). Empty ids skip the corresponding gate.

batch_execute_validated is two-phase:
  1. Validate every action and aggregate its value with checked math in a
     single iteration pass (one budget consumption per envelope, no
     speculative pre-flights).
  2. Execute the sub-calls sequentially.
Any failure — policy denial, budget overrun, cumulative overflow, failing
sub-call — reverts the whole transaction, so validation and execution are
atomic. On success an aggregated BatchReceipt (executed, total_amount,
budget_remaining) is returned and ("wallet", "batch_validated") is
published.

Contract-level set_policy/set_budget (admin) wire the gates, mirroring the
treasury convention. Tests cover success, policy denial rollback, budget
insufficiency rollback, cumulative overflow, unwired-gate refusal,
envelope-less execution, mixed-asset aggregation, and the standard empty /
role / frozen gates.

Closes ASTROIDX556#90
…tion

Protects against sudden governance takeovers by refusing to execute a
freshly-approved proposal until its timelock delay has elapsed.

- Proposal gains an approved_at timestamp, recorded once when the threshold
  is reached (approve transitions to Approved); the created record carries
  approved_at = 0 until then.
- initialize now takes and stores the mandatory per-proposal timelock
  (seconds) in a new instance Timelock key; 0 disables the delay.
- execute checks the timelock after the state gate and before dependency
  resolution, reusing the protocol-wide TimelockNotExpired constant via the
  shared require_time_reached helper: a premature attempt is reported as a
  scheduling error rather than a dependency failure. State transitions and
  views (dependencies_met, is_executed, fail) are unaffected by the lock.
- Tests: approval records the timestamp; refusal inside the window; success
  at and after the release time; zero timelock preserves the historical
  immediate-execution behaviour; the lock never blocks non-execution state
  transitions. Proposal suite is now 34 tests.

Closes ASTROIDX556#93
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@merciiiqode 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

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

resolve conflict on this issue

@K1NGD4VID

Copy link
Copy Markdown
Contributor

resolve conflict on this issue @merciiiqode

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.

feat: implement timelock delay validation in proposal contract execution flow

3 participants