magicsvm 0.2.1

A fast and lightweight Solana + MagicBlock VM simulator for testing solana programs
use {
    ephemeral_rollups_sdk::consts::{MAGIC_CONTEXT_ID, MAGIC_PROGRAM_ID},
    solana_transaction::InstructionError,
    solana_transaction_context::{instruction::InstructionContext, IndexOfAccount},
};

const MAGIC_PAYER_IDX: IndexOfAccount = 0;
const MAGIC_CONTEXT_IDX: IndexOfAccount = 1;
const MAGIC_COMMITTEES_START_IDX: IndexOfAccount = 2;

pub(super) fn process_schedule_commit(
    instruction_context: &InstructionContext<'_, '_>,
    request_undelegation: bool,
) -> Result<(), InstructionError> {
    validate_magic_schedule_header(instruction_context)?;
    let account_count = instruction_context.get_number_of_instruction_accounts();
    if account_count <= MAGIC_COMMITTEES_START_IDX {
        return Err(InstructionError::MissingAccount);
    }

    let account_indices: Vec<_> = (MAGIC_COMMITTEES_START_IDX..account_count).collect();
    validate_commit_accounts(instruction_context, &account_indices, request_undelegation)
}

pub(super) fn process_schedule_commit_intent(
    instruction_context: &InstructionContext<'_, '_>,
    committed_accounts: &[u8],
    undelegated_accounts: &[u8],
) -> Result<(), InstructionError> {
    validate_magic_schedule_header(instruction_context)?;
    let committed_accounts: Vec<_> = committed_accounts
        .iter()
        .map(|index| u16::from(*index))
        .collect();
    let undelegated_accounts: Vec<_> = undelegated_accounts
        .iter()
        .map(|index| u16::from(*index))
        .collect();

    validate_commit_accounts(instruction_context, &committed_accounts, false)?;
    validate_commit_accounts(instruction_context, &undelegated_accounts, true)
}

fn validate_magic_schedule_header(
    instruction_context: &InstructionContext<'_, '_>,
) -> Result<(), InstructionError> {
    if instruction_context.get_program_key()? != &MAGIC_PROGRAM_ID {
        return Err(InstructionError::UnsupportedProgramId);
    }
    instruction_context.check_number_of_instruction_accounts(MAGIC_CONTEXT_IDX + 1)?;
    if instruction_context.get_key_of_instruction_account(MAGIC_CONTEXT_IDX)? != &MAGIC_CONTEXT_ID {
        return Err(InstructionError::MissingAccount);
    }
    if !instruction_context.is_instruction_account_signer(MAGIC_PAYER_IDX)? {
        return Err(InstructionError::MissingRequiredSignature);
    }
    if !instruction_context.is_instruction_account_writable(MAGIC_CONTEXT_IDX)? {
        return Err(InstructionError::ReadonlyDataModified);
    }
    Ok(())
}

fn validate_commit_accounts(
    instruction_context: &InstructionContext<'_, '_>,
    account_indices: &[IndexOfAccount],
    request_undelegation: bool,
) -> Result<(), InstructionError> {
    if account_indices.is_empty() {
        return Ok(());
    }

    for account_index in account_indices {
        let account = instruction_context.try_borrow_instruction_account(*account_index)?;
        if account.get_key() == &MAGIC_CONTEXT_ID || account.get_key() == &MAGIC_PROGRAM_ID {
            return Err(InstructionError::MissingAccount);
        }
        if request_undelegation
            && !instruction_context.is_instruction_account_writable(*account_index)?
        {
            return Err(InstructionError::ReadonlyDataModified);
        }
    }
    Ok(())
}