Skip to content

Commit

Permalink
lang: Add Lamports trait (coral-xyz#2552)
Browse files Browse the repository at this point in the history
  • Loading branch information
acheroncrypto committed Jul 1, 2023
1 parent 5624bfe commit e55cd3e
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 17 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The minor version will be incremented upon a breaking change and the patch versi

### Features

- lang: Add `get_lamports`, `add_lamports` and `sub_lamports` methods for all account types ([#2552](https://github.com/coral-xyz/anchor/pull/2552)).

### Fixes

- ts: Packages no longer depend on `assert` ([#2535](https://github.com/coral-xyz/anchor/pull/2535)).
Expand Down
49 changes: 47 additions & 2 deletions lang/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,51 @@ where
}
}

/// Lamports related utility methods for accounts.
pub trait Lamports<'info>: AsRef<AccountInfo<'info>> {
/// Get the lamports of the account.
fn get_lamports(&self) -> u64 {
self.as_ref().lamports()
}

/// Add lamports to the account.
///
/// This method is useful for transfering lamports from a PDA.
///
/// # 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::sub_lamports`] for subtracting lamports.
fn add_lamports(&self, amount: u64) -> Result<&Self> {
**self.as_ref().try_borrow_mut_lamports()? += amount;
Ok(self)
}

/// Subtract lamports from the account.
///
/// This method is useful for transfering lamports from a PDA.
///
/// # 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.
fn sub_lamports(&self, amount: u64) -> Result<&Self> {
**self.as_ref().try_borrow_mut_lamports()? -= amount;
Ok(self)
}
}

impl<'info, T: AsRef<AccountInfo<'info>>> Lamports<'info> for T {}

/// A data structure that can be serialized and stored into account storage,
/// i.e. an
/// [`AccountInfo`](../solana_program/account_info/struct.AccountInfo.html#structfield.data)'s
Expand Down Expand Up @@ -300,8 +345,8 @@ pub mod prelude {
require, require_eq, require_gt, require_gte, require_keys_eq, require_keys_neq,
require_neq, solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, source,
system_program::System, zero_copy, AccountDeserialize, AccountSerialize, Accounts,
AccountsClose, AccountsExit, AnchorDeserialize, AnchorSerialize, Id, InitSpace, Key, Owner,
ProgramData, Result, Space, ToAccountInfo, ToAccountInfos, ToAccountMetas,
AccountsClose, AccountsExit, AnchorDeserialize, AnchorSerialize, Id, InitSpace, Key,
Lamports, Owner, ProgramData, Result, Space, ToAccountInfo, ToAccountInfos, ToAccountMetas,
};
#[cfg(feature = "event-cpi")]
pub use super::{emit_cpi, event_cpi};
Expand Down
5 changes: 3 additions & 2 deletions tests/misc/Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ cluster = "localnet"
wallet = "~/.config/solana/id.json"

[programs.localnet]
misc = "3TEqcc8xhrhdspwbvoamUJe2borm4Nr72JxL66k6rgrh"
misc_optional = "FNqz6pqLAwvMSds2FYjR4nKV3moVpPNtvkfGFrqLKrgG"
idl_doc = "BqmKjZGVa8fqyWuojJzG16zaKSV1GjAisZToNuvEaz6m"
init_if_needed = "BZoppwWi6jMnydnUBEJzotgEXHwLr3b3NramJgZtWeF2"
lamports = "Lamports11111111111111111111111111111111111"
misc = "3TEqcc8xhrhdspwbvoamUJe2borm4Nr72JxL66k6rgrh"
misc_optional = "FNqz6pqLAwvMSds2FYjR4nKV3moVpPNtvkfGFrqLKrgG"

[workspace]
exclude = ["programs/shared"]
15 changes: 15 additions & 0 deletions tests/misc/programs/lamports/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "lamports"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]

[features]
no-entrypoint = []
cpi = ["no-entrypoint"]

[dependencies]
anchor-lang = { path = "../../../../lang" }
2 changes: 2 additions & 0 deletions tests/misc/programs/lamports/Xargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []
75 changes: 75 additions & 0 deletions tests/misc/programs/lamports/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use anchor_lang::prelude::*;

declare_id!("Lamports11111111111111111111111111111111111");

#[program]
pub mod lamports {
use super::*;

pub fn test_lamports_trait(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(amount)?;
signer.add_lamports(amount)?;

// 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)]
pub struct TestLamportsTrait<'info> {
#[account(mut)]
pub signer: Signer<'info>,

#[account(
init,
payer = signer,
space = 8,
seeds = [b"lamports"],
bump
)]
pub pda: Account<'info, LamportsPda>,

pub system_program: Program<'info, System>,
}

#[account]
pub struct LamportsPda {}
2 changes: 2 additions & 0 deletions tests/misc/tests/lamports/Test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[scripts]
test = "yarn run ts-mocha -t 1000000 ./tests/lamports/*.ts"
23 changes: 23 additions & 0 deletions tests/misc/tests/lamports/lamports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as anchor from "@coral-xyz/anchor";

import { Lamports, IDL } from "../../target/types/lamports";

describe(IDL.name, () => {
// Configure the client to use the local cluster
anchor.setProvider(anchor.AnchorProvider.env());

const program = anchor.workspace.Lamports as anchor.Program<Lamports>;

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

await program.methods
.testLamportsTrait(new anchor.BN(anchor.web3.LAMPORTS_PER_SOL))
.accounts({ signer, pda })
.rpc();
});
});
19 changes: 9 additions & 10 deletions tests/misc/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{
"compilerOptions": {
"types": ["mocha", "chai"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true,
"skipLibCheck": true
},
}
"compilerOptions": {
"types": ["mocha", "node"],
"lib": ["ES6"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true,
"skipLibCheck": true
}
}
3 changes: 0 additions & 3 deletions tests/zero-copy/rust-toolchain.toml

This file was deleted.

0 comments on commit e55cd3e

Please sign in to comment.