Skip to main content
Technical proposal SIMD-0432 Review

Loader V3: Reclaim Closed Program

  1. Idea
  2. Draft
  3. Review
  4. Accept
  5. Implement
  6. Active

Decision brief

Why this proposal matters

This SIMD proposes changing the default behavior when closing upgradeable programs so that program accounts are fully reclaimed and their addresses become reusable. Tombstoning program accounts would remain supported, but only when explicitly requested.

Source-backed reading aid · source language: EN

Proposal at a glance

What changes

  • Account 0: Programdata account (writable)
  • Account 1: Recipient (writable)
  • Account 2: Authority (signer)

Stakeholder map

Who is affected

Builders & client teams Medium impact

This proposal removes a harmful default behavior that has caused repeated loss of funds and persistent state bloat, while preserving security guarantees for users who explicitly wish to permanently disable a program ID.

Action requirement unknown
Validators & operators Impact unknown

No proposal-specific evidence was found for this group.

Action requirement unknown
Users & stakers Medium impact

This proposal removes a harmful default behavior that has caused repeated loss of funds and persistent state bloat, while preserving security guarantees for users who explicitly wish to permanently disable a program ID.

Action requirement unknown
Governance & ecosystem Medium impact

This proposal removes a harmful default behavior that has caused repeated loss of funds and persistent state bloat, while preserving security guarantees for users who explicitly wish to permanently disable a program ID.

Action requirement unknown

Exact source revision

Full proposal document

Source-language document. Technical identifiers and evidence remain unchanged. eac20f2994dd

Summary

This SIMD proposes changing the default behavior when closing upgradeable programs so that program accounts are fully reclaimed and their addresses become reusable. Tombstoning program accounts would remain supported, but only when explicitly requested.

Motivation

Today, closing an upgradeable program permanently tombstones its program account, preventing reuse of the program ID. This behavior has led to several issues:

  • Loss of funds: Users frequently confuse close program with close buffer in the Solana CLI, unintentionally irreversibly disabling programs.
  • Permanent account bloat: Tombstoned program accounts cannot be reclaimed and accumulate indefinitely in the accounts database.
  • RPC performance degradation: getProgramAccounts against the loader v3 program must return all program accounts, including closed ones, increasing response size and latency.

These drawbacks outweigh the benefits of mandatory tombstoning. A more flexible model allows safe address reuse by default while preserving explicit tombstoning for users who require it.

New Terminology

No new terminology is introduced by this proposal.

Detailed Design

The Loader V3 Close instruction will be updated to include an optional boolean input. If not provided, the default will be false.

Close { tombstone: bool }
| 4-byte discriminator | 1-byte boolean |

The accounts required by the instruction are unchanged:

  • Account 0: Programdata account (writable)
  • Account 1: Recipient (writable)
  • Account 2: Authority (signer)
  • Account 3: Program account (writable)

Currently, the authority (index 2) and program (index 3) accounts are only required for closure of initialized programs. This proposal also requires them for reclamation of legacy tombstones. This is detailed in the Control Flow section.

Base Workflow

For a value of false, the program will clear the program account's data, resize it to zero, and withdraw all lamports. This will render the account no longer rent-exempt and subject to garbage collection by the runtime at the end of the transaction. As such, the program address can be reclaimed after the account has been garbage collected.

The Close instruction MUST fail if tombstone is false and the program was deployed in the current slot (this field is stored in the programdata account layout). This prevents a deploy-close-reclaim loop within the same slot, which would corrupt the program cache (see Security Considerations). Programs deployed in the current slot can still be closed with tombstone=true.

For a value of true, the program will clear the program account's data, resize it to zero, but retain the rent-exempt minimum lamports for the base account metadata. The program account will then be assigned to itself, creating a permanent tombstone for the program.

                           Close { tombstone }
                                  |
                      +-----------+-----------+
                      |                       |
               tombstone=false          tombstone=true
                      |                       |
              Deployed this slot?        Clear data & resize
               /            \           Retain rent-exempt min
             Yes             No               |
              |               |         Owner → self (tombstone)
         FAIL (reject)  Clear data &    Address permanently locked
                        resize
                        Withdraw all
                        lamports
                              |
                        Account → GC'd
                        Address reclaimable

In both workflows, the programdata account will be defunded and set to Uninitialized, causing it to be garbage collected at end of transaction.

Control Flow

The entire control flow of the Loader V3 Close instruction is detailed below with modifications highlighted.

  1. At least 2 accounts must be provided, otherwise throw NotEnoughAccountKeys.
  2. Accounts at index 0 and 1 must not alias, otherwise throw InvalidArgument.
  3. The "close" account (index 0) must deserialize as UpgradeableLoaderState, otherwise throw InvalidAccountData.

Once the close account's state is deserialized, the remaining control flow depends on the type of account.

ProgramData

  1. At least 4 accounts must be provided, otherwise throw NotEnoughAccountKeys.
  2. The program account (index 3) must be writable, otherwise throw InvalidArgument.
  3. The program account must be owned by Loader v3, otherwise throw IncorrectProgramId.
  4. [NEW] If tombstone is false, the programdata's slot field must not equal the current slot (from Clock sysvar), otherwise throw InvalidArgument.
  5. The program account must deserialize as UpgradeableLoaderState, otherwise throw InvalidAccountData.
  6. The program account must be in Program state, otherwise throw InvalidArgument.
  7. The program account's programdata_address must match the close account (index 0), otherwise throw InvalidArgument.
  8. If the programdata's upgrade_authority_address is None (frozen program), throw Immutable.
  9. The authority (index 2) must match the programdata's upgrade_authority_address, otherwise throw IncorrectAuthority.
  10. The authority must be a signer, otherwise throw MissingRequiredSignature.
  11. Transfer all lamports from the close account to the recipient (index 1).
  12. Set the close account's state to Uninitialized (account will be garbage collected at end of transaction due to zero lamports).
  13. [NEW] Clear the program account's data and resize to zero.
  14. [NEW] If tombstone is true: transfer excess lamports (above rent-exempt minimum for zero-data account) to recipient, then assign the program account to itself.
  15. [NEW] If tombstone is false: transfer all lamports from the program account to the recipient (account will be garbage collected).

Uninitialized

First determine if this is a reclaim of a legacy tombstone.

Programs closed before this proposal remain in a legacy tombstone state:

  • Program account: Owned by Loader v3, Program { programdata } state, funded, programdata address points to programdata account.
  • Programdata account: Uninitialized (all-zeroes).

[NEW] This state must be infallibly evaluated. If the above program account state is confirmed, the control flow for a legacy tombstone reclaim is as follows:

  1. At least 4 accounts must be provided, otherwise throw NotEnoughAccountKeys.
  2. The program account (index 3) must be writable, otherwise throw InvalidArgument.
  3. The program account must be owned by Loader v3, otherwise throw IncorrectProgramId.
  4. The authority (index 2) must equal the program account's pubkey (i.e., the program keypair), otherwise throw IncorrectAuthority.
  5. The authority must be a signer, otherwise throw MissingRequiredSignature.
  6. Transfer all lamports from the close account to the recipient (index 1).
  7. Set the close account's state to Uninitialized (account will be garbage collected at end of transaction due to zero lamports).
  8. Clear the program account's data and resize to zero.
  9. If tombstone is true: transfer excess lamports (above rent-exempt minimum for zero-data account) to recipient, then assign the program account to itself.
  10. If tombstone is false: transfer all lamports from the program account to the recipient.

If this is not a reclaim of a legacy tombstone, the control flow is as follows:

  1. At least 1 account must be provided, otherwise throw NotEnoughAccountKeys.
  2. Transfer all lamports from the close account to the recipient (index 1).

Buffer

  1. At least 3 accounts must be provided, otherwise throw NotEnoughAccountKeys.
  2. If the buffer's authority_address is None, throw Immutable.
  3. The authority (index 2) must match the buffer's authority_address, otherwise throw IncorrectAuthority.
  4. The authority must be a signer, otherwise throw MissingRequiredSignature.
  5. Transfer all lamports from the close account to the recipient (index 1).
  6. Set the close account's state to Uninitialized (account will be garbage collected at end of transaction due to zero lamports).

Program

  1. Throw InvalidArgument. Program accounts cannot be closed directly; the programdata account must be closed instead.

Feature Activation

This change will be a feature-gated behavioral change to the existing Close instruction. After the feature is activated, the boolean value can be included to utilize the new functionality, and legacy tombstones can be reclaimed.

Alternatives Considered

N/A

Impact

This proposal removes a harmful default behavior that has caused repeated loss of funds and persistent state bloat, while preserving security guarantees for users who explicitly wish to permanently disable a program ID.

Security Considerations

The program cache relies on two invariants:

  1. One redeployment per slot: The cache keys on program address and deployment slot. Multiple deployments to the same address in one slot would corrupt the cache.

  2. Loader stability within a transaction: A program's loader determines its ABI and alignment requirements. Changing loaders mid-transaction would cause CPI mismatches.

This proposal preserves both invariants:

  • Program account: When closing without tombstoning, the account is drained of lamports rather than reassigned to System. The account remains owned by Loader v3 until garbage-collected at transaction end, preventing same-TX redeployment. Additionally, closing without tombstoning is rejected if the program was deployed in the current slot, preventing multiple-TX loops.

  • Programdata account: Fully deallocated and reassigned to System. This is safe because programdata is not used for cache indexing or invocation.

  • Tombstone: When tombstoning, the program account is assigned to itself, permanently locking the address. Self-owned accounts cannot be modified.

Backwards Compatibility

This change modifies the semantics of an existing Loader v3 instruction and therefore requires a feature gate for consensus safety.

From a tooling perspective, the change is backwards compatible, though tooling updates are required to access the new explicit tombstoning behavior.

Evidence graph

Related proposals and rollout

One or more sources are unavailable.

Upstream review record

Upstream discussion & review

GitHub review is editorial context, not evidence of on-chain support, voting, or outcome.

PR #432 · merged SIMD-0432: Loader V3: Reclaim Closed Program 33 comments and reviews · Apr 21, 2026
@deanmlittle

imo, if you do the cost-benefit analysis on this, tombstoning programs has resulted in millions of dollars of funds being permanently locked and was a terrible choice. it really failed to protect anyone from anything and caused a whole lot of harm. my $0.02: "oh no, several cases of defi protocols and token sales with millions of dollars locked forever by our CLI having the worst UX imaginable can suddenly be recovered, a bunch of people can get their money back, a bunch of accounts might get cleaned up and people who throw their keypairs out into the wild learn why that's a bad idea. how will i ever live with the weight of this decision? 🤣 "

GitHub ↗
@t-nelson

why has the cli not been made more intuitive in the meantime?

GitHub ↗
@Lichtso

https://github.com/solana-labs/solana/pull/27108 I agree with @deanmlittle that the lost and revealed keys argument is negligible. Especially since the current status is that they don't have access to these funds, thus nothing changes in these cases.

GitHub ↗
@buffalojoec

Sure, but the motivation stands. Now this action is reversible.

GitHub ↗
@simd-botbot

✅ All approvals received! @buffalojoec, you can now merge this by commenting /merge. ✅ Status: Ready to merge

GitHub ↗
@simd-botbot

✅ Merge successful! buffalojoec(https://github.com/buffalojoec)'s PR has been merged.

GitHub ↗
@bart1e

I know that this PR has already been closed, but I believe that this SIMD may break the Close instruction when invoked via CPI (DAO or multisig cases). The problem is that executable accounts are not synchronized on CPI return and I'm providing more details below (the analysis below is relevant to the Agave validator). The 13th step ("Clear the program account's data and resize to zero.") unconditionally modifies the program account by setting its data size to 0, which means that for each Close invocation, program account is modified. The issue is that during CPI calls, executable accounts are skipped entirely in translateaccountscommon(https://github.com/anza-xyz/agave/blob/091099f16a/progr

GitHub ↗

simd.watch community discussion · SIMD-0432

Powered by Giscus · Sign in with GitHub to comment

Community comments load when this section approaches the viewport.

Provenance

Evidence & technical details

Rollout or chain data, source revisions, freshness and integrity.
2

SIMD

Deployment Status

Document lifecycleReview
CategoryCore protocol

Feature Gate not yet created

Exact source revision

Sources & integrity

  • Proposal document pinned_commit_blob
    eac20f2994dd064482d96f178a4bbc0f4e358fd0
  • simd-document document · stale · Oct 26, 2026
    eac20f2994dd064482d96f178a4bbc0f4e358fd0

One or more sources are unavailable.