Skip to content

feat(voting): record and retry failed vault releases (closes #314) - #319

Open
Ranjeet2063 wants to merge 1 commit into
Vero-protocol:mainfrom
Ranjeet2063:feat/issue-314-vault-release-retry
Open

Ranjeet2063 wants to merge 1 commit into
Vero-protocol:mainfrom
Ranjeet2063:feat/issue-314-vault-release-retry

Conversation

@Ranjeet2063

Copy link
Copy Markdown

Description

Closes #314

Resolves fund-stranding for tasks that resolve successfully but whose vault release fails. Previously, try_release_vault_funds swallowed vault failures by emitting an event, but because the task was marked is_done = true, the release block guarded by !t.is_done could never execute again, stranding contributor payouts permanently on-chain.

This PR introduces:

  1. Persistent recording of failed releases in contract storage under DataKey::PendingRelease(task_id) and DataKey::AllPendingReleases.
  2. A permissionless retry_vault_release(task_id) entrypoint guarded against reentrancy and paused states, which attempts the release again, clears the pending record on success, and prevents double-releasing.
  3. A public read entrypoint get_pending_releases() to query all outstanding unreleased task IDs without replaying past events.
  4. Clean compaction of pending release state if a terminal task is purged via purge_task.

Key Changes

1. Storage Layout (src/contracts/storage_layout.rs, src/storage.rs)

  • Added DataKey::PendingRelease(u64) namespace key and DataKey::AllPendingReleases index key.
  • Added helper functions:
    • has_pending_release(env, task_id) -> bool
    • get_pending_releases(env) -> Vec<u64>
    • set_pending_release(env, task_id) (appends to AllPendingReleases if not already tracked)
    • remove_pending_release(env, task_id) (removes key and compacts AllPendingReleases)

2. Error Definition & Gas Accounting (src/types.rs, src/gas.rs)

  • Added error variant ContractError::NoPendingRelease = 43.
  • Added operation Operation::RetryVaultRelease = 30 and registered calibrated gas estimate COST_RETRY_VAULT_RELEASE = 3_000_000.

3. Voting & Recovery Implementation (src/contracts/voting.rs, src/contracts/logic.rs)

  • In try_release_vault_funds: On Err(_), records storage::set_pending_release(env, task_id) while maintaining non-reverting resolution fault isolation.
  • Implemented retry_vault_release(env, task_id):
    • Validates task ID and checks that the contract is not paused.
    • Verifies has_pending_release; returns Err(ContractError::NoPendingRelease) if not pending.
    • Acquires reentrancy lock.
    • Invokes VaultClient::try_release_funds.
    • On success: clears pending release record, emits vault_ok event, releases reentrancy lock.
    • On failure: preserves pending record for future retry, emits vault_err event, releases reentrancy lock, and returns Err(ContractError::EscrowUnavailable).

4. Entrypoint Surface (src/contracts/proxy_entry/entry_tasks.rs)

  • Exposed public write entrypoint pub fn retry_vault_release(env: Env, task_id: u64) -> Result<(), ContractError>.
  • Exposed public read entrypoint pub fn get_pending_releases(env: Env) -> Vec<u64>.

5. Task Purge Compaction (src/task.rs)

  • In purge_task: cleans up any pending release storage and index entries for terminal tasks.

6. Test Suite (tests/vault_release_retry.rs)

  • Added 9 comprehensive integration tests covering all Acceptance Criteria:
    1. test_task_resolves_and_records_pending_release_when_vault_fails: Task resolves even if vault fails, and task is recorded in get_pending_releases().
    2. test_successful_vault_release_does_not_create_pending_release: Happy path releases directly without creating pending record.
    3. test_retry_after_vault_recovers_releases_funds_and_clears_record: Vault recovery enables successful retry which clears the pending record.
    4. test_retry_for_task_with_no_pending_release_is_rejected: Non-pending tasks reject retry with NoPendingRelease.
    5. test_retrying_twice_does_not_double_release: Second retry attempt rejects with NoPendingRelease.
    6. test_retry_fails_when_vault_still_unhealthy_preserves_record: Unhealthy vault failure preserves pending record.
    7. test_purging_task_cleans_up_pending_release: Task purging clears pending release state.
    8. test_retry_vault_release_paused_rejected: Paused contract rejects retry.
    9. test_batch_voting_records_pending_release_on_failure: Batch voting correctly records multiple failed releases.

Acceptance Criteria Checklist

…tocol#314)

- Record failed vault releases in instance storage (PendingRelease(task_id) and AllPendingReleases)
- Add permissionless retry_vault_release entrypoint that clears pending record on success
- Reject retrying tasks with no pending release with ContractError::NoPendingRelease
- Prevent double releases on repeated retries
- Clean up pending release record and compact index if a task is purged
- Expose public get_pending_releases query entrypoint
- Add comprehensive unit test suite covering all Acceptance Criteria
Copilot AI lite review requested due to automatic review settings September 19, 2026 09:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Ranjeet2063

Copy link
Copy Markdown
Author

Hello @maintainers,

Just a quick note that all local unit and integration tests are passing cleanly across the test suite (cargo test). Could maintainers please trigger / approve the CI workflow run on this pull request when convenient? Thank you!

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.

A failed vault release strands a resolved task's funds permanently, with no retry path

3 participants