Description
Add a recovery path for tasks that resolved successfully but whose vault release failed.
Problem Statement
src/contracts/voting.rs:22 swallows vault failures by design:
match result {
Ok(_) => events::emit_vault_release_success(env, task_id),
Err(_e) => events::emit_vault_release_failed(env, task_id),
}
This is the deliberate fix for #134 / #184 ("Vault release_funds call is not fault-isolated — a broken vault can permanently block task resolution"), and as a DoS fix it is correct: resolution must not be hostage to a broken vault.
But it traded a denial of service for silent fund-stranding, and the recovery half was never built.
When the release fails, the task is still marked is_done, resolved_at is set, and the task is persisted by storage::set_active_task. The release block is guarded by !t.is_done, so it can never run again for that task.
Searching every .rs file in the crate, release_funds appears only in voting.rs, events.rs and gas.rs. There is no admin retry entrypoint, no pending-release record, and no re-trigger path anywhere in the contract.
So a vault that is paused, underfunded, misconfigured, mid-upgrade, or simply out of gas for one subcall loses that task's payout permanently. The only trace is a vault_release_failed event, which nothing consumes on-chain.
Given the protocol exists to make contributor payouts verifiable, a resolved task whose funds silently never move is a direct failure of its core promise.
Proposed Changes
Technical Implementation Scaffolding
- Target Repository: vero-core-contracts
- Target Path: src/contracts/voting.rs, src/contracts/vault_ops.rs, src/types.rs
- Branch Naming: feat/issue--vault-release-retry
- Authority Context: Security-sensitive — permanent loss of contributor payouts
Acceptance Criteria
Definition of Done
This issue is self-contained. Everything it needs already exists on main; it does not wait on any other issue. Deliver the change and its tests in one PR.
Description
Add a recovery path for tasks that resolved successfully but whose vault release failed.
Problem Statement
src/contracts/voting.rs:22swallows vault failures by design:This is the deliberate fix for #134 / #184 ("Vault release_funds call is not fault-isolated — a broken vault can permanently block task resolution"), and as a DoS fix it is correct: resolution must not be hostage to a broken vault.
But it traded a denial of service for silent fund-stranding, and the recovery half was never built.
When the release fails, the task is still marked
is_done,resolved_atis set, and the task is persisted bystorage::set_active_task. The release block is guarded by!t.is_done, so it can never run again for that task.Searching every
.rsfile in the crate,release_fundsappears only invoting.rs,events.rsandgas.rs. There is no admin retry entrypoint, no pending-release record, and no re-trigger path anywhere in the contract.So a vault that is paused, underfunded, misconfigured, mid-upgrade, or simply out of gas for one subcall loses that task's payout permanently. The only trace is a
vault_release_failedevent, which nothing consumes on-chain.Given the protocol exists to make contributor payouts verifiable, a resolved task whose funds silently never move is a direct failure of its core promise.
Proposed Changes
DataKey::PendingRelease(task_id)) whentry_release_vault_fundsfails, rather than only emitting an eventTechnical Implementation Scaffolding
Acceptance Criteria
Definition of Done
This issue is self-contained. Everything it needs already exists on
main; it does not wait on any other issue. Deliver the change and its tests in one PR.