spl_record/
instruction.rs

1//! Program instructions
2
3use {
4    crate::id,
5    solana_instruction::{AccountMeta, Instruction},
6    solana_program_error::ProgramError,
7    solana_pubkey::Pubkey,
8    std::mem::size_of,
9};
10
11/// Instructions supported by the program
12#[derive(Clone, Debug, PartialEq)]
13pub enum RecordInstruction<'a> {
14    /// Create a new record
15    ///
16    /// Accounts expected by this instruction:
17    ///
18    /// 0. `[writable]` Record account, must be uninitialized
19    /// 1. `[]` Record authority
20    Initialize,
21
22    /// Write to the provided record account
23    ///
24    /// Accounts expected by this instruction:
25    ///
26    /// 0. `[writable]` Record account, must be previously initialized
27    /// 1. `[signer]` Current record authority
28    Write {
29        /// Offset to start writing record, expressed as `u64`.
30        offset: u64,
31        /// Data to replace the existing record data
32        data: &'a [u8],
33    },
34
35    /// Update the authority of the provided record account
36    ///
37    /// Accounts expected by this instruction:
38    ///
39    /// 0. `[writable]` Record account, must be previously initialized
40    /// 1. `[signer]` Current record authority
41    /// 2. `[]` New record authority
42    SetAuthority,
43
44    /// Close the provided record account, draining lamports to recipient
45    /// account
46    ///
47    /// Accounts expected by this instruction:
48    ///
49    /// 0. `[writable]` Record account, must be previously initialized
50    /// 1. `[signer]` Record authority
51    /// 2. `[]` Receiver of account lamports
52    CloseAccount,
53
54    /// Reallocate additional space in a record account
55    ///
56    /// If the record account already has enough space to hold the specified
57    /// data length, then the instruction does nothing.
58    ///
59    /// Accounts expected by this instruction:
60    ///
61    /// 0. `[writable]` The record account to reallocate
62    /// 1. `[signer]` The account's owner
63    Reallocate {
64        /// The length of the data to hold in the record account excluding meta
65        /// data
66        data_length: u64,
67    },
68}
69
70impl<'a> RecordInstruction<'a> {
71    /// Unpacks a byte buffer into a [`RecordInstruction`].
72    pub fn unpack(input: &'a [u8]) -> Result<Self, ProgramError> {
73        const U32_BYTES: usize = 4;
74        const U64_BYTES: usize = 8;
75
76        let (&tag, rest) = input
77            .split_first()
78            .ok_or(ProgramError::InvalidInstructionData)?;
79        Ok(match tag {
80            0 => Self::Initialize,
81            1 => {
82                let offset = rest
83                    .get(..U64_BYTES)
84                    .and_then(|slice| slice.try_into().ok())
85                    .map(u64::from_le_bytes)
86                    .ok_or(ProgramError::InvalidInstructionData)?;
87                let (length, data) = rest[U64_BYTES..].split_at(U32_BYTES);
88                let length = u32::from_le_bytes(
89                    length
90                        .try_into()
91                        .map_err(|_| ProgramError::InvalidInstructionData)?,
92                ) as usize;
93
94                Self::Write {
95                    offset,
96                    data: &data[..length],
97                }
98            }
99            2 => Self::SetAuthority,
100            3 => Self::CloseAccount,
101            4 => {
102                let data_length = rest
103                    .get(..U64_BYTES)
104                    .and_then(|slice| slice.try_into().ok())
105                    .map(u64::from_le_bytes)
106                    .ok_or(ProgramError::InvalidInstructionData)?;
107
108                Self::Reallocate { data_length }
109            }
110            _ => return Err(ProgramError::InvalidInstructionData),
111        })
112    }
113
114    /// Packs a [`RecordInstruction`] into a byte buffer.
115    pub fn pack(&self) -> Vec<u8> {
116        let mut buf = Vec::with_capacity(size_of::<Self>());
117        match self {
118            Self::Initialize => buf.push(0),
119            Self::Write { offset, data } => {
120                buf.push(1);
121                buf.extend_from_slice(&offset.to_le_bytes());
122                buf.extend_from_slice(&(data.len() as u32).to_le_bytes());
123                buf.extend_from_slice(data);
124            }
125            Self::SetAuthority => buf.push(2),
126            Self::CloseAccount => buf.push(3),
127            Self::Reallocate { data_length } => {
128                buf.push(4);
129                buf.extend_from_slice(&data_length.to_le_bytes());
130            }
131        };
132        buf
133    }
134}
135
136/// Create a `RecordInstruction::Initialize` instruction
137pub fn initialize(record_account: &Pubkey, authority: &Pubkey) -> Instruction {
138    Instruction {
139        program_id: id(),
140        accounts: vec![
141            AccountMeta::new(*record_account, false),
142            AccountMeta::new_readonly(*authority, false),
143        ],
144        data: RecordInstruction::Initialize.pack(),
145    }
146}
147
148/// Create a `RecordInstruction::Write` instruction
149pub fn write(record_account: &Pubkey, signer: &Pubkey, offset: u64, data: &[u8]) -> Instruction {
150    Instruction {
151        program_id: id(),
152        accounts: vec![
153            AccountMeta::new(*record_account, false),
154            AccountMeta::new_readonly(*signer, true),
155        ],
156        data: RecordInstruction::Write { offset, data }.pack(),
157    }
158}
159
160/// Create a `RecordInstruction::SetAuthority` instruction
161pub fn set_authority(
162    record_account: &Pubkey,
163    signer: &Pubkey,
164    new_authority: &Pubkey,
165) -> Instruction {
166    Instruction {
167        program_id: id(),
168        accounts: vec![
169            AccountMeta::new(*record_account, false),
170            AccountMeta::new_readonly(*signer, true),
171            AccountMeta::new_readonly(*new_authority, false),
172        ],
173        data: RecordInstruction::SetAuthority.pack(),
174    }
175}
176
177/// Create a `RecordInstruction::CloseAccount` instruction
178pub fn close_account(record_account: &Pubkey, signer: &Pubkey, receiver: &Pubkey) -> Instruction {
179    Instruction {
180        program_id: id(),
181        accounts: vec![
182            AccountMeta::new(*record_account, false),
183            AccountMeta::new_readonly(*signer, true),
184            AccountMeta::new(*receiver, false),
185        ],
186        data: RecordInstruction::CloseAccount.pack(),
187    }
188}
189
190/// Create a `RecordInstruction::Reallocate` instruction
191pub fn reallocate(record_account: &Pubkey, signer: &Pubkey, data_length: u64) -> Instruction {
192    Instruction {
193        program_id: id(),
194        accounts: vec![
195            AccountMeta::new(*record_account, false),
196            AccountMeta::new_readonly(*signer, true),
197        ],
198        data: RecordInstruction::Reallocate { data_length }.pack(),
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use {super::*, crate::state::tests::TEST_BYTES, solana_program_error::ProgramError};
205
206    #[test]
207    fn serialize_initialize() {
208        let instruction = RecordInstruction::Initialize;
209        let expected = vec![0];
210        assert_eq!(instruction.pack(), expected);
211        assert_eq!(RecordInstruction::unpack(&expected).unwrap(), instruction);
212    }
213
214    #[test]
215    fn serialize_write() {
216        let data = &TEST_BYTES;
217        let offset = 0u64;
218        let instruction = RecordInstruction::Write { offset: 0, data };
219        let mut expected = vec![1];
220        expected.extend_from_slice(&offset.to_le_bytes());
221        expected.extend_from_slice(&(data.len() as u32).to_le_bytes());
222        expected.extend_from_slice(data);
223        assert_eq!(instruction.pack(), expected);
224        assert_eq!(RecordInstruction::unpack(&expected).unwrap(), instruction);
225    }
226
227    #[test]
228    fn serialize_set_authority() {
229        let instruction = RecordInstruction::SetAuthority;
230        let expected = vec![2];
231        assert_eq!(instruction.pack(), expected);
232        assert_eq!(RecordInstruction::unpack(&expected).unwrap(), instruction);
233    }
234
235    #[test]
236    fn serialize_close_account() {
237        let instruction = RecordInstruction::CloseAccount;
238        let expected = vec![3];
239        assert_eq!(instruction.pack(), expected);
240        assert_eq!(RecordInstruction::unpack(&expected).unwrap(), instruction);
241    }
242
243    #[test]
244    fn serialize_reallocate() {
245        let data_length = 16u64;
246        let instruction = RecordInstruction::Reallocate { data_length };
247        let mut expected = vec![4];
248        expected.extend_from_slice(&data_length.to_le_bytes());
249        assert_eq!(instruction.pack(), expected);
250        assert_eq!(RecordInstruction::unpack(&expected).unwrap(), instruction);
251    }
252
253    #[test]
254    fn deserialize_invalid_instruction() {
255        let mut expected = vec![12];
256        expected.extend_from_slice(&TEST_BYTES);
257        let err: ProgramError = RecordInstruction::unpack(&expected).unwrap_err();
258        assert_eq!(err, ProgramError::InvalidInstructionData);
259    }
260}