Skip to content

fix(rewards): harden direct close lifecycle and revoke accounting#32

Open
dev-jodee wants to merge 5 commits intomainfrom
fix/direct-tombstone-and-revoke-hardening
Open

fix(rewards): harden direct close lifecycle and revoke accounting#32
dev-jodee wants to merge 5 commits intomainfrom
fix/direct-tombstone-and-revoke-hardening

Conversation

@dev-jodee
Copy link
Copy Markdown
Collaborator

Summary

  • add direct distribution tombstone state/PDA checks so closed distributions cannot be recreated at the same address
  • create the tombstone during CloseDirectDistribution and require tombstone + system_program accounts in direct create/close flows
  • allow CloseDirectRecipient to succeed after the parent distribution account is closed
  • update merkle revoke non-vested handling to persist MerkleClaim.claimed_amount correctly when a claim account exists

Test Plan

  • just build
  • cargo test -p tests-rewards-program test_create_direct_distribution -- --nocapture
  • cargo test -p tests-rewards-program test_close_direct_distribution -- --nocapture
  • cargo test -p tests-rewards-program test_close_direct_recipient -- --nocapture
  • cargo test -p tests-rewards-program test_revoke_merkle_claim -- --nocapture
  • cargo test -p rewards-program

Add direct-distribution tombstones so a closed direct distribution cannot be recreated at the same PDA, and wire close/create instruction accounts accordingly. Also allow closing direct recipient accounts after the parent direct distribution is closed and keep merkle claim claimed_amount in sync for non-vested revoke flows.
Update direct distribution create/close web instruction builders to derive and include the tombstone PDA required by the generated client types after the direct tombstone hardening change.
@dev-jodee dev-jodee requested a review from amilz April 13, 2026 16:49
…mbstone PDA

Replace the separate DirectDistributionTombstone PDA with an in-place
state flip on the distribution PDA itself. On close, the distribution's
discriminator byte is rewritten to DirectDistributionClosed (1-byte data
= bump), the account is resized from 130 bytes down to 3 bytes, and the
freed rent is refunded to the authority. On create, the first byte is
inspected — if it matches DirectDistributionClosed::DISCRIMINATOR, the
instruction returns DistributionPermanentlyClosed.

Benefits over the tombstone approach:
- No extra account passed in each create/close transaction
- No extra PDA seed derivation
- Simpler mental model — closed is just a state of the same PDA
- Smaller transaction size

close_direct_recipient detects closed distributions by checking the
discriminator byte instead of the account owner (the PDA now always
stays program-owned).

IDL: CloseDirectDistribution lost tombstone + system_program accounts
(8 total); CreateDirectDistribution lost tombstone account (11 total).

Net -157 lines of code. All 407 unit + 207 integration tests pass.
Replace inline discriminator-byte checks in create_distribution and
close_recipient processors with a single `DirectDistribution::is_closed`
method. The method takes an AccountView and program ID, returns true
iff the account is program-owned and its first byte matches the
DirectDistributionClosed discriminator.

Callers are clearer about intent ("is this distribution closed?") and
the byte-layout detail is encapsulated on the type that owns it.
…t helpers

Pull the state-flip logic out of the close_direct_distribution processor:

- Add DirectDistribution::close_in_place(account, rent_recipient) which
  overwrites the account header with DirectDistributionClosed's discriminator
  + version, resizes the account to the closed marker's length, and refunds
  freed rent to the recipient.

- Add refund_excess_rent(account, recipient, new_size) as a general-purpose
  utility in pda_utils.rs for any program-owned account that was resized
  down and needs its excess lamports returned.

Processor drops ~15 lines of state-flip + rent-refund bookkeeping to a
single call.
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.

1 participant