magicsvm 0.2.1

A fast and lightweight Solana + MagicBlock VM simulator for testing solana programs
use {
    magicblock_magic_program_api::{
        args::{
            BaseActionArgs, CommitAndUndelegateArgs, CommitTypeArgs, MagicBaseIntentArgs,
            MagicIntentBundleArgs, UndelegateTypeArgs,
        },
        instruction::MagicBlockInstruction,
    },
    solana_transaction::InstructionError,
};

/// A simplified, decoded view of the magic program instructions that the
/// simulator cares about.
///
/// This is the result of normalizing the on-chain
/// [`MagicBlockInstruction`](magicblock_magic_program_api::instruction::MagicBlockInstruction)
/// into the subset of variants the simulator acts on; the account-index lists
/// reference positions within the instruction's account list.
pub enum MagicInstruction {
    /// Schedule a commit of the instruction's committee accounts.
    ScheduleCommit,
    /// Schedule a commit followed by undelegation of the committee accounts.
    ScheduleCommitAndUndelegate,
    /// Schedule a commit, optionally requesting undelegation.
    ScheduleCommitFinalize { request_undelegation: bool },
    /// Schedule a base-layer intent, listing accounts to commit and undelegate.
    ScheduleBaseIntent {
        committed_accounts: Vec<u8>,
        undelegated_accounts: Vec<u8>,
        base_actions: Vec<BaseActionArgs>,
    },
    /// Schedule a bundle of intents, listing accounts to commit and undelegate.
    ScheduleIntentBundle {
        committed_accounts: Vec<u8>,
        undelegated_accounts: Vec<u8>,
        base_actions: Vec<BaseActionArgs>,
    },
    /// Create an ephemeral account of `data_len` bytes.
    CreateEphemeralAccount { data_len: u32 },
    /// Resize an ephemeral account to `new_data_len` bytes.
    ResizeEphemeralAccount { new_data_len: u32 },
    /// Close an ephemeral account.
    CloseEphemeralAccount,
    /// No operation.
    Noop,
}

/// Decodes raw instruction `data` into a [`MagicInstruction`].
///
/// Returns [`InstructionError::InvalidInstructionData`] if the data cannot be
/// deserialized or names an instruction the simulator does not handle.
pub fn magic_instruction(data: &[u8]) -> Result<MagicInstruction, InstructionError> {
    let instruction: MagicBlockInstruction =
        bincode::deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)?;
    match instruction {
        MagicBlockInstruction::ScheduleCommit => Ok(MagicInstruction::ScheduleCommit),
        MagicBlockInstruction::ScheduleCommitAndUndelegate => {
            Ok(MagicInstruction::ScheduleCommitAndUndelegate)
        }
        MagicBlockInstruction::ScheduleCommitFinalize {
            request_undelegation,
        } => Ok(MagicInstruction::ScheduleCommitFinalize {
            request_undelegation,
        }),
        MagicBlockInstruction::ScheduleBaseIntent(args) => schedule_base_intent(args),
        MagicBlockInstruction::ScheduleIntentBundle(args) => schedule_intent_bundle(args),
        MagicBlockInstruction::CreateEphemeralAccount { data_len } => {
            Ok(MagicInstruction::CreateEphemeralAccount { data_len })
        }
        MagicBlockInstruction::ResizeEphemeralAccount { new_data_len } => {
            Ok(MagicInstruction::ResizeEphemeralAccount { new_data_len })
        }
        MagicBlockInstruction::CloseEphemeralAccount => Ok(MagicInstruction::CloseEphemeralAccount),
        MagicBlockInstruction::Noop(_) => Ok(MagicInstruction::Noop),
        _ => Err(InstructionError::InvalidInstructionData),
    }
}

fn schedule_base_intent(args: MagicBaseIntentArgs) -> Result<MagicInstruction, InstructionError> {
    match args {
        MagicBaseIntentArgs::BaseActions(base_actions) => {
            Ok(MagicInstruction::ScheduleBaseIntent {
                committed_accounts: Vec::new(),
                undelegated_accounts: Vec::new(),
                base_actions,
            })
        }
        MagicBaseIntentArgs::Commit(commit_type)
        | MagicBaseIntentArgs::CommitFinalize(commit_type) => {
            let (committed_accounts, base_actions) = commit_type_parts(commit_type);
            Ok(MagicInstruction::ScheduleBaseIntent {
                committed_accounts,
                undelegated_accounts: Vec::new(),
                base_actions,
            })
        }
        MagicBaseIntentArgs::CommitAndUndelegate(args)
        | MagicBaseIntentArgs::CommitFinalizeAndUndelegate(args) => {
            let (undelegated_accounts, base_actions) = commit_and_undelegate_parts(args);
            Ok(MagicInstruction::ScheduleBaseIntent {
                committed_accounts: Vec::new(),
                undelegated_accounts,
                base_actions,
            })
        }
    }
}

fn schedule_intent_bundle(
    args: MagicIntentBundleArgs,
) -> Result<MagicInstruction, InstructionError> {
    let mut committed_accounts = Vec::new();
    let mut undelegated_accounts = Vec::new();
    let mut base_actions = Vec::new();

    if let Some(commit_type) = args.commit {
        let (indices, actions) = commit_type_parts(commit_type);
        committed_accounts.extend(indices);
        base_actions.extend(actions);
    }
    if let Some(args) = args.commit_and_undelegate {
        let (indices, actions) = commit_and_undelegate_parts(args);
        undelegated_accounts.extend(indices);
        base_actions.extend(actions);
    }
    if let Some(commit_type) = args.commit_finalize {
        let (indices, actions) = commit_type_parts(commit_type);
        committed_accounts.extend(indices);
        base_actions.extend(actions);
    }
    if let Some(args) = args.commit_finalize_and_undelegate {
        let (indices, actions) = commit_and_undelegate_parts(args);
        undelegated_accounts.extend(indices);
        base_actions.extend(actions);
    }
    base_actions.extend(args.standalone_actions);

    Ok(MagicInstruction::ScheduleIntentBundle {
        committed_accounts,
        undelegated_accounts,
        base_actions,
    })
}

fn commit_type_parts(commit_type: CommitTypeArgs) -> (Vec<u8>, Vec<BaseActionArgs>) {
    match commit_type {
        CommitTypeArgs::Standalone(indices) => (indices, Vec::new()),
        CommitTypeArgs::WithBaseActions {
            committed_accounts,
            base_actions,
        } => (committed_accounts, base_actions),
    }
}

fn commit_and_undelegate_parts(args: CommitAndUndelegateArgs) -> (Vec<u8>, Vec<BaseActionArgs>) {
    let (indices, mut actions) = commit_type_parts(args.commit_type);
    actions.extend(match args.undelegate_type {
        UndelegateTypeArgs::Standalone => Vec::new(),
        UndelegateTypeArgs::WithBaseActions { base_actions } => base_actions,
    });
    (indices, actions)
}