Lamport Underflow Remediation
How to fix lamport transfers that may exceed available balance or violate rent-exempt minimums.
Lamport Underflow Remediation
Overview
Related Detector: Lamport Underflow
Lamport transfers that exceed an account’s balance cause transaction failures, accidental account closure, or rent-exempt violations. The fix is to validate cumulative transfer amounts against the source balance, use checked arithmetic for amount calculations, and reserve the rent-exempt minimum when the account must remain active.
Recommended Fix
Before (Vulnerable)
pub fn withdraw(
accounts: &[AccountInfo],
amount: u64,
fee: u64,
) -> ProgramResult {
// VULNERABLE: unchecked arithmetic + no balance validation
let total = amount + fee;
**accounts[0].try_borrow_mut_lamports()? -= total;
**accounts[1].try_borrow_mut_lamports()? += amount;
**accounts[2].try_borrow_mut_lamports()? += fee;
Ok(())
}
After (Fixed)
pub fn withdraw(
accounts: &[AccountInfo],
amount: u64,
fee: u64,
) -> ProgramResult {
// FIXED: checked arithmetic
let total = amount
.checked_add(fee)
.ok_or(ProgramError::ArithmeticOverflow)?;
// FIXED: balance validation with rent-exempt reserve
let rent = Rent::get()?;
let min_balance = rent.minimum_balance(accounts[0].data_len());
let available = (**accounts[0].try_borrow_lamports()?)
.checked_sub(min_balance)
.ok_or(ProgramError::InsufficientFunds)?;
if available < total {
return Err(ProgramError::InsufficientFunds);
}
**accounts[0].try_borrow_mut_lamports()? -= total;
**accounts[1].try_borrow_mut_lamports()? += amount;
**accounts[2].try_borrow_mut_lamports()? += fee;
Ok(())
}
Alternative Mitigations
1. Batch validation for multiple transfers
pub fn multi_transfer(
source: &AccountInfo,
destinations: &[(AccountInfo, u64)],
) -> ProgramResult {
// Calculate total needed upfront
let total: u64 = destinations.iter()
.try_fold(0u64, |acc, (_, amount)| {
acc.checked_add(*amount)
})
.ok_or(ProgramError::ArithmeticOverflow)?;
// Single balance check for cumulative amount
let balance = **source.try_borrow_lamports()?;
if balance < total {
return Err(ProgramError::InsufficientFunds);
}
// Execute transfers
for (dest, amount) in destinations {
**source.try_borrow_mut_lamports()? -= amount;
**dest.try_borrow_mut_lamports()? += amount;
}
Ok(())
}
2. Safe account closure pattern
When intentionally closing an account, transfer remaining lamports and zero the data:
pub fn close_account(
source: &AccountInfo,
destination: &AccountInfo,
) -> ProgramResult {
// Transfer all lamports to destination
let lamports = **source.try_borrow_lamports()?;
**source.try_borrow_mut_lamports()? = 0;
**destination.try_borrow_mut_lamports()? += lamports;
// Zero account data to prevent resurrection attacks
let mut data = source.try_borrow_mut_data()?;
data.fill(0);
Ok(())
}
3. Rent-aware helper
fn transferable_balance(account: &AccountInfo) -> Result<u64, ProgramError> {
let rent = Rent::get()?;
let min_balance = rent.minimum_balance(account.data_len());
let balance = **account.try_borrow_lamports()?;
balance
.checked_sub(min_balance)
.ok_or(ProgramError::InsufficientFunds)
}
Common Mistakes
Mistake 1: Checking balance after transfer
// WRONG: underflow already occurred
**source.try_borrow_mut_lamports()? -= amount;
if **source.try_borrow_lamports()? < min_balance {
// Too late -- lamports already deducted
}
Always check before the transfer, not after.
Mistake 2: Not accounting for rent-exempt minimum
// WRONG: checking raw balance without rent reserve
let balance = **source.try_borrow_lamports()?;
if balance >= amount {
**source.try_borrow_mut_lamports()? -= amount;
// Account may now be below rent-exempt minimum!
}
Subtract the rent-exempt minimum from the available balance before comparing.
Mistake 3: Checking individual transfers but not cumulative
let balance = **source.try_borrow_lamports()?;
// WRONG: each check uses the original balance, not the remaining
if balance >= amount_a { transfer_a()?; }
if balance >= amount_b { transfer_b()?; }
// Combined amount_a + amount_b may exceed balance
Track the running balance or calculate the cumulative total upfront.