Skip to content

fix(settlement): enforce strict idempotency guard to prevent double-c…#41

Merged
elizabetheonoja-art merged 1 commit into
Utility-Protocol:mainfrom
yunus-dev-codecrafter:bugfix/settlement-idempotency-guard
Jun 25, 2026
Merged

fix(settlement): enforce strict idempotency guard to prevent double-c…#41
elizabetheonoja-art merged 1 commit into
Utility-Protocol:mainfrom
yunus-dev-codecrafter:bugfix/settlement-idempotency-guard

Conversation

@yunus-dev-codecrafter

Copy link
Copy Markdown
Contributor

closes #23

PR: Strict Idempotency Guard for Settlement Finalization

Summary

Adds a strict idempotency guard to finalize_settlement() by introducing a separate storage flag (DataKey::BatchFinalized) that is checked and set before any state mutation, preventing accidental or malicious double-finalization of batch proposals.

Problem

finalize_settlement() could be called multiple times for the same proposal. The existing check on proposal.finalized had two issues:

  1. Happened after state mutation — the deadline check at the top called release_locked_resources() (a storage write) before checking proposal.finalized.
  2. Not independently persisted — the finalized flag was stored inside the SettlementProposal struct, which was only written to storage at the very end of the function after all other operations. If the function panicked mid-execution (e.g., in release_locked_resources()), the struct update was lost.

Solution

A two-layer defense with an independent storage flag:

Layer Mechanism When checked
Primary DataKey::BatchFinalized(proposal_id) — independent bool in instance storage First operation after entry
Secondary proposal.finalized field on the stored SettlementProposal struct After deadline check (belt-and-suspenders)

Changes

contracts/utility_contracts/src/lib.rs

  • Added BatchFinalized(u64) variant to the DataKey enum

contracts/utility_contracts/src/settlement.rs

  • Imported DataKey
  • Restructured finalize_settlement() to:
    1. Check DataKey::BatchFinalized(proposal_id) — reject with AlreadyFinalized if set
    2. Set the flag to true (optimistic — remains true even if the function panics later)
    3. Proceed with proposal retrieval, deadline check, auth, and resource release
  • Added two new tests:
    • test_double_finalization_via_independent_flag — verifies the flag blocks re-finalization even when the stored proposal.finalized field is manually reset to false
    • test_partial_execution_after_deadline_blocks_retry — verifies that a failed finalization (deadline exceeded after flag was set) permanently blocks future attempts

Design Rationale

  • Optimistic approach: If the function panics after setting the flag but before completing, the settlement is considered finalized. This is safer than allowing another execution attempt that could lead to double-claiming of tokens.
  • Separate storage key: The flag is stored independently from the SettlementProposal struct, ensuring it survives partial write failures.
  • Minimal gas overhead: ~2,500 instructions for the read-check-write sequence on the single-byte flag.
  • Consistent with codebase: Uses u64 for proposal_id matching the existing convention, not BytesN<32>.

@elizabetheonoja-art elizabetheonoja-art merged commit 2cdbadb into Utility-Protocol:main Jun 25, 2026
2 checks passed
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.

Missing Idempotency Guard in Settlement Finalization

2 participants