分类 Solana 下的文章

Solana基础 - 如何在程序中读取帐户


Solana 中的几乎所有指令都需要至少 2 - 3 个帐户,并且它们
会在指令处理程序中提及,说明这些帐户集的预期顺序。如果我们利用iter() Rust 中的方法,而不是手动索引帐户,那么这相当简单。该 next_account_info方法基本上对可迭代对象的第一个索引进行切片,并返回帐户数组中存在的帐户。让我们看一个简单的指令,它需要一堆帐户并需要解析每个帐户。

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    pubkey::Pubkey,
};

entrypoint!(process_instruction);

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct HelloState {
    is_initialized: bool,
}

// Accounts required
/// 1. [signer] Payer
/// 2. [writable] Hello state account
/// 3. [] Rent account
/// 4. [] System Program
pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    // Fetching all the accounts as a iterator (facilitating for loops and iterations)
    let accounts_iter = &mut accounts.iter();
    // Payer account
    let payer_account = next_account_info(accounts_iter)?;
    // Hello state account
    let hello_state_account = next_account_info(accounts_iter)?;
    // Rent account
    let rent_account = next_account_info(accounts_iter)?;
    // System Program
    let system_program = next_account_info(accounts_iter)?;

    Ok(())
}

https://solana.com/zh/developers/cookbook/programs/read-accounts


Solana基础 - 如何创建程序派生地址


程序派生地址只是程序拥有的一个帐户,但没有私钥。相反,它的签名是通过一组种子和一个碰撞(一个
随机数,确保它“偏离曲线”,即不是有效的公钥)获得的。“生成”程序地址
不同于“创建”它。可以使用生成 PDA Pubkey::find_program_address。创建 PDA 本质上意味着用空间初始化地址并将状态设置为它。可以在我们的程序之外创建一个普通的密钥对帐户,然后输入以初始化它的状态。不幸的是,对于 PDA,它必须在链上创建,因为它不能代表自己签名。因此,我们使用invoke_signed传递 PDA 的种子,以及资金账户的签名,从而创建 PDA 账户。

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    program::invoke_signed,
    program_error::ProgramError,
    pubkey::Pubkey,
    rent::Rent,
    system_instruction,
    sysvar::Sysvar,
};

entrypoint!(process_instruction);

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct HelloState {
    is_initialized: bool,
}

// Accounts required
/// 1. [signer, writable] Funding account
/// 2. [writable] PDA account
/// 3. [] System Program
pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    const ACCOUNT_DATA_LEN: usize = 1;

    let accounts_iter = &mut accounts.iter();
    // Getting required accounts
    let funding_account = next_account_info(accounts_iter)?;
    let pda_account = next_account_info(accounts_iter)?;
    let system_program = next_account_info(accounts_iter)?;

    // Getting PDA Bump from instruction data
    let (pda_bump, _) = instruction_data
        .split_first()
        .ok_or(ProgramError::InvalidInstructionData)?;

    // Checking if passed PDA and expected PDA are equal
    let signers_seeds: &[&[u8]; 3] = &[
        b"customaddress",
        &funding_account.key.to_bytes(),
        &[*pda_bump],
    ];
    let pda = Pubkey::create_program_address(signers_seeds, program_id)?;

    if pda.ne(&pda_account.key) {
        return Err(ProgramError::InvalidAccountData);
    }

    // Assessing required lamports and creating transaction instruction
    let lamports_required = Rent::get()?.minimum_balance(ACCOUNT_DATA_LEN);
    let create_pda_account_ix = system_instruction::create_account(
        &funding_account.key,
        &pda_account.key,
        lamports_required,
        ACCOUNT_DATA_LEN.try_into().unwrap(),
        &program_id,
    );
    // Invoking the instruction but with PDAs as additional signer
    invoke_signed(
        &create_pda_account_ix,
        &[
            funding_account.clone(),
            pda_account.clone(),
            system_program.clone(),
        ],
        &[signers_seeds],
    )?;

    // Setting state for PDA
    let mut pda_account_state = HelloState::try_from_slice(&pda_account.data.borrow())?;
    pda_account_state.is_initialized = true;
    pda_account_state.serialize(&mut &mut pda_account.data.borrow_mut()[..])?;

    Ok(())
}

可以通过客户端发送所需的帐户,如下所示

import {
  clusterApiUrl,
  Connection,
  Keypair,
  LAMPORTS_PER_SOL,
  PublicKey,
  sendAndConfirmTransaction,
  SystemProgram,
  Transaction,
  TransactionInstruction,
} from "@solana/web3.js";

const PAYER_KEYPAIR = Keypair.generate();

(async () => {
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
  const latestBlockHash = await connection.getLatestBlockhash();
  const programId = new PublicKey(
    "6eW5nnSosr2LpkUGCdznsjRGDhVb26tLmiM1P8RV1QQp",
  );

  // Airdrop to Payer
  await connection.confirmTransaction(
    {
      blockhash: latestBlockHash.blockhash,
      lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
      signature: await connection.requestAirdrop(
        PAYER_KEYPAIR.publicKey,
        LAMPORTS_PER_SOL,
      ),
    },
    "confirmed",
  );

  const [pda, bump] = await PublicKey.findProgramAddress(
    [Buffer.from("customaddress"), PAYER_KEYPAIR.publicKey.toBuffer()],
    programId,
  );

  console.log(`PDA Pubkey: ${pda.toString()}`);

  const createPDAIx = new TransactionInstruction({
    programId: programId,
    data: Buffer.from(Uint8Array.of(bump)),
    keys: [
      {
        isSigner: true,
        isWritable: true,
        pubkey: PAYER_KEYPAIR.publicKey,
      },
      {
        isSigner: false,
        isWritable: true,
        pubkey: pda,
      },
      {
        isSigner: false,
        isWritable: false,
        pubkey: SystemProgram.programId,
      },
    ],
  });

  const transaction = new Transaction();
  transaction.add(createPDAIx);

  const txHash = await sendAndConfirmTransaction(connection, transaction, [
    PAYER_KEYPAIR,
  ]);
  console.log(`Created PDA successfully. Tx Hash: ${txHash}`);
})();

https://solana.com/zh/developers/cookbook/programs/create-pda


Solana基础 - 如何进行跨程序调用


跨程序调用,简单地说就是在我们的程序中调用另一个程序的指令。最好的例子就是 Uniswap 的swap 功能。UniswapV2Router合约调用交换所需的逻辑,并调用ERC20合约的转移函数从一个人转移到另一个人。同样,我们可以调用程序的指令来实现多种用途。
让我们看一下第一个示例,即 SPL Token Program's transfer指令。进行转账所需的账户是

  • 源代币账户(我们持有代币的账户)
  • 目标代币账户(我们将把代币转移到的账户)
  • 源代币账户的持有人(我们将要签名的钱包地址)
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program::invoke,
    program_error::ProgramError,
    pubkey::Pubkey,
};
use spl_token::instruction::transfer;

entrypoint!(process_instruction);

// Accounts required
/// 1. [writable] Source Token Account
/// 2. [writable] Destination Token Account
/// 3. [signer] Source Token Account holder's PubKey
/// 4. [] Token Program
pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let accounts_iter = &mut accounts.iter();

    // Accounts required for token transfer

    // 1. Token account we hold
    let source_token_account = next_account_info(accounts_iter)?;
    // 2. Token account to send to
    let destination_token_account = next_account_info(accounts_iter)?;
    // 3. Our wallet address
    let source_token_account_holder = next_account_info(accounts_iter)?;
    // 4. Token Program
    let token_program = next_account_info(accounts_iter)?;

    // Parsing the token transfer amount from instruction data
    // a. Getting the 0th to 8th index of the u8 byte array
    // b. Converting the obtained non zero u8 to a proper u8 (as little endian integers)
    // c. Converting the little endian integers to a u64 number
    let token_transfer_amount = instruction_data
        .get(..8)
        .and_then(|slice| slice.try_into().ok())
        .map(u64::from_le_bytes)
        .ok_or(ProgramError::InvalidAccountData)?;

    msg!(
        "Transferring {} tokens from {} to {}",
        token_transfer_amount,
        source_token_account.key.to_string(),
        destination_token_account.key.to_string()
    );

    // Creating a new TransactionInstruction
    /*
        Internal representation of the instruction's return value (Result<Instruction, ProgramError>)

        Ok(Instruction {
            program_id: *token_program_id, // PASSED FROM USER
            accounts,
            data,
        })
    */

    let transfer_tokens_instruction = transfer(
        &token_program.key,
        &source_token_account.key,
        &destination_token_account.key,
        &source_token_account_holder.key,
        &[&source_token_account_holder.key],
        token_transfer_amount,
    )?;

    let required_accounts_for_transfer = [
        source_token_account.clone(),
        destination_token_account.clone(),
        source_token_account_holder.clone(),
    ];

    // Passing the TransactionInstruction to send
    invoke(
        &transfer_tokens_instruction,
        &required_accounts_for_transfer,
    )?;

    msg!("Transfer successful");

    Ok(())
}

相应的客户端指令如下。有关铸造和代币创建的指令,请参阅附近的完整代码。

import {
  clusterApiUrl,
  Connection,
  Keypair,
  LAMPORTS_PER_SOL,
  PublicKey,
  SystemProgram,
  sendAndConfirmTransaction
  Transaction,
  TransactionInstruction,
} from "@solana/web3.js";
import {
  AccountLayout,
  MintLayout,
  Token,
  TOKEN_PROGRAM_ID,
  u64,
} from "@solana/spl-token";

import * as BN from "bn.js";

// Users
const PAYER_KEYPAIR = Keypair.generate();
const RECEIVER_KEYPAIR = Keypair.generate().publicKey;

// Mint and token accounts
const TOKEN_MINT_ACCOUNT = Keypair.generate();
const SOURCE_TOKEN_ACCOUNT = Keypair.generate();
const DESTINATION_TOKEN_ACCOUNT = Keypair.generate();

// Numbers
const DEFAULT_DECIMALS_COUNT = 9;
const TOKEN_TRANSFER_AMOUNT = 50 * 10 ** DEFAULT_DECIMALS_COUNT;
const TOKEN_TRANSFER_AMOUNT_BUFFER = Buffer.from(
  Uint8Array.of(...new BN(TOKEN_TRANSFER_AMOUNT).toArray("le", 8))
);

(async () => {
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
  const programId = new PublicKey(
    "EfYK91eN3AqTwY1C34W6a33qGAtQ8HJYVhNv7cV4uMZj"
  );

  const mintDataSpace = MintLayout.span;
  const mintRentRequired = await connection.getMinimumBalanceForRentExemption(
    mintDataSpace
  );

  const tokenDataSpace = AccountLayout.span;
  const tokenRentRequired = await connection.getMinimumBalanceForRentExemption(
    tokenDataSpace
  );

  // Airdropping 1 SOL
  const feePayer = Keypair.generate();
  await connection.confirmTransaction({
      blockhash: latestBlockHash.blockhash,
      lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
      signature: await connection.requestAirdrop(feePayer.publicKey, LAMPORTS_PER_SOL),
    },
    'confirmed',
  );


  // Allocating space and rent for mint account
  const createMintAccountIx = SystemProgram.createAccount({
    fromPubkey: PAYER_KEYPAIR.publicKey,
    lamports: mintRentRequired,
    newAccountPubkey: TOKEN_MINT_ACCOUNT.publicKey,
    programId: TOKEN_PROGRAM_ID,
    space: mintDataSpace,
  });

  // Initializing mint with decimals and authority
  const initializeMintIx = Token.createInitMintInstruction(
    TOKEN_PROGRAM_ID,
    TOKEN_MINT_ACCOUNT.publicKey,
    DEFAULT_DECIMALS_COUNT,
    PAYER_KEYPAIR.publicKey, // mintAuthority
    PAYER_KEYPAIR.publicKey // freezeAuthority
  );

  // Allocating space and rent for source token account
  const createSourceTokenAccountIx = SystemProgram.createAccount({
    fromPubkey: PAYER_KEYPAIR.publicKey,
    newAccountPubkey: SOURCE_TOKEN_ACCOUNT.publicKey,
    lamports: tokenRentRequired,
    programId: TOKEN_PROGRAM_ID,
    space: tokenDataSpace,
  });

  // Initializing token account with mint and owner
  const initializeSourceTokenAccountIx = Token.createInitAccountInstruction(
    TOKEN_PROGRAM_ID,
    TOKEN_MINT_ACCOUNT.publicKey,
    SOURCE_TOKEN_ACCOUNT.publicKey,
    PAYER_KEYPAIR.publicKey
  );

  // Minting tokens to the source token account for transferring later to destination account
  const mintTokensIx = Token.createMintToInstruction(
    TOKEN_PROGRAM_ID,
    TOKEN_MINT_ACCOUNT.publicKey,
    SOURCE_TOKEN_ACCOUNT.publicKey,
    PAYER_KEYPAIR.publicKey,
    [PAYER_KEYPAIR],
    TOKEN_TRANSFER_AMOUNT
  );

  // Allocating space and rent for destination token account
  const createDestinationTokenAccountIx = SystemProgram.createAccount({
    fromPubkey: PAYER_KEYPAIR.publicKey,
    newAccountPubkey: DESTINATION_TOKEN_ACCOUNT.publicKey,
    lamports: tokenRentRequired,
    programId: TOKEN_PROGRAM_ID,
    space: tokenDataSpace,
  });

  // Initializing token account with mint and owner
  const initializeDestinationTokenAccountIx =
    Token.createInitAccountInstruction(
      TOKEN_PROGRAM_ID,
      TOKEN_MINT_ACCOUNT.publicKey,
      DESTINATION_TOKEN_ACCOUNT.publicKey,
      RECEIVER_KEYPAIR
    );

  // Our program's CPI instruction (transfer)
  const transferTokensIx = new TransactionInstruction({
    programId: programId,
    data: TOKEN_TRANSFER_AMOUNT_BUFFER,
    keys: [
      {
        isSigner: false,
        isWritable: true,
        pubkey: SOURCE_TOKEN_ACCOUNT.publicKey,
      },
      {
        isSigner: false,
        isWritable: true,
        pubkey: DESTINATION_TOKEN_ACCOUNT.publicKey,
      },
      {
        isSigner: true,
        isWritable: true,
        pubkey: PAYER_KEYPAIR.publicKey,
      },
      {
        isSigner: false,
        isWritable: false,
        pubkey: TOKEN_PROGRAM_ID,
      },
    ],
  });

  const transaction = new Transaction();
  // Adding up all the above instructions
  transaction.add(
    createMintAccountIx,
    initializeMintIx,
    createSourceTokenAccountIx,
    initializeSourceTokenAccountIx,
    mintTokensIx,
    createDestinationTokenAccountIx,
    initializeDestinationTokenAccountIx,
    transferTokensIx
  );

  const txHash = await sendAndConfirmTransaction(connection, transaction, [
    PAYER_KEYPAIR,
    TOKEN_MINT_ACCOUNT,
    SOURCE_TOKEN_ACCOUNT,
    DESTINATION_TOKEN_ACCOUNT,
  ]);

  console.log(`Token transfer CPI success: ${txHash}`);
})();

现在让我们看另一个例子,即 System Program's create_account指令。上面提到的指令和这个指令之间有一点不同。在那里,我们从来不需要将token_program作为函数内部的一个帐户传递invoke。但是,也有例外,你需要传递调用指令的program_id。在我们的例子中,它将是System Program's program_id。(“1111111111111111111111111111111111111”)。所以现在所需的帐户将是

  1. 支付租金的付款人账户
  2. 即将创建的账户
  3. 系统程序帐户
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program::invoke,
    program_error::ProgramError,
    pubkey::Pubkey,
    rent::Rent,
    system_instruction::create_account,
    sysvar::Sysvar,
};

entrypoint!(process_instruction);

// Accounts required
/// 1. [signer, writable] Payer Account
/// 2. [signer, writable] General State Account
/// 3. [] System Program
pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let accounts_iter = &mut accounts.iter();

    // Accounts required for token transfer

    // 1. Payer account for the state account creation
    let payer_account = next_account_info(accounts_iter)?;
    // 2. Token account we hold
    let general_state_account = next_account_info(accounts_iter)?;
    // 3. System Program
    let system_program = next_account_info(accounts_iter)?;

    msg!(
        "Creating account for {}",
        general_state_account.key.to_string()
    );

    // Parsing the token transfer amount from instruction data
    // a. Getting the 0th to 8th index of the u8 byte array
    // b. Converting the obtained non zero u8 to a proper u8 (as little endian integers)
    // c. Converting the little endian integers to a u64 number
    let account_span = instruction_data
        .get(..8)
        .and_then(|slice| slice.try_into().ok())
        .map(u64::from_le_bytes)
        .ok_or(ProgramError::InvalidAccountData)?;

    let lamports_required = (Rent::get()?).minimum_balance(account_span as usize);

    // Creating a new TransactionInstruction
    /*
        Internal representation of the instruction's return value (Instruction)

        Instruction::new_with_bincode(
            system_program::id(), // NOT PASSED FROM USER
            &SystemInstruction::CreateAccount {
                lamports,
                space,
                owner: *owner,
            },
            account_metas,
        )
    */

    let create_account_instruction = create_account(
        &payer_account.key,
        &general_state_account.key,
        lamports_required,
        account_span,
        program_id,
    );

    let required_accounts_for_create = [
        payer_account.clone(),
        general_state_account.clone(),
        system_program.clone(),
    ];

    // Passing the TransactionInstruction to send (with the issued program_id)
    invoke(&create_account_instruction, &required_accounts_for_create)?;

    msg!("Transfer successful");

    Ok(())
}

相应的客户端代码如下所示

import { clusterApiUrl, Connection, Keypair } from "@solana/web3.js";
import { LAMPORTS_PER_SOL, PublicKey, SystemProgram } from "@solana/web3.js";
import {
  Transaction,
  TransactionInstruction,
  sendAndConfirmTransaction,
} from "@solana/web3.js";

import * as BN from "bn.js";

// Users
const PAYER_KEYPAIR = Keypair.generate();
const GENERAL_STATE_KEYPAIR = Keypair.generate();

const ACCOUNT_SPACE_BUFFER = Buffer.from(
  Uint8Array.of(...new BN(100).toArray("le", 8)),
);

(async () => {
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
  const latestBlockHash = await connection.getLatestBlockhash();
  const programId = new PublicKey(
    "DkuQ5wsndkzXfgqDB6Lgf4sDjBi4gkLSak1dM5Mn2RuQ",
  );

  // Airdropping 1 SOL
  const feePayer = Keypair.generate();
  await connection.confirmTransaction(
    {
      blockhash: latestBlockHash.blockhash,
      lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
      signature: await connection.requestAirdrop(
        feePayer.publicKey,
        LAMPORTS_PER_SOL,
      ),
    },
    "confirmed",
  );

  // Our program's CPI instruction (create_account)
  const createAccountIx = new TransactionInstruction({
    programId: programId,
    data: ACCOUNT_SPACE_BUFFER,
    keys: [
      {
        isSigner: true,
        isWritable: true,
        pubkey: PAYER_KEYPAIR.publicKey,
      },
      {
        isSigner: true,
        isWritable: true,
        pubkey: GENERAL_STATE_KEYPAIR.publicKey,
      },
      {
        isSigner: false,
        isWritable: false,
        pubkey: SystemProgram.programId,
      },
    ],
  });

  const transaction = new Transaction();
  // Adding up all the above instructions
  transaction.add(createAccountIx);

  const txHash = await sendAndConfirmTransaction(connection, transaction, [
    PAYER_KEYPAIR,
    GENERAL_STATE_KEYPAIR,
  ]);

  console.log(`Create Account CPI Success: ${txHash}`);
})();

https://solana.com/zh/developers/cookbook/programs/cross-program-invocation


Solana基础 - 如何更改账户规模


您可以使用 更改程序拥有的帐户的大小realloc。 realloc最多可以将帐户大小调整为 10KB。当您使用realloc增加帐户大小时,您必须转移lamports才能使该帐户免于租用。

use {
  crate::{
      instruction::WhitelistInstruction,
      state::WhiteListData,
  },
  borsh::{BorshDeserialize, BorshSerialize},
  solana_program::{
      account_info::{next_account_info, AccountInfo},
      entrypoint::ProgramResult,
      msg,
      program::invoke_signed,
      program::invoke,
      program_error::ProgramError,
      pubkey::Pubkey,
      sysvar::Sysvar,
      sysvar::rent::Rent,
      system_instruction,
  },
  std::convert::TryInto,
};

pub fn process_instruction(
  _program_id: &Pubkey,
  accounts: &[AccountInfo],
  input: &[u8],
) -> ProgramResult {
  // Length = BOOL + VEC + Pubkey * n (n = number of keys)
  const INITIAL_ACCOUNT_LEN: usize = 1 + 4 + 0 ;
  msg!("input: {:?}", input);

  let instruction = WhitelistInstruction::try_from_slice(input)?;

  let accounts_iter = &mut accounts.iter();

  let funding_account = next_account_info(accounts_iter)?;
  let pda_account = next_account_info(accounts_iter)?;
  let system_program = next_account_info(accounts_iter)?;

  match instruction {
    WhitelistInstruction::Initialize => {
      msg!("Initialize");

      let (pda, pda_bump) = Pubkey::find_program_address(
          &[
            b"customaddress",
            &funding_account.key.to_bytes(),
          ],
          _program_id,
      );

      let signers_seeds: &[&[u8]; 3] = &[
          b"customaddress",
          &funding_account.key.to_bytes(),
          &[pda_bump],
      ];

      if pda.ne(&pda_account.key) {
          return Err(ProgramError::InvalidAccountData);
      }

      let lamports_required = Rent::get()?.minimum_balance(INITIAL_ACCOUNT_LEN);
      let create_pda_account_ix = system_instruction::create_account(
          &funding_account.key,
          &pda_account.key,
          lamports_required,
          INITIAL_ACCOUNT_LEN.try_into().unwrap(),
          &_program_id,
      );

      invoke_signed(
          &create_pda_account_ix,
          &[
              funding_account.clone(),
              pda_account.clone(),
              system_program.clone(),
          ],
          &[signers_seeds],
      )?;

      let mut pda_account_state = WhiteListData::try_from_slice(&pda_account.data.borrow())?;

      pda_account_state.is_initialized = true;
      pda_account_state.white_list = Vec::new();
      pda_account_state.serialize(&mut &mut pda_account.data.borrow_mut()[..])?;
      Ok(())
    }
    WhitelistInstruction::AddKey { key } => {
      msg!("AddKey");

      let mut pda_account_state = WhiteListData::try_from_slice(&pda_account.data.borrow())?;

      if !pda_account_state.is_initialized {
          return Err(ProgramError::InvalidAccountData);
      }

      let new_size = pda_account.data.borrow().len() + 32;

      let rent = Rent::get()?;
      let new_minimum_balance = rent.minimum_balance(new_size);

      let lamports_diff = new_minimum_balance.saturating_sub(pda_account.lamports());
      invoke(
          &system_instruction::transfer(funding_account.key, pda_account.key, lamports_diff),
          &[
              funding_account.clone(),
              pda_account.clone(),
              system_program.clone(),
          ],
      )?;

      pda_account.realloc(new_size, false)?;

      pda_account_state.white_list.push(key);
      pda_account_state.serialize(&mut &mut pda_account.data.borrow_mut()[..])?;

      Ok(())
    }
  }
}

https://solana.com/zh/developers/cookbook/programs/change-account-size


Solana基础 - 如何在程序中获取时钟


获取时钟(即当前时间)可以通过两种方式完成:

  1. 传递SYSVAR_CLOCK_PUBKEY指令
  2. 在指令内部直接访问时钟。

了解这两种方法是很有好处的,因为一些遗留程序仍然需要将其SYSVAR_CLOCK_PUBKEY作为一个帐户。

在指令中将 Clock 作为账户传递

让我们创建一个接收初始化帐户和 sysvar 公钥的指令

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    clock::Clock,
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    pubkey::Pubkey,
    sysvar::Sysvar,
};

entrypoint!(process_instruction);

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct HelloState {
    is_initialized: bool,
}

// Accounts required
/// 1. [signer, writable] Payer
/// 2. [writable] Hello state account
/// 3. [] Clock sys var
pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    let accounts_iter = &mut accounts.iter();
    // Payer account
    let _payer_account = next_account_info(accounts_iter)?;
    // Hello state account
    let hello_state_account = next_account_info(accounts_iter)?;
    // Clock sysvar
    let sysvar_clock_pubkey = next_account_info(accounts_iter)?;

    let mut hello_state = HelloState::try_from_slice(&hello_state_account.data.borrow())?;
    hello_state.is_initialized = true;
    hello_state.serialize(&mut &mut hello_state_account.data.borrow_mut()[..])?;
    msg!("Account initialized :)");

    // Type casting [AccountInfo] to [Clock]
    let clock = Clock::from_account_info(&sysvar_clock_pubkey)?;
    // Getting timestamp
    let current_timestamp = clock.unix_timestamp;
    msg!("Current Timestamp: {}", current_timestamp);

    Ok(())
}

现在我们通过客户端传递时钟的 sysvar 公共地址

import {
  clusterApiUrl,
  Connection,
  Keypair,
  LAMPORTS_PER_SOL,
  PublicKey,
  SystemProgram,
  SYSVAR_CLOCK_PUBKEY,
  Transaction,
  TransactionInstruction,
  sendAndConfirmTransaction,
} from "@solana/web3.js";

(async () => {
  const programId = new PublicKey(
    "77ezihTV6mTh2Uf3ggwbYF2NyGJJ5HHah1GrdowWJVD3",
  );

  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
  const latestBlockHash = await connection.getLatestBlockhash();

  // Airdropping 1 SOL
  const feePayer = Keypair.generate();
  await connection.confirmTransaction(
    {
      blockhash: latestBlockHash.blockhash,
      lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
      signature: await connection.requestAirdrop(
        feePayer.publicKey,
        LAMPORTS_PER_SOL,
      ),
    },
    "confirmed",
  );

  // Hello state account
  const helloAccount = Keypair.generate();

  const accountSpace = 1; // because there exists just one boolean variable
  const rentRequired =
    await connection.getMinimumBalanceForRentExemption(accountSpace);

  // Allocating space for hello state account
  const allocateHelloAccountIx = SystemProgram.createAccount({
    fromPubkey: feePayer.publicKey,
    lamports: rentRequired,
    newAccountPubkey: helloAccount.publicKey,
    programId: programId,
    space: accountSpace,
  });

  // Passing Clock Sys Var
  const passClockIx = new TransactionInstruction({
    programId: programId,
    keys: [
      {
        isSigner: true,
        isWritable: true,
        pubkey: feePayer.publicKey,
      },
      {
        isSigner: false,
        isWritable: true,
        pubkey: helloAccount.publicKey,
      },
      {
        isSigner: false,
        isWritable: false,
        pubkey: SYSVAR_CLOCK_PUBKEY,
      },
    ],
  });

  const transaction = new Transaction();
  transaction.add(allocateHelloAccountIx, passClockIx);

  const txHash = await sendAndConfirmTransaction(connection, transaction, [
    feePayer,
    helloAccount,
  ]);

  console.log(`Transaction succeeded. TxHash: ${txHash}`);
})();

直接在指令内部访问时钟

让我们创建相同的指令,但不期望 SYSVAR_CLOCK_PUBKEY来自客户端。

use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    clock::Clock,
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    pubkey::Pubkey,
    sysvar::Sysvar,
};

entrypoint!(process_instruction);

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct HelloState {
    is_initialized: bool,
}

// Accounts required
/// 1. [signer, writable] Payer
/// 2. [writable] Hello state account
pub fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    let accounts_iter = &mut accounts.iter();
    // Payer account
    let _payer_account = next_account_info(accounts_iter)?;
    // Hello state account
    let hello_state_account = next_account_info(accounts_iter)?;

    // Getting clock directly
    let clock = Clock::get()?;

    let mut hello_state = HelloState::try_from_slice(&hello_state_account.data.borrow())?;
    hello_state.is_initialized = true;
    hello_state.serialize(&mut &mut hello_state_account.data.borrow_mut()[..])?;
    msg!("Account initialized :)");

    // Getting timestamp
    let current_timestamp = clock.unix_timestamp;
    msg!("Current Timestamp: {}", current_timestamp);

    Ok(())
}

客户端指令现在只需要传递州和付款人账户。

import {
  clusterApiUrl,
  Connection,
  Keypair,
  LAMPORTS_PER_SOL,
  PublicKey,
  SystemProgram,
  Transaction,
  TransactionInstruction,
} from "@solana/web3.js";

(async () => {
  const programId = new PublicKey(
    "4ZEdbCtb5UyCSiAMHV5eSHfyjq3QwbG3yXb6oHD7RYjk",
  );

  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
  const latestBlockHash = await connection.getLatestBlockhash();

  // Airdropping 1 SOL
  const feePayer = Keypair.generate();
  await connection.confirmTransaction(
    {
      blockhash: latestBlockHash.blockhash,
      lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
      signature: await connection.requestAirdrop(
        feePayer.publicKey,
        LAMPORTS_PER_SOL,
      ),
    },
    "confirmed",
  );

  // Hello state account
  const helloAccount = Keypair.generate();

  const accountSpace = 1; // because there exists just one boolean variable
  const rentRequired =
    await connection.getMinimumBalanceForRentExemption(accountSpace);

  // Allocating space for hello state account
  const allocateHelloAccountIx = SystemProgram.createAccount({
    fromPubkey: feePayer.publicKey,
    lamports: rentRequired,
    newAccountPubkey: helloAccount.publicKey,
    programId: programId,
    space: accountSpace,
  });

  const initIx = new TransactionInstruction({
    programId: programId,
    keys: [
      {
        isSigner: true,
        isWritable: true,
        pubkey: feePayer.publicKey,
      },
      {
        isSigner: false,
        isWritable: true,
        pubkey: helloAccount.publicKey,
      },
    ],
  });

  const transaction = new Transaction();
  transaction.add(allocateHelloAccountIx, initIx);

  const txHash = await sendAndConfirmTransaction(connection, transaction, [
    feePayer,
    helloAccount,
  ]);

  console.log(`Transaction succeeded. TxHash: ${txHash}`);
})();

https://solana.com/zh/developers/cookbook/programs/clock