lang: Add `Lamports` trait (#2552)

This commit is contained in:
acheron 2023-07-01 22:59:36 +02:00 committed by GitHub
parent 5624bfe0ff
commit e55cd3e646
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 178 additions and 17 deletions

View File

@ -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)).

View File

@ -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
@ -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};

View File

@ -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"]

View File

@ -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" }

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -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 {}

View File

@ -0,0 +1,2 @@
[scripts]
test = "yarn run ts-mocha -t 1000000 ./tests/lamports/*.ts"

View File

@ -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();
});
});

View File

@ -1,11 +1,10 @@
{
"compilerOptions": {
"types": ["mocha", "chai"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"types": ["mocha", "node"],
"lib": ["ES6"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true,
"skipLibCheck": true
},
}
}

View File

@ -1,3 +0,0 @@
# TODO: Remove when `cargo-test-sbf` works with stable Rust
[toolchain]
channel = "1.66.1-x86_64-unknown-linux-gnu"