Solana Smart Contract Best Practices

Info

This is a beta version of the Solana Toolkit, and is still a WIP. Please post all feedback as a GitHub issue here.

Optimize Compute Usage #

To prevent abuse of computational resources, each transaction is allocated a "compute budget". This budget specifies details about compute units and includes:

  • 与交易可能执行的不同类型操作相关的计算成本(每个操作消耗的计算单元),
  • 交易可以消耗的最大计算单元数量(计算单元限制),
  • 以及交易必须遵守的操作界限(如账户数据大小限制)

When the transaction consumes its entire compute budget (compute budget exhaustion), or exceeds a bound such as attempting to exceed the max call stack depth or max loaded account data size limit, the runtime halts the transaction processing and returns an error. Resulting in a failed transaction and no state changes (aside from the transaction fee being collected).

Additional References #

Saving Bumps #

Info

Program Derived Address (PDAs) are addresses that PDAs are addresses that are deterministically derived and look like standard public keys, but have no associated private keys. These PDAs are derived using a numerical value, called a "bump", to guarantee that the PDA is off-curve and cannot have an associated private key. It "bumps" the address off the cryptographic curve.

Saving the bump to your Solana smart contract account state ensures deterministic address generation, efficiency in address reconstruction, reduced transaction failure, and consistency across invocations.

Additional References #

Payer-Authority Pattern #

The Payer-Authority pattern is an elegant way to handle situations where the account’s funder (payer) differs from the account’s owner or manager (authority). By requiring separate signers and validating them in your onchain logic, you can maintain clear, robust, and flexible ownership semantics in your program.

Shank Example #

// Create a new account.
#[account(0, writable, signer, name="account", desc = "The address of the new account")]
#[account(1, writable, signer, name="payer", desc = "The account paying for the storage fees")]
#[account(2, optional, signer, name="authority", desc = "The authority signing for the account creation")]
#[account(3, name="system_program", desc = "The system program")]
CreateAccountV1(CreateAccountV1Args),

Anchor Example #

#[derive(Accounts)]
pub struct CreateAccount<'info> {
    /// The address of the new account
    #[account(init, payer = payer_one, space = 8 + NewAccount::MAXIMUM_SIZE)]
    pub account: Account<'info, NewAccount>,
 
    /// The account paying for the storage fees
    #[account(mut)]
    pub payer: Signer<'info>,
 
    /// The authority signing for the account creation
    pub authority: Option<Signer<'info>>,
 
    // The system program
    pub system_program: Program<'info, System>
}

Additional References #

Invariants #

Implement invariants, which are functions that you can call at the end of your instruction to assert specific properties because they help ensure the correctness and reliability of programs.

require!(amount > 0, ErrorCode::InvalidAmount);

Additional References #

Optimize Indexing #

You can make indexing easier by placing static size fields at the beginning and variable size fields at the end of your onchain structures.