sealevel_tools/
lib.rs

1//! This crate is not an attempt to create a new framework for writing Solana programs. Instead, it
2//! is a set of tools that should help a developer write a Solana program without prescribing any
3//! specific way of doing so. By using these tools, a developer can write a lightweight program with
4//! functionality found in other frameworks.
5//!
6//! Currently, this package leverages [sealevel_nostd_entrypoint], which is a fork of an optimized
7//! no-std program entrypoint library. Its contents are re-exported for convenience.
8//! ```
9//! use sealevel_tools::{
10//!     entrypoint::{NoStdAccountInfo, ProgramResult, entrypoint_nostd},
11//!     log::sol_log,
12//!     pubkey::Pubkey,
13//! };
14//!
15//! pub fn process_instruction(
16//!     program_id: &Pubkey,
17//!     accounts: &[NoStdAccountInfo],
18//!     instruction_data: &[u8],
19//! ) -> ProgramResult {
20//!     // TODO: Check your program ID.
21//!     let _ = program_id;
22//!
23//!     // TODO: Check and use your accounts.
24//!     let _ = accounts;
25//!
26//!     // TODO: Check and use your data.
27//!     let _ = instruction_data;
28//!
29//!     sol_log("Hello, world!");
30//!
31//!     Ok(())
32//! }
33//!
34//! entrypoint_nostd!(process_instruction, 8);
35//! ```
36//!
37//! See this crate's [README] for more information about MSRV and feature flags.
38//!
39//! # Examples
40//!
41//! Check out the [safer-solana] repository for [working examples] of using this
42//! package. Below are some rudimentary examples of how to use some of these
43//! tools.
44//!
45//! # Details
46//!
47//! Here are some ways of using these tools to write your first program.
48//!
49//! ## Instruction Selectors
50//!
51//! Frameworks like [anchor-lang] and [spl-discriminator] prescribe that the first 8 bytes of a
52//! Sha256 hash representing the name of a given instruction should be used to determine how
53//! instruction data should be processed in your program.
54//!
55//! For example, [anchor-lang] typically uses the input "global:your_instruction_name" to generate
56//! the Sha256 hash. This can be achieved using [Discriminator]:
57//! ```
58//! # use sealevel_tools::discriminator::Discriminator;
59//! #
60//! const YOUR_INSTRUCTION_SELECTOR: [u8; 8] =
61//!     Discriminator::Sha2(b"global:your_instruction_name").to_bytes();
62//! ```
63//!
64//! Maybe you believe these selectors do not have to be so large as the collision among your
65//! instructions is nearly zero. You can make a 4-byte selector similarly:
66//! ```
67//! # use sealevel_tools::discriminator::Discriminator;
68//! #
69//! const YOUR_INSTRUCTION_SELECTOR: [u8; 4] =
70//!     Discriminator::Sha2(b"ix::your_instruction_name").to_bytes();
71//! ```
72//!
73//! Or use a different hashing computation incorporating the arguments for your instruction (like
74//! how Solidity works).
75//! ```
76//! # use sealevel_tools::discriminator::Discriminator;
77//! #
78//! const YOUR_INSTRUCTION_SELECTOR: [u8; 4] =
79//!     Discriminator::Keccak(b"your_instruction_name(u64,Pubkey)").to_bytes();
80//! ```
81//!
82//! Usually it is nice to store your instructions in an enum. Implementing the constant selectors is
83//! a nice way to build these into your program binary as consts. Then your processor can take the
84//! deserialized arguments of each instruction. NOTE: This example uses [borsh] for serde, but your
85//! program is not required to use it to decode instruction data.
86//! ```
87//! use sealevel_tools::{
88//!     borsh::{io, BorshDeserialize, BorshSerialize},
89//!     discriminator::Discriminator,
90//!     entrypoint::{entrypoint_nostd, NoStdAccountInfo, ProgramResult},
91//!     msg,
92//!     program_error::ProgramError,
93//!     pubkey::Pubkey,
94//! };
95//!
96//! sealevel_tools::declare_id!("Examp1eThing1111111111111111111111111111111");
97//!
98//! #[derive(Debug, BorshDeserialize, BorshSerialize)]
99//! # pub struct ThingArgs(u32);
100//!
101//! #[derive(Debug)]
102//! pub enum ProgramInstruction {
103//!     DoSomething(u64),
104//!     AddThing(ThingArgs),
105//!     RemoveThing,
106//!     DoSomethingElse { a: u32, b: [u8; 12] }
107//! }
108//!
109//! pub type Selector = [u8; 4];
110//!
111//! impl ProgramInstruction {
112//!     pub const DO_SOMETHING: Selector = Discriminator::Sha2(b"do_something").to_bytes();
113//!     pub const ADD_THING: Selector = Discriminator::Sha2(b"add_thing").to_bytes();
114//!     pub const REMOVE_THING: Selector = Discriminator::Sha2(b"remove_thing").to_bytes();
115//!     pub const DO_SOMETHING_ELSE: Selector =
116//!         Discriminator::Sha2(b"do_something_else").to_bytes();
117//! }
118//!
119//! impl BorshDeserialize for ProgramInstruction {
120//!     fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
121//!         match BorshDeserialize::deserialize_reader(reader)? {
122//!             Self::DO_SOMETHING => Ok(Self::DoSomething(BorshDeserialize::deserialize_reader(
123//!                 reader,
124//!             )?)),
125//!             Self::ADD_THING => Ok(Self::AddThing(BorshDeserialize::deserialize_reader(
126//!                 reader,
127//!             )?)),
128//!             Self::REMOVE_THING => Ok(Self::RemoveThing),
129//!             Self::DO_SOMETHING_ELSE => Ok(Self::DoSomethingElse {
130//!                 a: BorshDeserialize::deserialize_reader(reader)?,
131//!                 b: BorshDeserialize::deserialize_reader(reader)?,
132//!             }),
133//!             _ => Err(io::Error::new(
134//!                 io::ErrorKind::InvalidData,
135//!                 "Invalid discriminator",
136//!             )),
137//!         }
138//!     }
139//! }
140//!
141//! impl BorshSerialize for ProgramInstruction {
142//!     fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
143//!         match self {
144//!             Self::DoSomething(data) => {
145//!                 Self::DO_SOMETHING.serialize(writer)?;
146//!                 data.serialize(writer)
147//!             }
148//!             Self::AddThing(args) => {
149//!                 Self::ADD_THING.serialize(writer)?;
150//!                 args.serialize(writer)
151//!             }
152//!             Self::RemoveThing => Self::REMOVE_THING.serialize(writer),
153//!             Self::DoSomethingElse { a, b } => {
154//!                 Self::DO_SOMETHING_ELSE.serialize(writer)?;
155//!                 a.serialize(writer)?;
156//!                 b.serialize(writer)
157//!             }
158//!         }
159//!     }
160//! }
161//!
162//! pub fn process_instruction(
163//!     program_id: &Pubkey,
164//!     accounts: &[NoStdAccountInfo],
165//!     instruction_data: &[u8],
166//! ) -> ProgramResult {
167//!     if program_id != &ID {
168//!         return Err(ProgramError::IncorrectProgramId);
169//!     }
170//!
171//!     match BorshDeserialize::try_from_slice(instruction_data)
172//!         .map_err(|_| ProgramError::InvalidInstructionData)?
173//!     {
174//!         ProgramInstruction::DoSomething(data) => {
175//!             msg!("DoSomething: {}", data);
176//!         }
177//!         ProgramInstruction::AddThing(_) => {
178//!             msg!("AddThing");
179//!         }
180//!         ProgramInstruction::RemoveThing => {
181//!             msg!("RemoveThing");
182//!         }
183//!         ProgramInstruction::DoSomethingElse { a, b } => {
184//!             msg!("DoSomethingElse: a={}, b={:?}", a, b);
185//!         }
186//!     }
187//!
188//!     Ok(())
189//! }
190//!
191//! entrypoint_nostd!(process_instruction, 8);
192//! ```
193//!
194//! Instead of just logging using [msg!], you would use a processor method relevant for each
195//! instruction. For example, matching `DoSomething` would call an internal method resembling:
196//! ```
197//! # use sealevel_tools::entrypoint::{NoStdAccountInfo, ProgramResult};
198//! #
199//! fn process_do_something(accounts: &[NoStdAccountInfo], data: u64) -> ProgramResult {
200//!     // Do something useful here.
201//!     Ok(())
202//! }
203//! ```
204//!
205//! ## Accounts
206//!
207//! Without using a framework, the account slice's iterator is used in conjunction with
208//! [next_account_info] to take the next account from this slice.
209//!
210//! With a framework like [anchor-lang], these accounts are defined upfront in a struct, which
211//! derives the [Accounts] trait:
212//! ```ignore
213//! #[derive(Accounts)]
214//! pub struct AddThing<'a> {
215//!     #[account(mut)]
216//!     payer: Signer<'a>,
217//!
218//!     #[account(
219//!         init,
220//!         payer = payer,
221//!         space = 16,
222//!         seeds = [b"thing"],
223//!         bump,
224//!     )]
225//!     new_thing: Account<'a, Thing>,
226//!
227//!     system_program: Program<'a, System>,
228//! }
229//! ```
230//!
231//! And `Thing` account schema is defined as:
232//! ```ignore
233//! #[account]
234//! #[derive(Debug, PartialEq, Eq)]
235//! pub struct Thing {
236//!     pub data: u64,
237//! }
238//! ```
239//!
240//! Using these tools, accounts can be plucked off in the processor method or accounts can be contained
241//! in a struct similar to how Solana program frameworks organize them.
242//!
243//! Without a struct, you may iterate like so:
244//! ```
245//! # use sealevel_tools::{
246//! #   account_info::{
247//! #       try_next_enumerated_account, AccountInfoConstraints, Payer, WritableAccount
248//! #   },
249//! #   entrypoint::{NoStdAccountInfo, ProgramResult},
250//! #   pubkey::Pubkey,
251//! # };
252//! #
253//! # sealevel_tools::declare_id!("Examp1eThing1111111111111111111111111111111");
254//! #
255//! fn process(accounts: &[NoStdAccountInfo]) -> ProgramResult {
256//!     let mut accounts_iter = accounts.iter().enumerate();
257//!
258//!     // First account will be paying the rent.
259//!     let (_, payer) =
260//!         try_next_enumerated_account::<Payer>(&mut accounts_iter, Default::default())?;
261//!
262//!     let (new_thing_addr, new_thing_bump) =
263//!         Pubkey::find_program_address(&[b"thing"], &ID);
264//!
265//!     // Second account is the new Thing.
266//!     let (_, new_thing_account) = try_next_enumerated_account::<WritableAccount>(
267//!         &mut accounts_iter,
268//!         AccountInfoConstraints {
269//!             key: Some(&new_thing_addr),
270//!             ..Default::default()
271//!         },
272//!     )?;
273//!
274//!     Ok(())
275//! }
276//! ```
277//!
278//! [try_next_enumerated_account] takes an enumerated iterator and
279//! returns tools-defined types, which are simple wrappers around [NoStdAccountInfo] (e.g.
280//! [Payer], which is a writable [Signer]. [AccountInfoConstraints] provide some optional
281//! constraints when plucking off the next account (e.g. verifying that the pubkey equals what you
282//! expect). In the above example, we are asserting that the new `Thing` account is a
283//! [WritableAccount], whose const bool value says that it is a writable account.
284//!
285//! If you desire more structure in your life, encapsulate the account plucking logic in a struct
286//! via the [TakeAccounts] trait:
287//!
288//! ```
289//! # use sealevel_tools::{
290//! #   account_info::{
291//! #       try_next_enumerated_account, AccountInfoConstraints, Payer, TakeAccounts,
292//! #       WritableAccount
293//! #   },
294//! #   entrypoint::NoStdAccountInfo,
295//! #   program_error::ProgramError,
296//! #   pubkey::Pubkey,
297//! # };
298//! #
299//! # sealevel_tools::declare_id!("Examp1eThing1111111111111111111111111111111");
300//! #
301//! struct AddThingAccounts<'a> {
302//!     payer: (usize, Payer<'a>),
303//!     new_thing: (
304//!         usize,
305//!         WritableAccount<'a>,
306//!         u8, // bump
307//!     ),
308//! }
309//!
310//! impl<'a> TakeAccounts<'a> for AddThingAccounts<'a> {
311//!     fn take_accounts(
312//!         iter: &mut impl Iterator<Item = (usize, &'a NoStdAccountInfo)>,
313//!     ) -> Result<Self, ProgramError> {
314//!         let payer = try_next_enumerated_account(iter, Default::default())?;
315//!
316//!         let (new_thing_addr, new_thing_bump) =
317//!             Pubkey::find_program_address(&[b"thing"], &ID);
318//!
319//!         let (new_thing_index, new_thing_account) = try_next_enumerated_account(
320//!             iter,
321//!             AccountInfoConstraints {
322//!                 key: Some(&new_thing_addr),
323//!                 ..Default::default()
324//!             },
325//!         )?;
326//!
327//!         Ok(Self {
328//!             payer,
329//!             new_thing: (new_thing_index, new_thing_account, new_thing_bump),
330//!         })
331//!     }
332//! }
333//! ```
334//!
335//! Account indices are helpful when a particular account has an error (where you can revert with a
336//! colorful error message indicating which account is the culprit). Solana program frameworks just
337//! give a pubkey or name of the account that failed, which are helpful relative to the IDL these
338//! SDKs leverage. But when writing a program with these tools, the next best option is giving the
339//! index of the accounts array you passed into your transaction. [try_next_enumerated_account] has
340//! error handling that gives the user information about which account index failed any checks using
341//! the [AccountInfoConstraints].
342//!
343//! Also notice that we do not check that the System program is provided. You can add an explicit
344//! check for it (like how [anchor-lang] requires it). Or it can be assumed that it is one of the
345//! remaining accounts in the [NoStdAccountInfo] slice since the `Thing` being created would fail
346//! without it (since the CPI call to the System program requires it).
347//!
348//! To wrap up this example, because `Thing` is a new account, you can create it like so:
349//! ```
350//! # use borsh::{BorshDeserialize, BorshSerialize};
351//! # use sealevel_tools::{
352//! #    account::{AccountSerde, BorshAccountSchema},
353//! #    account_info::{
354//! #       try_next_enumerated_account, AccountInfoConstraints, Payer, WritableAccount
355//! #    },
356//! #    cpi::system_program::CreateAccount,
357//! #    discriminator::{Discriminate, Discriminator},
358//! #    entrypoint::{NoStdAccountInfo, ProgramResult},
359//! #    pubkey::Pubkey,
360//! # };
361//! #
362//! # sealevel_tools::declare_id!("Examp1eThing1111111111111111111111111111111");
363//! #
364//! #[derive(Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
365//! pub struct Thing {
366//!     pub data: u64,
367//! }
368//!
369//! impl Discriminate<8> for Thing {
370//!     const DISCRIMINATOR: [u8; 8] = Discriminator::Sha2(b"account:Thing").to_bytes();
371//! }
372//!
373//! fn process(accounts: &[NoStdAccountInfo]) -> ProgramResult {
374//! #     let mut accounts_iter = accounts.iter().enumerate();
375//! #
376//! #     let (_, payer) =
377//! #         try_next_enumerated_account::<Payer>(&mut accounts_iter, Default::default())?;
378//! #
379//! #     let (new_thing_addr, new_thing_bump) =
380//! #         Pubkey::find_program_address(&[b"thing"], &ID);
381//! #
382//! #     let (_, new_thing_account) = try_next_enumerated_account::<WritableAccount>(
383//! #         &mut accounts_iter,
384//! #         AccountInfoConstraints {
385//! #             key: Some(&new_thing_addr),
386//! #             ..Default::default()
387//! #         },
388//! #     )?;
389//! #
390//!     CreateAccount {
391//!         payer: payer.as_cpi_authority(),
392//!         to: new_thing_account.as_cpi_authority(Some(&[b"thing", &[new_thing_bump]])),
393//!         program_id: &ID,
394//!         space: None,
395//!         lamports: None,
396//!     }
397//!     .try_invoke_and_serialize(&BorshAccountSchema(Thing { data: 69 }))?;
398//! #
399//! #   Ok(())
400//! }
401//! ```
402//!
403//! The account discriminator does not have to be 8 bytes like how [anchor-lang] and
404//! [spl-discriminator] enforce it to be. To save on a bit of rent, 4 bytes should be sufficient to
405//! avoid collision among all of your program's data accounts (where the cost savings is 4 * 6,960
406//! lamports).
407//!
408//! There are more lines of code required to perform the same functionality that Solana program
409//! framework may remove from your life. For example, [anchor-lang] would only require this to
410//! instantiate your `Thing`:
411//! ```ignore
412//! pub fn add_thing(ctx: Context<AddThing>) -> Result<()> {
413//!     ctx.accounts.new_thing.set_inner(Thing { data: 69 });
414//!     Ok(())
415//! }
416//! ```
417//!
418//! But in an attempt to keeping things simple and lightweight, the cost is a huge increase in program
419//! binary size and requiring more compute units than necessary to perform the same task. Pick your
420//! poison. But larger binary size translates to a higher deployment cost and higher compute units can
421//! affect your end users.
422//!
423//! [AccountInfo]: https://docs.rs/solana-account-info/latest/solana_account_info/struct.AccountInfo.html
424//! [Accounts]: https://docs.rs/anchor-lang/latest/anchor_lang/trait.Accounts.html
425//! [Discriminator]: crate::discriminator::Discriminator
426//! [AccountInfoConstraints]: crate::account_info::AccountInfoConstraints
427//! [NoStdAccountInfo]: crate::entrypoint::NoStdAccountInfo
428//! [Payer]: crate::account_info::Payer
429//! [README]: https://crates.io/crates/sealevel-tools
430//! [Signer]: crate::account_info::Signer
431//! [TakeAccounts]: crate::account_info::TakeAccounts
432//! [WritableAccount]: crate::account_info::WritableAccount
433//! [anchor-lang]: https://docs.rs/anchor-lang/latest/anchor_lang/
434//! [msg!]: https://docs.rs/solana-msg/latest/solana_msg/macro.msg.html
435//! [next_account_info]: https://docs.rs/solana-account-info/latest/solana_account_info/fn.next_account_info.html
436//! [safer-solana]: https://github.com/rtrombone/safer-solana
437//! [spl-discriminator]: https://docs.rs/spl-discriminator/latest/spl_discriminator/
438//! [shank]: https://docs.rs/shank/latest/shank/
439//! [try_next_enumerated_account]: crate::account_info::try_next_enumerated_account
440//! [working examples]: https://github.com/rtrombone/safer-solana/tree/main/examples/
441
442#![deny(dead_code, unused_imports, unused_mut, unused_variables)]
443#![no_std]
444
445pub mod account;
446pub mod account_info;
447pub mod cpi;
448pub mod discriminator;
449mod error;
450pub mod log;
451pub mod pda;
452pub mod sysvar;
453
454pub use error::SealevelToolsError;
455
456/// Re-export of [sealevel_nostd_entrypoint] items.
457///
458/// ### Notes
459///
460/// Because our package leverages this optimized no-std Solana entrypoint crate for account and CPI
461/// handling, its contents are re-exported here for convenience (so there is no need to add
462/// [sealevel_nostd_entrypoint] as a dependency in your program).
463pub mod entrypoint {
464    pub use sealevel_nostd_entrypoint::{
465        basic_panic_impl, deserialize_nostd, deserialize_nostd_no_dup,
466        deserialize_nostd_no_dup_no_program, deserialize_nostd_no_program, entrypoint_nostd,
467        entrypoint_nostd_no_duplicates, entrypoint_nostd_no_duplicates_no_program,
468        entrypoint_nostd_no_program, noalloc_allocator, AccountInfoC, AccountMetaC, InstructionC,
469        NoStdAccountInfo, NoStdAccountInfoInner, RcRefCellInner, Ref, RefMut,
470    };
471
472    pub use crate::program_error::ProgramResult;
473}
474
475#[cfg(feature = "alloc")]
476extern crate alloc;
477
478#[cfg(feature = "borsh")]
479pub use borsh;
480pub use solana_msg::msg;
481pub use solana_program_error as program_error;
482pub use solana_pubkey::{self as pubkey, declare_id, pubkey};
483#[cfg(feature = "token")]
484pub use spl_token_2022;