Skip to content
Merged
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
44 changes: 44 additions & 0 deletions src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::{fast_process_instruction, slow_process_instruction};

use solana_program::entrypoint;

entrypoint::custom_heap_default!();
entrypoint::custom_panic_default!();

/// # Safety
///
/// It's pretty close to the code generated by entrypoint!() macro, with one minor tweak to
/// support fallback branch.
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
const UNINIT: core::mem::MaybeUninit<pinocchio::account_info::AccountInfo> =
core::mem::MaybeUninit::<pinocchio::account_info::AccountInfo>::uninit();
let mut accounts = [UNINIT; { pinocchio::MAX_TX_ACCOUNTS }];

let (program_id, count, data) =
pinocchio::entrypoint::deserialize::<{ pinocchio::MAX_TX_ACCOUNTS }>(input, &mut accounts);
match fast_process_instruction(
program_id,
core::slice::from_raw_parts(accounts.as_ptr() as _, count),
data,
Comment on lines +14 to +23
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.

🧹 Nitpick | 🔵 Trivial

Document safety invariants for the fast path buffer cast.

Add a brief comment explaining why the MaybeUninit cast is sound.

 /// support fallback branch.
 #[no_mangle]
 pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
+    // SAFETY: `pinocchio::entrypoint::deserialize::<N>` initializes at most `count <= N`
+    // elements in `accounts`. We only create a slice over the first `count` elements,
+    // making the cast from `[MaybeUninit<_>]` to `[_]` sound.
     const UNINIT: core::mem::MaybeUninit<pinocchio::account_info::AccountInfo> =
         core::mem::MaybeUninit::<pinocchio::account_info::AccountInfo>::uninit();
     let mut accounts = [UNINIT; { pinocchio::MAX_TX_ACCOUNTS }];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const UNINIT: core::mem::MaybeUninit<pinocchio::account_info::AccountInfo> =
core::mem::MaybeUninit::<pinocchio::account_info::AccountInfo>::uninit();
let mut accounts = [UNINIT; { pinocchio::MAX_TX_ACCOUNTS }];
let (program_id, count, data) =
pinocchio::entrypoint::deserialize::<{ pinocchio::MAX_TX_ACCOUNTS }>(input, &mut accounts);
match fast_process_instruction(
program_id,
core::slice::from_raw_parts(accounts.as_ptr() as _, count),
data,
/// support fallback branch.
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
// SAFETY: `pinocchio::entrypoint::deserialize::<N>` initializes at most `count <= N`
// elements in `accounts`. We only create a slice over the first `count` elements,
// making the cast from `[MaybeUninit<_>]` to `[_]` sound.
const UNINIT: core::mem::MaybeUninit<pinocchio::account_info::AccountInfo> =
core::mem::MaybeUninit::<pinocchio::account_info::AccountInfo>::uninit();
let mut accounts = [UNINIT; { pinocchio::MAX_TX_ACCOUNTS }];
let (program_id, count, data) =
pinocchio::entrypoint::deserialize::<{ pinocchio::MAX_TX_ACCOUNTS }>(input, &mut accounts);
match fast_process_instruction(
program_id,
core::slice::from_raw_parts(accounts.as_ptr() as _, count),
data,
)
}
🤖 Prompt for AI Agents
In src/entrypoint.rs around lines 14 to 23, the unsafe cast from
MaybeUninit<AccountInfo> array to a pointer for core::slice::from_raw_parts
needs a short comment documenting safety invariants: state that deserialize
initializes exactly `count` elements of `accounts` (no more), that AccountInfo
is Plain Old Data (no drop glue / representation compatible with MaybeUninit),
the memory is properly aligned for AccountInfo, and the resulting slice lifetime
is limited to the immediate use in fast_process_instruction; add this brief
comment immediately above the from_raw_parts call describing those guarantees so
reviewers know why the cast is sound.

) {
Some(Ok(())) => pinocchio::SUCCESS,
Some(Err(error)) => error.into(),

// Fallback to the slow path that does not use pinocchio SDK.
None => slow_entrypoint(input),
}
}

/// # Safety
///
/// It's pretty close to the code generated by entrypoint!() macro, with one difference: the
/// function name is slow_entrypoint() as opposed to entrypoint() because this is a fallback
/// entrypoint (a slow one).
pub unsafe fn slow_entrypoint(input: *mut u8) -> u64 {
let (program_id, accounts, instruction_data) = unsafe { entrypoint::deserialize(input) };
match slow_process_instruction(program_id, &accounts, instruction_data) {
Ok(()) => entrypoint::SUCCESS,
Err(error) => error.into(),
}
}
99 changes: 27 additions & 72 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
#![allow(unexpected_cfgs)] // silence clippy for target_os solana and other solana program custom features

use crate::processor::process_call_handler;
use crate::discriminator::DlpDiscriminator;
use pinocchio_log::log;
use solana_program::{
account_info::AccountInfo,
declare_id,
entrypoint::{self, ProgramResult},
msg,
program_error::ProgramError,
pubkey::Pubkey,
};

use discriminator::DlpDiscriminator;
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use solana_program::{declare_id, msg};

pub mod args;
pub mod consts;
Expand All @@ -24,16 +19,15 @@ pub mod state;

#[cfg(feature = "log-cost")]
mod cu;
#[cfg(not(feature = "no-entrypoint"))]
mod entrypoint;
Comment thread
snawaz marked this conversation as resolved.

declare_id!("DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh");

pub mod fast {
pinocchio_pubkey::declare_id!("DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh");
}

entrypoint::custom_heap_default!();
entrypoint::custom_panic_default!();

#[cfg(all(not(feature = "no-entrypoint"), feature = "solana-security-txt"))]
solana_security_txt::security_txt! {
name: "MagicBlock Delegation Program",
Expand All @@ -44,44 +38,6 @@ solana_security_txt::security_txt! {
source_code: "https://github.com/magicblock-labs/delegation-program"
}

/// # Safety
///
/// It's pretty close to the code generated by entrypoint!() macro, with one minor tweak to
/// support fallback branch.
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
const UNINIT: core::mem::MaybeUninit<pinocchio::account_info::AccountInfo> =
core::mem::MaybeUninit::<pinocchio::account_info::AccountInfo>::uninit();
let mut accounts = [UNINIT; { pinocchio::MAX_TX_ACCOUNTS }];

let (program_id, count, data) =
pinocchio::entrypoint::deserialize::<{ pinocchio::MAX_TX_ACCOUNTS }>(input, &mut accounts);
match fast_process_instruction(
program_id,
core::slice::from_raw_parts(accounts.as_ptr() as _, count),
data,
) {
Some(Ok(())) => pinocchio::SUCCESS,
Some(Err(error)) => error.into(),

// Fallback to the slow path that does not use pinocchio SDK.
None => slow_entrypoint(input),
}
}

/// # Safety
///
/// It's pretty close to the code generated by entrypoint!() macro, with one difference: the
/// function name is slow_entrypoint() as opposed to entrypoint() because this is a fallback
/// entrypoint (a slow one).
pub unsafe fn slow_entrypoint(input: *mut u8) -> u64 {
let (program_id, accounts, instruction_data) = unsafe { entrypoint::deserialize(input) };
match slow_process_instruction(program_id, &accounts, instruction_data) {
Ok(()) => entrypoint::SUCCESS,
Err(error) => error.into(),
}
}

pub fn fast_process_instruction(
program_id: &pinocchio::pubkey::Pubkey,
accounts: &[pinocchio::account_info::AccountInfo],
Expand All @@ -106,19 +62,19 @@ pub fn fast_process_instruction(
};

match discriminator {
discriminator::DlpDiscriminator::Delegate => Some(processor::fast::process_delegate(
DlpDiscriminator::Delegate => Some(processor::fast::process_delegate(
program_id, accounts, data,
)),
discriminator::DlpDiscriminator::CommitState => Some(
processor::fast::process_commit_state(program_id, accounts, data),
),
discriminator::DlpDiscriminator::CommitStateFromBuffer => Some(
DlpDiscriminator::CommitState => Some(processor::fast::process_commit_state(
program_id, accounts, data,
)),
DlpDiscriminator::CommitStateFromBuffer => Some(
processor::fast::process_commit_state_from_buffer(program_id, accounts, data),
),
discriminator::DlpDiscriminator::Finalize => Some(processor::fast::process_finalize(
DlpDiscriminator::Finalize => Some(processor::fast::process_finalize(
program_id, accounts, data,
)),
discriminator::DlpDiscriminator::Undelegate => Some(processor::fast::process_undelegate(
DlpDiscriminator::Undelegate => Some(processor::fast::process_undelegate(
program_id, accounts, data,
)),
_ => None,
Expand All @@ -135,40 +91,39 @@ pub fn slow_process_instruction(
}

let (tag, data) = data.split_at(8);
let ix = discriminator::DlpDiscriminator::try_from(tag[0])
.or(Err(ProgramError::InvalidInstructionData))?;
let ix = DlpDiscriminator::try_from(tag[0]).or(Err(ProgramError::InvalidInstructionData))?;

msg!("Processing instruction: {:?}", ix);
match ix {
discriminator::DlpDiscriminator::InitValidatorFeesVault => {
DlpDiscriminator::InitValidatorFeesVault => {
processor::process_init_validator_fees_vault(program_id, accounts, data)?
}
discriminator::DlpDiscriminator::InitProtocolFeesVault => {
DlpDiscriminator::InitProtocolFeesVault => {
processor::process_init_protocol_fees_vault(program_id, accounts, data)?
}
discriminator::DlpDiscriminator::ValidatorClaimFees => {
DlpDiscriminator::ValidatorClaimFees => {
processor::process_validator_claim_fees(program_id, accounts, data)?
}
discriminator::DlpDiscriminator::WhitelistValidatorForProgram => {
DlpDiscriminator::WhitelistValidatorForProgram => {
processor::process_whitelist_validator_for_program(program_id, accounts, data)?
}
discriminator::DlpDiscriminator::TopUpEphemeralBalance => {
DlpDiscriminator::TopUpEphemeralBalance => {
processor::process_top_up_ephemeral_balance(program_id, accounts, data)?
}
discriminator::DlpDiscriminator::DelegateEphemeralBalance => {
DlpDiscriminator::DelegateEphemeralBalance => {
processor::process_delegate_ephemeral_balance(program_id, accounts, data)?
}
discriminator::DlpDiscriminator::CloseEphemeralBalance => {
DlpDiscriminator::CloseEphemeralBalance => {
processor::process_close_ephemeral_balance(program_id, accounts, data)?
}
discriminator::DlpDiscriminator::ProtocolClaimFees => {
DlpDiscriminator::ProtocolClaimFees => {
processor::process_protocol_claim_fees(program_id, accounts, data)?
}
discriminator::DlpDiscriminator::CloseValidatorFeesVault => {
DlpDiscriminator::CloseValidatorFeesVault => {
processor::process_close_validator_fees_vault(program_id, accounts, data)?
}
discriminator::DlpDiscriminator::CallHandler => {
process_call_handler(program_id, accounts, data)?
DlpDiscriminator::CallHandler => {
processor::process_call_handler(program_id, accounts, data)?
}
_ => {
log!("PANIC: Instruction must be processed by fast_process_instruction");
Expand Down