#account #generics #solana #sanctum

solana-readonly-account

Readonly solana account field getter traits that work for both on-chain AccountInfos and off-chain Accounts

2 stable releases

1.1.0 Jan 3, 2024
1.0.0 Dec 11, 2023

#303 in Magic Beans

Download history 7/week @ 2023-12-10 8/week @ 2023-12-31 6/week @ 2024-02-18 39/week @ 2024-02-25 2/week @ 2024-03-03 4/week @ 2024-03-10 16/week @ 2024-03-17 28/week @ 2024-03-24

53 downloads per month

MIT/Apache

19KB
299 lines

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?

  • You cannot use the original ReadableAccount trait from solana-sdk in on-chain programs because the solana-sdk feature flags don't work properly and it won't compile with build-sbf
  • Rc<RefCell<>>s in AccountInfo make it incompatible with &[u8] for .data

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

Dependencies

~16–25MB
~406K SLoC