Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ members = [
"programs/memo",
"programs/system",
"programs/token",
"programs/token-2022",
"programs/token-2022", "sdk/err",
"sdk/log/crate",
"sdk/log/macro",
"sdk/pinocchio",
Expand Down
15 changes: 15 additions & 0 deletions sdk/err/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "pinocchio_err"
version = "0.1.0"
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true

[lib]
proc_macro = true

[dependencies]
pinocchio.workspace = true
quote.workspace = true
syn.workspace = true
62 changes: 62 additions & 0 deletions sdk/err/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use pinocchio::program_error::ProgramError;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput};

/// Defines a custom error macro to create custom errors in pinocchio framework
#[proc_macro_derive(ErrorCode, attributes(msg))]
pub fn error_code(input: TokenStream) -> TokenStream {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to replicate the logic from thiserror, so not sure if that is necessary.

let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let variants = if let Data::Enum(data_enum) = &input.data {
&data_enum.variants
} else {
return syn::Error::new_spanned(name, "ErrorCode can only be derived for enums")
.to_compile_error()
.into();
};

let match_arms = variants.iter().map(|variant| {
let variant_name = &variant.ident;
let msg = variant
.attrs
.iter()
.find(|attr| attr.path.is_ident("msg"))
.and_then(|attr| attr.parse_meta().ok())
.and_then(|meta| {
if let syn::Meta::NameValue(nv) = meta {
if let syn::Lit::Str(lit) = nv.lit {
return Some(lit);
}
}
None
})
.unwrap_or_else(|| {
panic!(
"Variant `{}` must have a #[msg(\"...\")] attribute",
variant_name
)
});
quote! {
Self::#variant_name => #msg,
}
});

let expanded = quote! {
impl #name {
pub fn message(&self) -> &str {
match self {
#( #match_arms )*
}
}
}

impl From<#name> for ProgramError {
fn from(e: #name) -> Self {
ProgramError::Custom(e as u32)
}
}
};

TokenStream::from(expanded)
}
32 changes: 32 additions & 0 deletions sdk/pinocchio/src/program_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,38 @@ impl ToStr for ProgramError {
}
}

/// Require function to check for invariants and error out using custom errors defined in the pinocchio-err crate
/// Logs the message defined in the msg attribute of the custom error's variants before erroring out
///
/// *Arguments* :
///
/// `invariant`: The invariant that shouldn't be false
///
/// `error`: A variant of the custom error defined in your program, which derives the ErrorCode trait
///
/// **`Note`**: **This function cannot be used with the ProgramError enum, as it's only optimized for custom errors for now**
#[macro_export]
macro_rules! require {
($invariant:expr, $error:expr $(,)?) => {
if !$invariant {
// If the error type has a `message()` function, call it.
// Otherwise, do nothing.
#[allow(unused_variables)]
{
if false {
// just to scope-check type
} else {
#[allow(unused_unsafe)]
unsafe {
$crate::msg!($error.message());
}
}
}
Comment on lines +301 to +313
Copy link
Collaborator

@febo febo Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is super strange. I am not sure why it is not written just like:

$crate::msg!($error.message());

Also, it is inefficient in terms of program size since it will generate a call to the log syscall in every use of require!. It would be much better to follow a pattern where any error is printed at the end of the process_instruction, similar to p-token: https://github.com/solana-program/token/blob/main/pinocchio/program/src/entrypoint.rs#L258

return Err($error.into());
}
};
}

#[cfg(feature = "std")]
impl core::fmt::Display for ProgramError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Expand Down