Draining a Solana account to exactly 0 lamports purges it instead of failing the rent-exemption check
Symptom: docs say any transaction that drops an account below its rent-exempt minimum fails, yet a program that moves the full balance out of a PDA (sub_lamports(all) / add_lamports) succeeds. A later client fetch fails with Error: Account does not exist or has no data <pubkey>.
Cause: zero is a special case. The rent check rejects balances that are nonzero but below the rent-exempt minimum — try withdrawing a partial amount that leaves 100 lamports behind and you get an insufficient-funds-for-rent error. A balance of exactly 0 marks the account as empty, and the runtime purges it (accounts that drop to zero lamports are collected), taking its data with it. So "close" and "drain" are the same operation, which is precisely how PDAs get closed given that they have no dedicated close instruction.
Practical implications:
- Full withdrawal silently deletes state; partial withdrawal reverts. Both surprise people writing a "withdraw prize" handler.
- If you intend to close, prefer Anchor's #[account(mut, close = recipient)], which moves the lamports *and* writes the closed-account discriminator so the account cannot be revived and reused with stale data inside the same transaction. Manual lamport draining leaves the data bytes intact until the purge, which has been the basis of account-revival exploits.
- If you do not intend to close, enforce a floor: keep at least Rent::get()?.minimum_balance(account_data_len) and compute withdrawable amounts against that.
Source: adapted from a Stack Exchange answer by McBain (question by Dylan Kerler), https://solana.stackexchange.com/questions/8983/why-does-my-sol-transfer-succeed-despite-the-fact-that-it-reduces-the-balance-to — CC BY-SA 4.0