Solana IDL to Rust client / CPI interface generator.
solita, light of my life, fire of my loins
This software is still in its early stages of development. USE AT YOUR OWN RISK. It's a codegen CLI, so you can always read and modify the generated code if you need to.
Table of contents generated with markdown-toc
cargo install solores to install the CLI binary.
For anchor IDLs, the crate will also:
- export all accounts' discriminant as consts.
- create a
*Accountnewtype that includes account discriminant checking in borsh serde operations - export event struct defs
serde is added as an optional dependency behind the serde feature-flag to the generated crate to provide Serialize and Deserialize implementations for the various typedefs and onchain accounts.
Do note that since it's a simple derive, Pubkeys are de/serialized as byte arrays instead of base-58 strings.
The various *Keys struct also impl From<[Pubkey; *_IX_ACCOUNTS_LEN]> to support indexing
use my_token_interface::{TRANSFER_IX_ACCOUNTS_LEN, TransferKeys};
use solana_program::{pubkey::Pubkey, sysvar::instructions::{BorrowedAccountMeta, BorrowedInstruction}};
use std::convert::TryInto;
fn index_instruction(ix: BorrowedInstruction) {
let metas: [BorrowedAccountMeta<'_>; TRANSFER_IX_ACCOUNTS_LEN] = ix.accounts.try_into().unwrap();
let pubkeys = metas.map(|meta| *meta.pubkey);
let transfer_keys: TransferKeys = pubkeys.into();
// Now you can do stuff like `transfer_keys.src` instead of
// having to keep track of the various account indices
//
// ...
}The various *Accounts also impl From<&[AccountInfo; *_IX_ACCOUNTS_LEN]> to make unpacking from the program accounts slice more ergonomic.
use my_token_interface::{TRANSFER_IX_ACCOUNTS_LEN, TransferAccounts, TransferArgs, TransferIxArgs, transfer_invoke};
use solana_program::{account_info::{AccountInfo, next_account_info}, entrypoint::ProgramResult, program::invoke, pubkey::Pubkey};
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let transfer_accounts: &[AccountInfo; TRANSFER_IX_ACCOUNTS_LEN] = accounts[..TRANSFER_IX_ACCOUNTS_LEN].try_into().unwrap();
let accounts: TransferAccounts = transfer_accounts.into();
transfer_invoke(
accounts,
TransferIxArgs {
transfer_args: TransferArgs { amount: 1_000 },
}
)
}A function to compare equality between the pubkeys of a instruction *Accounts struct with a *Keys struct is generated:
use my_token_interface::{TransferAccounts, TransferKeys, transfer_verify_account_keys};
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, program_error::ProgramError};
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let accounts: TransferAccounts = ...
let expected_keys: TransferKeys = ...
// transfer_verify_account_keys() returns the first non-matching pubkeys between accounts and expected_keys
if let Err((actual_pubkey, expected_pubkey)) = transfer_verify_account_keys(accounts, expected_keys) {
return Err(ProgramError::InvalidAccountData);
}
}This function is not generated if the instruction has no account inputs.
A function to ensure writable + signer privileges of a instruction *Accounts struct is also generated:
use my_token_interface::{TransferAccounts, TransferKeys, transfer_verify_account_privileges};
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, program_error::ProgramError};
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let accounts: TransferAccounts = ...
if let Err((offending_acc, program_err)) = transfer_verify_account_privileges(accounts) {
solana_program::msg!("Writable/signer privilege escalation for {}: {}", offending_acc.key, program_err);
return Err(program_err);
}
}This function is not generated if the instruction has no privileged account inputs (only non-signer and non-writable accounts).
Pass -z <name-of-type-or-account-in-idl> to additionally derive Pod + Zeroable + Copy for the generated types. Accepts multiple options. The correctness of the derive is not checked.
The following instructions that take a program ID pubkey as argument are also exported:
*_ix_with_program_id()*_invoke_with_program_id()*_invoke_signed_with_program_id()
They allow the creation of Instructions and invoking of programs of the same interface at a different program ID.
Compared to anchor-gen, solores:
-
Has no dependency on anchor. The generated crate's dependencies are:
- borsh + solana-program
- thiserror + num-derive + num-traits if the idl contains error enum definitions.
- bytemuck if any
-ztypes are provided
-
Produces human-readable rust code in a new, separate crate instead of using a proc-macro.
-
Exposes lower-level constructs such as functions for creating the
solana_program::instruction::Instructionstruct to allow for greater customizability.
Please check the repo's issues list for more.
- Does not check correctness of zero-copy/bytemuck accounts derives
- Does not handle account namespaces
- Does not handle the state instruction namespace