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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions e-token-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ edition = { workspace = true }

[dependencies]
pinocchio = { workspace = true }
pinocchio-log = { workspace = true }
pinocchio-pubkey = { workspace = true }
solana-address = { workspace = true, features = ["bytemuck"] }
ephemeral-rollups-pinocchio = { workspace = true }
Expand Down
41 changes: 29 additions & 12 deletions e-token-api/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
use pinocchio::error::{ProgramError, ToStr};

#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EphemeralSplError {
// invalid instruction data
InvalidInstruction,
InvalidInstruction = 1,
// account already initialized / in use
AlreadyInUse,
// Ephemeral ATA, Vault, Mint or Owner mismatch
EphemeralAtaMismatch,
AlreadyInUse = 2,
// Ephemeral ATA mismatch
EphemeralAtaMismatch = 3,
// mint account mismatch
MintMismatch = 4,
// vault token account mismatch
VaultTokenAccountMismatch = 5,
// too many accounts were passed to the instruction
TooManyAccountKeys = 6,
// internal invariant / unreachable conversion failure
InfallibleError = 255,
}

impl From<EphemeralSplError> for ProgramError {
Expand All @@ -18,12 +27,16 @@ impl From<EphemeralSplError> for ProgramError {

impl ToStr for EphemeralSplError {
fn to_str(&self) -> &'static str {
use EphemeralSplError::*;

match self {
EphemeralSplError::InvalidInstruction => "Error: Invalid instruction",
EphemeralSplError::AlreadyInUse => "Error: Account already in use",
EphemeralSplError::EphemeralAtaMismatch => {
"Error: Ephemeral ATA/Vault/Mint/Owner mismatch"
}
InvalidInstruction => "EphemeralSplError::InvalidInstruction",
AlreadyInUse => "EphemeralSplError::AlreadyInUse",
EphemeralAtaMismatch => "EphemeralSplError::EphemeralAtaMismatch",
MintMismatch => "EphemeralSplError::MintMismatch",
VaultTokenAccountMismatch => "EphemeralSplError::VaultTokenAccountMismatch",
TooManyAccountKeys => "EphemeralSplError::TooManyAccountKeys",
InfallibleError => "EphemeralSplError::InfallibleError",
}
}
}
Expand All @@ -32,9 +45,13 @@ impl core::convert::TryFrom<u32> for EphemeralSplError {
type Error = ProgramError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(EphemeralSplError::InvalidInstruction),
1 => Ok(EphemeralSplError::AlreadyInUse),
2 => Ok(EphemeralSplError::EphemeralAtaMismatch),
1 => Ok(EphemeralSplError::InvalidInstruction),
2 => Ok(EphemeralSplError::AlreadyInUse),
3 => Ok(EphemeralSplError::EphemeralAtaMismatch),
4 => Ok(EphemeralSplError::MintMismatch),
5 => Ok(EphemeralSplError::VaultTokenAccountMismatch),
6 => Ok(EphemeralSplError::TooManyAccountKeys),
255 => Ok(EphemeralSplError::InfallibleError),
_ => Err(ProgramError::InvalidArgument),
}
}
Expand Down
1 change: 1 addition & 0 deletions e-token-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// the on-chain program crate stays cdylib-only.
pub mod consts;
pub mod error;
pub mod requires;
pub mod state;
pub mod program {
pub use ephemeral_rollups_pinocchio::consts::DELEGATION_PROGRAM_ID;
Expand Down
240 changes: 240 additions & 0 deletions e-token-api/src/requires.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
///
/// require true
///
#[macro_export]
macro_rules! require {
($cond:expr, $error:expr) => {{
if !$cond {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

they can all use core::hiint::unlikely to improve perfs (tried it locally, up to 600 CU gains on some instructions, requires nightly)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. I could use that. But 600 CU is very unlikely (pun not intended), because if usually consumes 4 to 6 CU.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not addressing this in this PR. Will consider doing it in the coming PRs. We'll probably move requires.rs to some common library crate.

Copy link
Copy Markdown
Contributor

@Dodecahedr0x Dodecahedr0x Apr 14, 2026

Choose a reason for hiding this comment

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

Sure, can be done in another PRs, it just felt appropriate since introducing all the requires.

FYI, here's the benchmark by just adding unlikely. It probably indicates that either the compiler does some weird stuff, or we are doing too many checks on some paths.

───────────────────────────────────────────────────────────────────────────────
Key                                          Baseline CU Current CU Δ CU    Δ %
───────────────────────────────────────────────────────────────────────────────
close_eata::close                                  1,726      1,723   -3 -0.17%
create_eata_perm::default                         13,709     13,569 -140 -1.02%
create_eata_perm::permissionless                  24,198     24,058 -140 -0.58%
del_eata::delegate                                43,435     43,337  -98 -0.23%
del_eata::non_owner                               29,935     29,837  -98 -0.33%
del_eata::redelegate                                 152        152   +0 +0.00%
del_eata::reinit                                   7,713      7,716   +3 +0.04%
del_eata_perm::delegate                           48,487     48,485   -2 -0.00%
del_eata_perm::non_owner                          52,987     52,985   -2 -0.00%
del_eata_perm::redelegate                            175        174   -1 -0.57%
del_shuttle_eata::delegate                        25,546     25,434 -112 -0.44%
del_shuttle_eata::redelegate                         173        160  -13 -7.51%
del_shuttle_merge::delegate                       80,548     79,861 -687 -0.85%
del_shuttle_priv::queue                           27,991     27,890 -101 -0.36%
del_shuttle_priv::shuttle                        128,773    128,086 -687 -0.53%
del_tq::delegate                                  23,491     23,390 -101 -0.43%
del_tq::redelegate                                 1,793      1,794   +1 +0.06%
dep_queue::accepts_legacy_destination_ata         13,509     13,503   -6 -0.04%
dep_queue::all_splits_use_client_ref_id           10,980     10,976   -4 -0.04%
dep_queue::assign_group_id_0                      10,712     10,706   -6 -0.06%
dep_queue::assign_group_id_1                      11,180     11,174   -6 -0.05%
dep_queue::deterministic_delays                   11,383     11,377   -6 -0.05%
dep_queue::once_split                             10,953     10,947   -6 -0.05%
dep_queue::reject_delay_range                        576        574   -2 -0.35%
dep_queue::reject_queue_full                         289        282   -7 -2.42%
dep_queue::reject_split_gt_amt                       573        572   -1 -0.17%
dep_queue::reject_zero_split                         571        569   -2 -0.35%
dep_queue::return_to_shuttle                       7,851      7,844   -7 -0.09%
dep_queue::split3_mod5                            11,152     11,146   -6 -0.05%
dep_queue::split4_mod5                            11,504     11,498   -6 -0.05%
dep_shuttle::deposit                               7,861      7,858   -3 -0.04%
dep_spl::deposit                                   7,861      7,858   -3 -0.04%
init_eata::init                                    4,723      4,588 -135 -2.86%
init_gvault::init                                 15,931     15,795 -136 -0.85%
init_gvault::migrate                              12,998     12,863 -135 -1.04%
init_shuttle::init                                19,009     18,733 -276 -1.45%
init_tq::custom                                   14,997     14,991   -6 -0.04%
init_tq::default                                  13,530     13,524   -6 -0.04%
init_tq::noop                                      6,424      6,420   -4 -0.06%
lamports_pda::allow_extra_lamports                 1,876      1,834  -42 -2.24%
lamports_pda::sponsored_lamports_transfer         61,282     60,894 -388 -0.63%
lamports_pda::transfer_lamports_pda                6,376      6,334  -42 -0.66%
merge_shuttle::merge                               9,489      9,456  -33 -0.35%
rent_pda::init                                     1,631      1,632   +1 +0.06%
rent_pda::reinit                                     240        241   +1 +0.42%
reset_eata_perm::reset                            26,531     26,389 -142 -0.54%
tq_alloc::already_allocated                        1,837      1,837   +0 +0.00%
tq_alloc::first_allocate                          19,030     19,030   +0 +0.00%
tq_auto::crank_1                                   5,331      5,332   +1 +0.02%
tq_auto::crank_2                                   6,497      6,498   +1 +0.02%
tq_auto::crank_bad_magic                             807        807   +0 +0.00%
tq_auto::enqueue_delayed                          10,437     10,431   -6 -0.06%
tq_auto::enqueue_ready                            10,437     10,431   -6 -0.06%
tq_auto::enqueue_with_client_ref_id               10,438     10,434   -4 -0.04%
tq_auto::run_scheduled_tick                       15,323     15,319   -4 -0.03%
tq_auto::run_scheduled_tick_via_magic_bundle      15,321     15,317   -4 -0.03%
tq_auto::schedule                                  5,331      5,332   +1 +0.02%
tq_auto::schedule_with_client_ref_id               5,331      5,332   +1 +0.02%
tq_auto::tick_after_bundle                         2,112      2,106   -6 -0.28%
tq_auto::tick_empty                                2,112      2,106   -6 -0.28%
tq_auto::tick_not_ready                            2,198      2,192   -6 -0.27%
ud_wd_close::undelegate                           13,765     13,743  -22 -0.16%
undel_eata_cb::undelegate                         36,856     36,856   +0 +0.00%
undel_perm_cb::undelegate                         34,444     34,444   +0 +0.00%
wd_shuttle::withdraw                              64,188     63,703 -485 -0.76%
wd_spl::withdraw                                   7,885      7,874  -11 -0.14%
───────────────────────────────────────────────────────────────────────────────

let expr = stringify!($cond);
pinocchio_log::log!("require!({}) failed.", expr);
return Err($error.into());
}
}};
}

///
/// require key1 == key2
///
#[macro_export]
macro_rules! require_eq_keys {
( $key1:expr, $key2:expr, $error:expr) => {{
if !pinocchio::address::address_eq($key1, $key2) {
pinocchio_log::log!(
"require_eq_keys!({}, {}) failed: ",
stringify!($key1),
stringify!($key2)
);
$key1.log();
$key2.log();
return Err($error.into());
}
}};
}

///
/// require key1 != key2
///
#[macro_export]
macro_rules! require_ne_keys {
( $key1:expr, $key2:expr, $error:expr) => {{
if pinocchio::address::address_eq($key1, $key2) {
pinocchio_log::log!(
"require_ne_keys!({}, {}) failed: ",
stringify!($key1),
stringify!($key2)
);
$key1.log();
$key2.log();
return Err($error.into());
}
}};
}

///
/// require a <= b
///
#[macro_export]
macro_rules! require_le {
( $val1:expr, $val2:expr, $error:expr) => {{
if !($val1 <= $val2) {
pinocchio_log::log!(
"require_le!({}, {}) failed: {} <= {}",
stringify!($val1),
stringify!($val2),
$val1,
$val2
);
return Err($error.into());
}
}};
}

///
/// require a < b
///
#[macro_export]
macro_rules! require_lt {
( $val1:expr, $val2:expr, $error:expr) => {{
if !($val1 < $val2) {
pinocchio_log::log!(
"require_lt!({}, {}) failed: {} < {}",
stringify!($val1),
stringify!($val2),
$val1,
$val2
);
return Err($error.into());
}
}};
}

///
/// require a == b
///
#[macro_export]
macro_rules! require_eq {
( $val1:expr, $val2:expr, $error:expr) => {{
if !($val1 == $val2) {
pinocchio_log::log!(
"require_eq!({}, {}) failed: {} == {}",
stringify!($val1),
stringify!($val2),
$val1,
$val2
);
return Err($error.into());
}
}};
}

///
/// require a >= b
///
#[macro_export]
macro_rules! require_ge {
( $val1:expr, $val2:expr, $error:expr) => {{
if !($val1 >= $val2) {
pinocchio_log::log!(
"require_ge!({}, {}) failed: {} >= {}",
stringify!($val1),
stringify!($val2),
$val1,
$val2
);
return Err($error.into());
}
}};
}

///
/// require a > b
///
#[macro_export]
macro_rules! require_gt {
( $val1:expr, $val2:expr, $error:expr) => {{
if !($val1 > $val2) {
pinocchio_log::log!(
"require_gt!({}, {}) failed: {} > {}",
stringify!($val1),
stringify!($val2),
$val1,
$val2
);
return Err($error.into());
}
}};
}

///
/// require exactly n accounts
///
#[macro_export]
macro_rules! require_n_accounts {
( $accounts:expr, $n:literal) => {{
match $accounts.len().cmp(&$n) {
core::cmp::Ordering::Less => {
pinocchio_log::log!(
"Need {} accounts, but got less ({}) accounts",
$n,
$accounts.len()
);
return Err(pinocchio::error::ProgramError::NotEnoughAccountKeys);
}
core::cmp::Ordering::Equal => TryInto::<&[_; $n]>::try_into($accounts)
.map_err(|_| $crate::error::EphemeralSplError::InfallibleError)?,
core::cmp::Ordering::Greater => {
pinocchio_log::log!(
"Need {} accounts, but got more ({}) accounts",
$n,
$accounts.len()
);
return Err($crate::error::EphemeralSplError::TooManyAccountKeys.into());
}
}
}};
}

///
/// require n-or-more accounts, more is returned as slice.
///
#[macro_export]
macro_rules! require_n_accounts_with_optionals {
( $accounts:expr, $n:literal) => {{
match $accounts.len().cmp(&$n) {
core::cmp::Ordering::Less => {
pinocchio_log::log!(
"Need {} accounts, but got less ({}) accounts",
$n,
$accounts.len()
);
return Err(pinocchio::error::ProgramError::NotEnoughAccountKeys);
}
_ => {
let (exact, optionals) = $accounts.split_at($n);

(
TryInto::<&[_; $n]>::try_into(exact)
.map_err(|_| $crate::error::EphemeralSplError::InfallibleError)?,
optionals,
)
}
}
}};
}

///
/// require n-or-more accounts, more is ignored.
///
#[macro_export]
macro_rules! require_n_accounts_with_ignored {
( $accounts:expr, $n:literal) => {{
match $accounts.len().cmp(&$n) {
core::cmp::Ordering::Less => {
pinocchio_log::log!(
"Need {} accounts, but got less ({}) accounts",
$n,
$accounts.len()
);
return Err(pinocchio::error::ProgramError::NotEnoughAccountKeys);
}
_ => {
let (exact, _) = $accounts.split_at($n);
TryInto::<&[_; $n]>::try_into(exact)
.map_err(|_| $crate::error::EphemeralSplError::InfallibleError)?
}
}
}};
}

///
/// require Option to be Some
///
#[macro_export]
macro_rules! require_some {
($option:expr, $error:expr) => {{
match $option {
Some(val) => val,
None => return Err($error.into()),
}
}};
}
24 changes: 14 additions & 10 deletions e-token/src/processor/allocate_transfer_queue.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use ephemeral_spl_api::state::transfer_queue::{item_len, queue_views_checked, TransferQueue};
use pinocchio::address::address_eq;
use ephemeral_spl_api::{require, require_eq_keys, require_n_accounts};
use pinocchio::sysvars::rent::Rent;
use pinocchio::sysvars::Sysvar;
use pinocchio::{error::ProgramError, AccountView, ProgramResult};

use crate::assert_owner;

pub const MAX_ITEMS_PER_REALLOC: usize = 10_240 / item_len();

///
Expand All @@ -23,19 +21,25 @@ pub fn process_allocate_transfer_queue(
accounts: &[AccountView],
_instruction_data: &[u8],
) -> ProgramResult {
let [queue_info, _system_program_info, ..] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
let [
queue_info, // force multi-line
_system_program_info,
] = require_n_accounts!(accounts, 2);

assert_owner!(queue_info, &crate::ID);
require!(
queue_info.owned_by(&crate::ID),
ProgramError::InvalidAccountOwner
);

let realloc_size = {
let (header, _) = queue_views_checked(unsafe { queue_info.borrow_unchecked() })?;
let derived_queue =
TransferQueue::derive_pda(&header.mint, &header.validator, header.bump)?;
if !address_eq(&derived_queue, queue_info.address()) {
return Err(ProgramError::InvalidSeeds);
}
require_eq_keys!(
&derived_queue,
queue_info.address(),
ProgramError::InvalidSeeds
);

let rent = Rent::get()?;
let cost_per_item = rent.try_minimum_balance(item_len())? - rent.try_minimum_balance(0)?;
Expand Down
Loading
Loading