spl_record/
processor.rs

1//! Program state processor
2
3use {
4    crate::{error::RecordError, instruction::RecordInstruction, state::RecordData},
5    solana_account_info::{next_account_info, AccountInfo},
6    solana_msg::msg,
7    solana_program_error::{ProgramError, ProgramResult},
8    solana_program_pack::IsInitialized,
9    solana_pubkey::Pubkey,
10};
11
12fn check_authority(authority_info: &AccountInfo, expected_authority: &Pubkey) -> ProgramResult {
13    if expected_authority != authority_info.key {
14        msg!("Incorrect record authority provided");
15        return Err(RecordError::IncorrectAuthority.into());
16    }
17    if !authority_info.is_signer {
18        msg!("Record authority signature missing");
19        return Err(ProgramError::MissingRequiredSignature);
20    }
21    Ok(())
22}
23
24/// Instruction processor
25pub fn process_instruction(
26    _program_id: &Pubkey,
27    accounts: &[AccountInfo],
28    input: &[u8],
29) -> ProgramResult {
30    let instruction = RecordInstruction::unpack(input)?;
31    let account_info_iter = &mut accounts.iter();
32
33    match instruction {
34        RecordInstruction::Initialize => {
35            msg!("RecordInstruction::Initialize");
36
37            let data_info = next_account_info(account_info_iter)?;
38            let authority_info = next_account_info(account_info_iter)?;
39
40            let raw_data = &mut data_info.data.borrow_mut();
41            if raw_data.len() < RecordData::WRITABLE_START_INDEX {
42                return Err(ProgramError::InvalidAccountData);
43            }
44
45            let account_data = bytemuck::try_from_bytes_mut::<RecordData>(
46                &mut raw_data[..RecordData::WRITABLE_START_INDEX],
47            )
48            .map_err(|_| ProgramError::InvalidArgument)?;
49            if account_data.is_initialized() {
50                msg!("Record account already initialized");
51                return Err(ProgramError::AccountAlreadyInitialized);
52            }
53
54            account_data.authority = *authority_info.key;
55            account_data.version = RecordData::CURRENT_VERSION;
56            Ok(())
57        }
58
59        RecordInstruction::Write { offset, data } => {
60            msg!("RecordInstruction::Write");
61            let data_info = next_account_info(account_info_iter)?;
62            let authority_info = next_account_info(account_info_iter)?;
63            {
64                let raw_data = &data_info.data.borrow();
65                if raw_data.len() < RecordData::WRITABLE_START_INDEX {
66                    return Err(ProgramError::InvalidAccountData);
67                }
68                let account_data = bytemuck::try_from_bytes::<RecordData>(
69                    &raw_data[..RecordData::WRITABLE_START_INDEX],
70                )
71                .map_err(|_| ProgramError::InvalidArgument)?;
72                if !account_data.is_initialized() {
73                    msg!("Record account not initialized");
74                    return Err(ProgramError::UninitializedAccount);
75                }
76                check_authority(authority_info, &account_data.authority)?;
77            }
78            let start = RecordData::WRITABLE_START_INDEX.saturating_add(offset as usize);
79            let end = start.saturating_add(data.len());
80            if end > data_info.data.borrow().len() {
81                Err(ProgramError::AccountDataTooSmall)
82            } else {
83                data_info.data.borrow_mut()[start..end].copy_from_slice(data);
84                Ok(())
85            }
86        }
87
88        RecordInstruction::SetAuthority => {
89            msg!("RecordInstruction::SetAuthority");
90            let data_info = next_account_info(account_info_iter)?;
91            let authority_info = next_account_info(account_info_iter)?;
92            let new_authority_info = next_account_info(account_info_iter)?;
93            let raw_data = &mut data_info.data.borrow_mut();
94            if raw_data.len() < RecordData::WRITABLE_START_INDEX {
95                return Err(ProgramError::InvalidAccountData);
96            }
97            let account_data = bytemuck::try_from_bytes_mut::<RecordData>(
98                &mut raw_data[..RecordData::WRITABLE_START_INDEX],
99            )
100            .map_err(|_| ProgramError::InvalidArgument)?;
101            if !account_data.is_initialized() {
102                msg!("Record account not initialized");
103                return Err(ProgramError::UninitializedAccount);
104            }
105            check_authority(authority_info, &account_data.authority)?;
106            account_data.authority = *new_authority_info.key;
107            Ok(())
108        }
109
110        RecordInstruction::CloseAccount => {
111            msg!("RecordInstruction::CloseAccount");
112            let data_info = next_account_info(account_info_iter)?;
113            let authority_info = next_account_info(account_info_iter)?;
114            let destination_info = next_account_info(account_info_iter)?;
115            let raw_data = &mut data_info.data.borrow_mut();
116            if raw_data.len() < RecordData::WRITABLE_START_INDEX {
117                return Err(ProgramError::InvalidAccountData);
118            }
119            let account_data = bytemuck::try_from_bytes_mut::<RecordData>(
120                &mut raw_data[..RecordData::WRITABLE_START_INDEX],
121            )
122            .map_err(|_| ProgramError::InvalidArgument)?;
123            if !account_data.is_initialized() {
124                msg!("Record not initialized");
125                return Err(ProgramError::UninitializedAccount);
126            }
127            check_authority(authority_info, &account_data.authority)?;
128            let destination_starting_lamports = destination_info.lamports();
129            let data_lamports = data_info.lamports();
130            **data_info.lamports.borrow_mut() = 0;
131            **destination_info.lamports.borrow_mut() = destination_starting_lamports
132                .checked_add(data_lamports)
133                .ok_or(RecordError::Overflow)?;
134            Ok(())
135        }
136
137        RecordInstruction::Reallocate { data_length } => {
138            msg!("RecordInstruction::Reallocate");
139            let data_info = next_account_info(account_info_iter)?;
140            let authority_info = next_account_info(account_info_iter)?;
141
142            {
143                let raw_data = &mut data_info.data.borrow_mut();
144                if raw_data.len() < RecordData::WRITABLE_START_INDEX {
145                    return Err(ProgramError::InvalidAccountData);
146                }
147                let account_data = bytemuck::try_from_bytes_mut::<RecordData>(
148                    &mut raw_data[..RecordData::WRITABLE_START_INDEX],
149                )
150                .map_err(|_| ProgramError::InvalidArgument)?;
151                if !account_data.is_initialized() {
152                    msg!("Record not initialized");
153                    return Err(ProgramError::UninitializedAccount);
154                }
155                check_authority(authority_info, &account_data.authority)?;
156            }
157
158            // needed account length is the sum of the meta data length and the specified
159            // data length
160            let needed_account_length = std::mem::size_of::<RecordData>()
161                .checked_add(
162                    usize::try_from(data_length).map_err(|_| ProgramError::InvalidArgument)?,
163                )
164                .unwrap();
165
166            // reallocate
167            if data_info.data_len() >= needed_account_length {
168                msg!("no additional reallocation needed");
169                return Ok(());
170            }
171            msg!(
172                "reallocating +{:?} bytes",
173                needed_account_length
174                    .checked_sub(data_info.data_len())
175                    .unwrap(),
176            );
177            data_info.resize(needed_account_length)?;
178            Ok(())
179        }
180    }
181}