Skip to content
This repository was archived by the owner on May 2, 2025. It is now read-only.

igneous-labs/solores

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ARCHIVE NOTICE

Development of this library has stopped and will likely not continue.

IDLs are the devil's work, and I will not contribute to their further proliferation.

Rust is all you need. You can write a minimal no-std base sdk crate that is portable across onchain (both CPI and for the development of the program itself) and offchain (rust clients, js via wasm, python via pyo3) environments. Use the same codebase for everything instead of trying to sync changes across different languages using a lossy json format. Here's an incomplete example with only CPI integrations.

You can just do this instead of spending your time learning about how each of codama's "over 60 different types of (AST) nodes" work and debugging generated code.

The only major disadvantage of this approach is possibly large wasm binary sizes from including cryptographic operations not supported by the web Crypto interface, with find_pda being the main culprit. But you can also structure it such that that part is delegated to javascript users.

solores

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.

Contents

Table of contents generated with markdown-toc

Supported IDL Formats

Installation

cargo install solores to install the CLI binary.

Examples

Shank IDL

Lets say you had the following shank generated IDL, my_token_idl.json:

{
  "name": "my_token",
  "instructions": [
    {
      "name": "transfer",
      "accounts": [
        {
          "name": "src",
          "isMut": true,
          "isSigner": true
        },
        {
          "name": "dest",
          "isMut": true,
          "isSigner": false
        }
      ],
      "args": [
        {
          "name": "transferArgs",
          "type": {
            "defined": "TransferArgs"
          }
        }
      ],
      "discriminant": {
        "type": "u8",
        "value": 0
      }
    }
  ],
  "types": [
    {
      "name": "TransferArgs",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "amount",
            "type": "u64"
          }
        ]
      }
    }
  ]
}

Running solores my_token_idl.json should generate a my_token_interface rust crate that allows you to use it in an on-chain program as so:

use my_token_interface::{TransferAccounts, TransferArgs, TransferIxArgs, transfer_invoke_signed};
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 account_info_iter = &mut accounts.iter();
    let src = next_account_info(account_info_iter)?;
    let dest = next_account_info(account_info_iter)?;

    transfer_invoke_signed(
        TransferAccounts { src, dest },
        TransferIxArgs {
            transfer_args: TransferArgs { amount: 1_000 },
        },
        &[&[&[0u8]]],
    )
}

or in a client-side app:

use my_token_interface::{TransferKeys, TransferArgs, transfer_ix};

pub fn do_something_with_instruction() -> std::io::Result<()> {
    ...

    let transfer_accounts = TransferKeys {
        src: some_pubkey,
        dest: another_pubkey,
    };
    let transfer_ix_args = TransferIxArgs {
        transfer_args: TransferArgs { amount: 1_000 },
    };
    let ix = transfer_ix(transfer_accounts, transfer_ix_args)?;

    ...
}

The crate will also combine all instructions into a single borsh de/serializable ProgramIx enum

use borsh::BorshSerialize;
use my_token_interface::{MyTokenProgramIx, TransferArgs, TransferIxArgs};

#[test]
pub fn test_borsh_serde_roundtrip_program_ix() {
    let program_ix = MyTokenProgramIx::Transfer(
        TransferIxArgs {
            transfer_args: TransferArgs { amount: 1 },
        }
    );

    // [0, 1, 0, 0, 0, 0, 0, 0, 0]
    let serialized = program_ix.try_to_vec().unwrap();

    // note that deserialize is an associated function/method
    // rather than the BorshDeserialize trait impl,
    // i.e. MyTokenProgramIx does NOT impl BorshDeserialize
    // since it doesn't follow the borsh spec
    let deserialized = MyTokenProgramIx::deserialize(&serialized).unwrap();
    assert_eq!(program_ix, deserialized);
}

The crate will also export the instructions' discriminant as consts, and any error types defined in the IDL as an enum convertible to/from u32.

Anchor IDL

The usage for anchor IDLs is essentially the same as Shank IDL's. Additionally, the crate will also:

  • export all accounts' discriminant as consts.
  • create a *Account newtype that includes account discriminant checking in borsh serde operations
  • export event struct defs

Bincode IDL

For supporting older solana programs (system, stake), solores also supports a custom bincode IDL format identified by { "metadata": { "origin": "bincode" }}.

The instructions must be declared in enum order to work with bincode.

No account definitions are supported, since system and stake program have their account defs in solana-program already.

Features

Serde

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.

Keys From Array

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
    //
    // ...
}

Accounts From Array

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 },
        }
    )
}

Instruction Accounts Verification Functions

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).

Zero-copy/bytemuck support

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.

*_with_program_id()

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.

Comparison To Similar Libs

anchor-gen

Compared to anchor-gen, solores:

  • Has no dependency on anchor. The generated crate's dependencies are:

  • 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::Instruction struct to allow for greater customizability.

Known Missing Features

Please check the repo's issues list for more.

General

  • Does not check correctness of zero-copy/bytemuck accounts derives

Anchor

  • Does not handle account namespaces
  • Does not handle the state instruction namespace

About

Solana IDL to Rust client / CPI interface generator

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •