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;