use core::mem::size_of;
use solana_instruction::{AccountMeta, Instruction};
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
use crate::instruction::InstructionInputParsing;
use crate::SettlementInstruction;
pub struct RemoveSolver {
pub program_id: Pubkey,
pub manager: Pubkey,
pub rent_recipient: Pubkey,
pub state_pda: Pubkey,
pub solver: Pubkey,
}
impl From<RemoveSolver> for Instruction {
fn from(builder: RemoveSolver) -> Self {
let mut data = vec![SettlementInstruction::RemoveSolver.discriminator()];
data.extend_from_slice(&builder.solver.to_bytes());
Instruction {
program_id: builder.program_id,
accounts: vec![
AccountMeta::new_readonly(builder.manager, true),
AccountMeta::new(builder.rent_recipient, false),
AccountMeta::new(builder.state_pda, false),
],
data,
}
}
}
pub struct RemoveSolverInput<'a, A> {
pub manager: &'a A,
pub rent_recipient: &'a A,
pub state_pda: &'a A,
pub solver: Pubkey,
}
impl<'a, A> InstructionInputParsing<'a, A> for RemoveSolverInput<'a, A> {
const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::RemoveSolver;
fn parse_body(instruction_data: &[u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
let solver: &[u8; size_of::<Pubkey>()] = instruction_data
.try_into()
.map_err(|_| ProgramError::InvalidInstructionData)?;
let solver = Pubkey::new_from_array(*solver);
let [manager, rent_recipient, state_pda, ..] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
Ok(Self {
manager,
rent_recipient,
state_pda,
solver,
})
}
}
#[cfg(any(test, feature = "test-fixtures"))]
pub mod fixtures {
use solana_address::Address;
use super::{Instruction, RemoveSolver};
pub const NUM_ACCOUNTS: usize = 3;
pub fn remove_solver_data() -> Vec<u8> {
let zero = Address::new_from_array([0; 32]);
Instruction::from(RemoveSolver {
program_id: zero,
manager: zero,
rent_recipient: zero,
state_pda: zero,
solver: zero,
})
.data
}
}
#[cfg(test)]
mod tests {
use super::fixtures::{remove_solver_data, NUM_ACCOUNTS};
use super::*;
use crate::fixtures::pubkey_from_seed;
use crate::instruction::fixtures::{fake_account, fake_sequential_accounts};
use crate::instruction::tests::{assert_readonly_signer, assert_writable_nonsigner};
use solana_account_view::AccountView;
#[test]
fn remove_solver_input_parses_valid_input() {
let program_id = pubkey_from_seed("program id");
let manager = fake_account(pubkey_from_seed("manager"));
let rent_recipient = fake_account(pubkey_from_seed("rent recipient"));
let state_pda = fake_account(pubkey_from_seed("state pda"));
let solver = pubkey_from_seed("solver");
let data = Instruction::from(RemoveSolver {
program_id,
manager: *manager.address(),
rent_recipient: *rent_recipient.address(),
state_pda: *state_pda.address(),
solver,
})
.data;
let accounts = [manager, rent_recipient, state_pda];
let RemoveSolverInput {
manager: parsed_manager,
rent_recipient: parsed_rent_recipient,
state_pda: parsed_state_pda,
solver: parsed_solver,
} = RemoveSolverInput::parse(&data, &accounts).expect("parse should succeed");
assert_eq!(parsed_manager.address(), accounts[0].address());
assert_eq!(parsed_rent_recipient.address(), accounts[1].address());
assert_eq!(parsed_state_pda.address(), accounts[2].address());
assert_eq!(parsed_solver, solver);
}
#[test]
fn remove_solver_input_rejects_long_data() {
let mut data = remove_solver_data();
data.push(0); let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
assert_eq!(
RemoveSolverInput::parse(&data, &accounts).err(),
Some(ProgramError::InvalidInstructionData),
);
}
#[test]
fn remove_solver_input_rejects_short_data() {
let mut data = remove_solver_data();
data.pop(); let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
assert_eq!(
RemoveSolverInput::parse(&data, &accounts).err(),
Some(ProgramError::InvalidInstructionData),
);
}
#[test]
fn remove_solver_input_rejects_missing_accounts() {
let data = remove_solver_data();
let mut accounts: Vec<AccountView> = fake_sequential_accounts::<NUM_ACCOUNTS>().into();
accounts.pop();
assert_eq!(
RemoveSolverInput::parse(&data, &accounts).err(),
Some(ProgramError::NotEnoughAccountKeys),
);
}
#[test]
fn instruction_data_has_expected_layout() {
let solver = pubkey_from_seed("solver");
let Instruction { data, .. } = RemoveSolver {
program_id: pubkey_from_seed("program id"),
manager: pubkey_from_seed("manager"),
rent_recipient: pubkey_from_seed("rent recipient"),
state_pda: pubkey_from_seed("state pda"),
solver,
}
.into();
assert_eq!(data.len(), 1 + size_of::<Pubkey>());
assert_eq!(data[0], SettlementInstruction::RemoveSolver.discriminator());
assert_eq!(&data[1..], &solver.to_bytes());
}
#[test]
fn instruction_data_regression() {
let solver = Pubkey::new_from_array([0x11; 32]);
let Instruction { data, .. } = RemoveSolver {
program_id: pubkey_from_seed("program id"),
manager: pubkey_from_seed("manager"),
rent_recipient: pubkey_from_seed("rent recipient"),
state_pda: pubkey_from_seed("state pda"),
solver,
}
.into();
#[rustfmt::skip]
let expected: [u8; 1 + size_of::<Pubkey>()] = [
0x09,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
];
assert_eq!(data, expected);
}
#[test]
fn instruction_has_expected_accounts() {
let manager = pubkey_from_seed("manager");
let rent_recipient = pubkey_from_seed("rent recipient");
let state_pda = pubkey_from_seed("state pda");
let Instruction { accounts, .. } = RemoveSolver {
program_id: pubkey_from_seed("program id"),
manager,
rent_recipient,
state_pda,
solver: pubkey_from_seed("solver"),
}
.into();
assert_eq!(accounts.len(), 3);
assert_readonly_signer(&accounts[0], manager);
assert_writable_nonsigner(&accounts[1], rent_recipient);
assert_writable_nonsigner(&accounts[2], state_pda);
}
}