Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions basics/transfer-sol/anchor/programs/transfer-sol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,36 @@ pub mod transfer_sol {
ctx: Context<TransferSolWithProgram>,
amount: u64,
) -> Result<()> {
**ctx.accounts.payer.try_borrow_mut_lamports()? -= amount;
**ctx.accounts.recipient.try_borrow_mut_lamports()? += amount;
// Security invariants:
// - The source account must authorize the transfer (is_signer)
// - The source account must be owned by this program (direct lamports mutation)
// - Prevent under/overflow on lamport arithmetic

let payer_lamports = ctx.accounts.payer.to_account_info().lamports();
require!(payer_lamports >= amount, TransferSolError::InsufficientFunds);

**ctx.accounts.payer.try_borrow_mut_lamports()? = payer_lamports
.checked_sub(amount)
.ok_or(TransferSolError::LamportArithmeticOverflow)?;
Comment on lines +35 to +40
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New failure modes are introduced here (missing payer signature, insufficient funds, arithmetic error), but the test suite only covers the success path. Add negative tests that (1) omit .signers([payerAccount]) and assert the instruction fails due to missing signature, and (2) attempt to transfer more than the payer balance and assert InsufficientFunds is returned (and balances remain unchanged).

Copilot uses AI. Check for mistakes.

let recipient_lamports = ctx.accounts.recipient.to_account_info().lamports();
**ctx.accounts.recipient.try_borrow_mut_lamports()? = recipient_lamports
.checked_add(amount)
.ok_or(TransferSolError::LamportArithmeticOverflow)?;

Ok(())
}
}

#[error_code]
pub enum TransferSolError {
#[msg("Insufficient funds in payer account")]
InsufficientFunds,

#[msg("Lamport arithmetic overflow/underflow")]
LamportArithmeticOverflow,
}

#[derive(Accounts)]
pub struct TransferSolWithCpi<'info> {
#[account(mut)]
Expand All @@ -44,12 +68,14 @@ pub struct TransferSolWithCpi<'info> {

#[derive(Accounts)]
pub struct TransferSolWithProgram<'info> {
/// CHECK: Use owner constraint to check account is owned by our program
// NOTE: This account must sign the transaction, otherwise *anyone* could drain lamports
// from program-owned accounts passed into this instruction.
#[account(
mut,
owner = id() // value of declare_id!()
)]
payer: UncheckedAccount<'info>,
payer: Signer<'info>,

#[account(mut)]
recipient: SystemAccount<'info>,
}
1 change: 1 addition & 0 deletions basics/transfer-sol/anchor/tests/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe("Anchor: Transfer SOL", () => {
payer: payerAccount.publicKey,
recipient: recipientAccount.publicKey,
})
.signers([payerAccount])
.rpc();

const recipientBalance = await provider.connection.getBalance(
Expand Down
Loading