Skip to content

Commit

Permalink
lang: add checked operations for lamport management
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinrodriguez-io committed Jul 9, 2023
1 parent 6ef6b79 commit bbc2af6
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The minor version will be incremented upon a breaking change and the patch versi

### Features

- lang: Add `add_lamports_checked` and `sub_lamports_checked` methods for all account types ([#2563](https://github.com/coral-xyz/anchor/pull/2563)).
- lang: Add `get_lamports`, `add_lamports` and `sub_lamports` methods for all account types ([#2552](https://github.com/coral-xyz/anchor/pull/2552)).
- client: Add a helper struct `DynSigner` to simplify use of `Client<C> where <C: Clone + Deref<Target = impl Signer>>` with Solana clap CLI utils that loads `Signer` as `Box<dyn Signer>` ([#2550](https://github.com/coral-xyz/anchor/pull/2550)).
- lang: Allow CPI calls matching an interface without pinning program ID ([#2559](https://github.com/coral-xyz/anchor/pull/2559)).
Expand Down
51 changes: 49 additions & 2 deletions lang/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,35 @@ pub trait Lamports<'info>: AsRef<AccountInfo<'info>> {
/// the transaction.
/// 3. `lamports` field of the account info should not currently be borrowed.
///
/// See [`Lamports::sub_lamports`] for subtracting lamports.
/// - See [`Lamports::add_lamports_checked`] for adding lamports with overflow checking.
/// - See [`Lamports::sub_lamports`] for subtracting lamports.
/// - See [`Lamports::sub_lamports_checked`] for subtracting lamports with overflow checking.
fn add_lamports(&self, amount: u64) -> Result<&Self> {
**self.as_ref().try_borrow_mut_lamports()? += amount;
Ok(self)
}

/// Add lamports to the account, checking for overflow.
///
/// This method is useful for transfering lamports from a PDA.
/// Also raises an error if the operation overflows.
///
/// # Requirements
///
/// 1. The account must be marked `mut`.
/// 2. The total lamports **before** the transaction must equal to total lamports **after**
/// the transaction.
/// 3. `lamports` field of the account info should not currently be borrowed.
///
/// - See [`Lamports::add_lamports`] for adding lamports.
/// - See [`Lamports::sub_lamports`] for subtracting lamports.
/// - See [`Lamports::sub_lamports_checked`] for subtracting lamports with overflow checking.
fn add_lamports_checked(&self, amount: u64, error: error::Error) -> Result<&Self> {
let result = self.get_lamports().checked_add(amount).ok_or(error)?;
**self.as_ref().try_borrow_mut_lamports()? = result;
Ok(self)
}

/// Subtract lamports from the account.
///
/// This method is useful for transfering lamports from a PDA.
Expand All @@ -178,11 +201,35 @@ pub trait Lamports<'info>: AsRef<AccountInfo<'info>> {
/// the transaction.
/// 4. `lamports` field of the account info should not currently be borrowed.
///
/// See [`Lamports::add_lamports`] for adding lamports.
/// - See [`Lamports::add_lamports`] for adding lamports.
/// - See [`Lamports::add_lamports_checked`] for adding lamports with overflow checking.
/// - See [`Lamports::sub_lamports_checked`] for subtracting lamports with overflow checking.
fn sub_lamports(&self, amount: u64) -> Result<&Self> {
**self.as_ref().try_borrow_mut_lamports()? -= amount;
Ok(self)
}

/// Subtract lamports from the account, checking for overflow.
///
/// This method is useful for transfering lamports from a PDA.
/// Also raises an error if the operation overflows.
///
/// # Requirements
///
/// 1. The account must be owned by the executing program.
/// 2. The account must be marked `mut`.
/// 3. The total lamports **before** the transaction must equal to total lamports **after**
/// the transaction.
/// 4. `lamports` field of the account info should not currently be borrowed.
///
/// - See [`Lamports::add_lamports`] for adding lamports.
/// - See [`Lamports::add_lamports_checked`] for adding lamports with overflow checking.
/// - See [`Lamports::sub_lamports`] for subtracting lamports.
fn sub_lamports_checked(&self, amount: u64, error: error::Error) -> Result<&Self> {
let result = self.get_lamports().checked_sub(amount).ok_or(error)?;
**self.as_ref().try_borrow_mut_lamports()? = result;
Ok(self)
}
}

impl<'info, T: AsRef<AccountInfo<'info>>> Lamports<'info> for T {}
Expand Down
53 changes: 53 additions & 0 deletions tests/misc/programs/lamports/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,53 @@ pub mod lamports {

Ok(())
}

pub fn test_lamports_trait_checked(ctx: Context<TestLamportsTrait>, amount: u64) -> Result<()> {
let pda = &ctx.accounts.pda;
let signer = &ctx.accounts.signer;

// Transfer **to** PDA
{
// Get the balance of the PDA **before** the transfer to PDA
let pda_balance_before = pda.get_lamports();

// Transfer to the PDA
anchor_lang::system_program::transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
anchor_lang::system_program::Transfer {
from: signer.to_account_info(),
to: pda.to_account_info(),
},
),
amount,
)?;

// Get the balance of the PDA **after** the transfer to PDA
let pda_balance_after = pda.get_lamports();

// Validate balance
require_eq!(pda_balance_after, pda_balance_before + amount);
}

// Transfer **from** PDA
{
// Get the balance of the PDA **before** the transfer from PDA
let pda_balance_before = pda.get_lamports();

// Transfer from the PDA
pda.sub_lamports_checked(amount, LamportsError::NumericOverflow)?;
signer.add_lamports_checked(amount, LamportsError::NumericOverflow)?;

// Get the balance of the PDA **after** the transfer from PDA
let pda_balance_after = pda.get_lamports();

// Validate balance
require_eq!(pda_balance_after, pda_balance_before - amount);
}

Ok(())
}
}

#[derive(Accounts)]
Expand All @@ -73,3 +120,9 @@ pub struct TestLamportsTrait<'info> {

#[account]
pub struct LamportsPda {}

#[error_code]
pub enum LamportsError {
#[msg("Numeric Overflow.")]
NumericOverflow,
}
20 changes: 20 additions & 0 deletions tests/misc/tests/lamports/lamports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,24 @@ describe(IDL.name, () => {
.accounts({ signer, pda })
.rpc();
});

it("Can use the Lamports trait for checked arithmetic", async () => {
const signer = program.provider.publicKey!;
const [pda] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("lamports")],
program.programId
);

await program.methods
.testLamportsTraitChecked(new anchor.BN(anchor.web3.LAMPORTS_PER_SOL))
.accounts({ signer, pda })
.rpc();
});

it("Throws when overflow occurs", async () => {
const signer = program.provider.publicKey!;
const [pda] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("lamports")],
program.programId
);
});

0 comments on commit bbc2af6

Please sign in to comment.