igneous-labs / solana-readonly-account

Readonly solana account field getter traits that work for both on-chain AccountInfos and off-chain Accounts
3 stars 1 forks source link

ARCHIVED

Repo has been moved to sanctum-solana-utils

solana-readonly-account

Reimplementation of ReadableAccount to enable code reuse across off-chain clients (solana-sdk) and on-chain programs (solana-program)

Why was this crate created?

Library

The 6 main account fields (key, lamports, data, owner, is_executable, rent_epoch) are split into a single getter trait each. This splitting allows for greater trait composability and flexibility.

For example, say you had a function that only requires the account's owner and this is a known static pubkey. Instead of having to fetch the full Account just to read its already-known owner field, or creating a dummy Account, you can simply define a newtype that only needs to implement ReadonlyAccountOwner, while still maintaining the ability to use this function with on-chain AccountInfos.

Since solana_sdk::Account doesn't have its pubkey field, the following Keyed struct is defined in crate::sdk for off-chain use cases:

pub struct Keyed<T> {
    pub pubkey: Pubkey,
    pub account: T,
}

The type KeyedAccount is an alias for Keyed<solana_sdk::account::Account>.

ReadonlyAccountPubkey trait

pub trait ReadonlyAccountPubkey {
    /// Returns the pubkey of this account
    fn pubkey(&self) -> &Pubkey;
}

impl for:

ReadonlyAccountLamports trait

pub trait ReadonlyAccountLamports {
    /// Returns the lamports of this account
    fn lamports(&self) -> u64;
}

impl for:

ReadonlyAccountData trait

pub trait ReadonlyAccountData {
    type SliceDeref<'s>: Deref<Target = [u8]>
    where
        Self: 's;
    type DataDeref<'d>: Deref<Target = Self::SliceDeref<'d>>
    where
        Self: 'd;

    /// Returns the data buffer of this account that can be derefed twice into a byte-slice
    fn data(&self) -> Self::DataDeref<'_>;
}

impl for:

ReadonlyAccountOwner trait

pub trait ReadonlyAccountOwner {
    /// Returns the pubkey of the program owning this account
    fn owner(&self) -> &Pubkey;
}

impl for:

ReadonlyAccountIsExecutable trait

pub trait ReadonlyAccountIsExecutable {
    /// Returns true if this is an executable account, false otherwise
    fn executable(&self) -> bool;
}

impl for:

ReadonlyAccountRentEpoch trait

pub trait ReadonlyAccountRentEpoch {
    /// Returns the rent epoch of this account
    fn rent_epoch(&self) -> Epoch;
}

impl for:

Usage

Importing the respective traits from the crate now enables you to write generic functions that work both on-chain and off-chain

use solana_program::{
    program_error::ProgramError, program_pack::Pack,
};
use solana_readonly_account::ReadonlyAccountData;
use spl_token_2022::state::Account;

pub fn try_deserialize_token_account<A: ReadonlyAccountData>(
    acc: A,
) -> Result<Account, ProgramError> {
    Account::unpack(&acc.data())
}

By default, this crate only has the traits implemented for AccountInfo and is only usable in an on-chain context. To use it in an off-chain context, enable the solana-sdk feature, which will implement them for Account.

Do not enable the feature in an on-chain program crate, or cargo-build-sbf will fail.

Testing

cargo test --all-features