Solana Smart Contract Best Practices
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:
- El costo del cómputo asociado a los distintos tipos de operaciones que puede realizar la transacción (unidades de cómputo consumidas por operación)
- El número máximo de unidades de cálculo que puede consumir una transacción (límite de unidades de cómputo),
- Y los límites operativos que debe respetar la transacción (como los límites de tamaño de los datos de la cuenta)
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 #
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.