Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit f36c2fb

Browse files
[spl-record] Remove borsh dependency from spl-record program (#6054)
1 parent ac239e5 commit f36c2fb

File tree

6 files changed

+201
-138
lines changed

6 files changed

+201
-138
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

record/program/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ no-entrypoint = []
1212
test-sbf = []
1313

1414
[dependencies]
15-
borsh = "0.10"
15+
bytemuck = { version = "1.14.0", features = ["derive"] }
1616
num-derive = "0.4"
1717
num-traits = "0.2"
1818
solana-program = "1.17.6"
1919
thiserror = "1.0"
20+
spl-pod = { version = "0.1", path = "../../libraries/pod" }
2021

2122
[dev-dependencies]
2223
solana-program-test = "1.17.6"

record/program/src/instruction.rs

Lines changed: 97 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22
33
use {
44
crate::id,
5-
borsh::{BorshDeserialize, BorshSerialize},
65
solana_program::{
76
instruction::{AccountMeta, Instruction},
7+
program_error::ProgramError,
88
pubkey::Pubkey,
99
},
10+
std::mem::size_of,
1011
};
1112

1213
/// Instructions supported by the program
13-
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
14-
pub enum RecordInstruction {
14+
#[derive(Clone, Debug, PartialEq)]
15+
pub enum RecordInstruction<'a> {
1516
/// Create a new record
1617
///
1718
/// Accounts expected by this instruction:
@@ -30,7 +31,7 @@ pub enum RecordInstruction {
3031
/// Offset to start writing record, expressed as `u64`.
3132
offset: u64,
3233
/// Data to replace the existing record data
33-
data: Vec<u8>,
34+
data: &'a [u8],
3435
},
3536

3637
/// Update the authority of the provided record account
@@ -53,28 +54,80 @@ pub enum RecordInstruction {
5354
CloseAccount,
5455
}
5556

57+
impl<'a> RecordInstruction<'a> {
58+
/// Unpacks a byte buffer into a [RecordInstruction].
59+
pub fn unpack(input: &'a [u8]) -> Result<Self, ProgramError> {
60+
let (&tag, rest) = input
61+
.split_first()
62+
.ok_or(ProgramError::InvalidInstructionData)?;
63+
Ok(match tag {
64+
0 => Self::Initialize,
65+
1 => {
66+
const U32_BYTES: usize = 4;
67+
const U64_BYTES: usize = 8;
68+
let offset = rest
69+
.get(..U64_BYTES)
70+
.and_then(|slice| slice.try_into().ok())
71+
.map(u64::from_le_bytes)
72+
.ok_or(ProgramError::InvalidInstructionData)?;
73+
let (length, data) = rest[U64_BYTES..].split_at(U32_BYTES);
74+
let length = u32::from_le_bytes(
75+
length
76+
.try_into()
77+
.map_err(|_| ProgramError::InvalidInstructionData)?,
78+
) as usize;
79+
80+
Self::Write {
81+
offset,
82+
data: &data[..length],
83+
}
84+
}
85+
2 => Self::SetAuthority,
86+
3 => Self::CloseAccount,
87+
_ => return Err(ProgramError::InvalidInstructionData),
88+
})
89+
}
90+
91+
/// Packs a [RecordInstruction] into a byte buffer.
92+
pub fn pack(&self) -> Vec<u8> {
93+
let mut buf = Vec::with_capacity(size_of::<Self>());
94+
match self {
95+
Self::Initialize => buf.push(0),
96+
Self::Write { offset, data } => {
97+
buf.push(1);
98+
buf.extend_from_slice(&offset.to_le_bytes());
99+
buf.extend_from_slice(&(data.len() as u32).to_le_bytes());
100+
buf.extend_from_slice(data);
101+
}
102+
Self::SetAuthority => buf.push(2),
103+
Self::CloseAccount => buf.push(3),
104+
};
105+
buf
106+
}
107+
}
108+
56109
/// Create a `RecordInstruction::Initialize` instruction
57110
pub fn initialize(record_account: &Pubkey, authority: &Pubkey) -> Instruction {
58-
Instruction::new_with_borsh(
59-
id(),
60-
&RecordInstruction::Initialize,
61-
vec![
111+
Instruction {
112+
program_id: id(),
113+
accounts: vec![
62114
AccountMeta::new(*record_account, false),
63115
AccountMeta::new_readonly(*authority, false),
64116
],
65-
)
117+
data: RecordInstruction::Initialize.pack(),
118+
}
66119
}
67120

68121
/// Create a `RecordInstruction::Write` instruction
69-
pub fn write(record_account: &Pubkey, signer: &Pubkey, offset: u64, data: Vec<u8>) -> Instruction {
70-
Instruction::new_with_borsh(
71-
id(),
72-
&RecordInstruction::Write { offset, data },
73-
vec![
122+
pub fn write(record_account: &Pubkey, signer: &Pubkey, offset: u64, data: &[u8]) -> Instruction {
123+
Instruction {
124+
program_id: id(),
125+
accounts: vec![
74126
AccountMeta::new(*record_account, false),
75127
AccountMeta::new_readonly(*signer, true),
76128
],
77-
)
129+
data: RecordInstruction::Write { offset, data }.pack(),
130+
}
78131
}
79132

80133
/// Create a `RecordInstruction::SetAuthority` instruction
@@ -83,92 +136,79 @@ pub fn set_authority(
83136
signer: &Pubkey,
84137
new_authority: &Pubkey,
85138
) -> Instruction {
86-
Instruction::new_with_borsh(
87-
id(),
88-
&RecordInstruction::SetAuthority,
89-
vec![
139+
Instruction {
140+
program_id: id(),
141+
accounts: vec![
90142
AccountMeta::new(*record_account, false),
91143
AccountMeta::new_readonly(*signer, true),
92144
AccountMeta::new_readonly(*new_authority, false),
93145
],
94-
)
146+
data: RecordInstruction::SetAuthority.pack(),
147+
}
95148
}
96149

97150
/// Create a `RecordInstruction::CloseAccount` instruction
98151
pub fn close_account(record_account: &Pubkey, signer: &Pubkey, receiver: &Pubkey) -> Instruction {
99-
Instruction::new_with_borsh(
100-
id(),
101-
&RecordInstruction::CloseAccount,
102-
vec![
152+
Instruction {
153+
program_id: id(),
154+
accounts: vec![
103155
AccountMeta::new(*record_account, false),
104156
AccountMeta::new_readonly(*signer, true),
105157
AccountMeta::new(*receiver, false),
106158
],
107-
)
159+
data: RecordInstruction::CloseAccount.pack(),
160+
}
108161
}
109162

110163
#[cfg(test)]
111164
mod tests {
112-
use {super::*, crate::state::tests::TEST_DATA, solana_program::program_error::ProgramError};
165+
use {
166+
super::*, crate::state::tests::TEST_DATA, solana_program::program_error::ProgramError,
167+
spl_pod::bytemuck::pod_bytes_of,
168+
};
113169

114170
#[test]
115171
fn serialize_initialize() {
116172
let instruction = RecordInstruction::Initialize;
117173
let expected = vec![0];
118-
assert_eq!(instruction.try_to_vec().unwrap(), expected);
119-
assert_eq!(
120-
RecordInstruction::try_from_slice(&expected).unwrap(),
121-
instruction
122-
);
174+
assert_eq!(instruction.pack(), expected);
175+
assert_eq!(RecordInstruction::unpack(&expected).unwrap(), instruction);
123176
}
124177

125178
#[test]
126179
fn serialize_write() {
127-
let data = TEST_DATA.try_to_vec().unwrap();
180+
let data = pod_bytes_of(&TEST_DATA);
128181
let offset = 0u64;
129-
let instruction = RecordInstruction::Write {
130-
offset: 0,
131-
data: data.clone(),
132-
};
182+
let instruction = RecordInstruction::Write { offset: 0, data };
133183
let mut expected = vec![1];
134184
expected.extend_from_slice(&offset.to_le_bytes());
135-
expected.append(&mut data.try_to_vec().unwrap());
136-
assert_eq!(instruction.try_to_vec().unwrap(), expected);
137-
assert_eq!(
138-
RecordInstruction::try_from_slice(&expected).unwrap(),
139-
instruction
140-
);
185+
expected.extend_from_slice(&(data.len() as u32).to_le_bytes());
186+
expected.extend_from_slice(data);
187+
assert_eq!(instruction.pack(), expected);
188+
assert_eq!(RecordInstruction::unpack(&expected).unwrap(), instruction);
141189
}
142190

143191
#[test]
144192
fn serialize_set_authority() {
145193
let instruction = RecordInstruction::SetAuthority;
146194
let expected = vec![2];
147-
assert_eq!(instruction.try_to_vec().unwrap(), expected);
148-
assert_eq!(
149-
RecordInstruction::try_from_slice(&expected).unwrap(),
150-
instruction
151-
);
195+
assert_eq!(instruction.pack(), expected);
196+
assert_eq!(RecordInstruction::unpack(&expected).unwrap(), instruction);
152197
}
153198

154199
#[test]
155200
fn serialize_close_account() {
156201
let instruction = RecordInstruction::CloseAccount;
157202
let expected = vec![3];
158-
assert_eq!(instruction.try_to_vec().unwrap(), expected);
159-
assert_eq!(
160-
RecordInstruction::try_from_slice(&expected).unwrap(),
161-
instruction
162-
);
203+
assert_eq!(instruction.pack(), expected);
204+
assert_eq!(RecordInstruction::unpack(&expected).unwrap(), instruction);
163205
}
164206

165207
#[test]
166208
fn deserialize_invalid_instruction() {
167209
let mut expected = vec![12];
168-
expected.append(&mut TEST_DATA.try_to_vec().unwrap());
169-
let err: ProgramError = RecordInstruction::try_from_slice(&expected)
170-
.unwrap_err()
171-
.into();
172-
assert!(matches!(err, ProgramError::BorshIoError(_)));
210+
expected.append(&mut pod_bytes_of(&TEST_DATA).to_vec());
211+
let err: ProgramError = RecordInstruction::unpack(&expected).unwrap_err();
212+
assert_eq!(err, ProgramError::InvalidInstructionData);
173213
}
174214
}

record/program/src/processor.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use {
66
instruction::RecordInstruction,
77
state::{Data, RecordData},
88
},
9-
borsh::BorshDeserialize,
109
solana_program::{
1110
account_info::{next_account_info, AccountInfo},
1211
entrypoint::ProgramResult,
@@ -15,6 +14,7 @@ use {
1514
program_pack::IsInitialized,
1615
pubkey::Pubkey,
1716
},
17+
spl_pod::bytemuck::{pod_from_bytes, pod_from_bytes_mut},
1818
};
1919

2020
fn check_authority(authority_info: &AccountInfo, expected_authority: &Pubkey) -> ProgramResult {
@@ -35,7 +35,7 @@ pub fn process_instruction(
3535
accounts: &[AccountInfo],
3636
input: &[u8],
3737
) -> ProgramResult {
38-
let instruction = RecordInstruction::try_from_slice(input)?;
38+
let instruction = RecordInstruction::unpack(input)?;
3939
let account_info_iter = &mut accounts.iter();
4040

4141
match instruction {
@@ -45,34 +45,37 @@ pub fn process_instruction(
4545
let data_info = next_account_info(account_info_iter)?;
4646
let authority_info = next_account_info(account_info_iter)?;
4747

48-
let mut account_data = RecordData::try_from_slice(*data_info.data.borrow())?;
48+
let raw_data = &mut data_info.data.borrow_mut();
49+
let account_data = pod_from_bytes_mut::<RecordData>(raw_data)?;
4950
if account_data.is_initialized() {
5051
msg!("Record account already initialized");
5152
return Err(ProgramError::AccountAlreadyInitialized);
5253
}
5354

5455
account_data.authority = *authority_info.key;
5556
account_data.version = RecordData::CURRENT_VERSION;
56-
borsh::to_writer(&mut data_info.data.borrow_mut()[..], &account_data)
57-
.map_err(|e| e.into())
57+
Ok(())
5858
}
5959

6060
RecordInstruction::Write { offset, data } => {
6161
msg!("RecordInstruction::Write");
6262
let data_info = next_account_info(account_info_iter)?;
6363
let authority_info = next_account_info(account_info_iter)?;
64-
let account_data = RecordData::try_from_slice(&data_info.data.borrow())?;
65-
if !account_data.is_initialized() {
66-
msg!("Record account not initialized");
67-
return Err(ProgramError::UninitializedAccount);
64+
{
65+
let raw_data = &data_info.data.borrow();
66+
let account_data = pod_from_bytes::<RecordData>(raw_data)?;
67+
if !account_data.is_initialized() {
68+
msg!("Record account not initialized");
69+
return Err(ProgramError::UninitializedAccount);
70+
}
71+
check_authority(authority_info, &account_data.authority)?;
6872
}
69-
check_authority(authority_info, &account_data.authority)?;
7073
let start = RecordData::WRITABLE_START_INDEX.saturating_add(offset as usize);
7174
let end = start.saturating_add(data.len());
7275
if end > data_info.data.borrow().len() {
7376
Err(ProgramError::AccountDataTooSmall)
7477
} else {
75-
data_info.data.borrow_mut()[start..end].copy_from_slice(&data);
78+
data_info.data.borrow_mut()[start..end].copy_from_slice(data);
7679
Ok(())
7780
}
7881
}
@@ -82,23 +85,24 @@ pub fn process_instruction(
8285
let data_info = next_account_info(account_info_iter)?;
8386
let authority_info = next_account_info(account_info_iter)?;
8487
let new_authority_info = next_account_info(account_info_iter)?;
85-
let mut account_data = RecordData::try_from_slice(&data_info.data.borrow())?;
88+
let raw_data = &mut data_info.data.borrow_mut();
89+
let account_data = pod_from_bytes_mut::<RecordData>(raw_data)?;
8690
if !account_data.is_initialized() {
8791
msg!("Record account not initialized");
8892
return Err(ProgramError::UninitializedAccount);
8993
}
9094
check_authority(authority_info, &account_data.authority)?;
9195
account_data.authority = *new_authority_info.key;
92-
borsh::to_writer(&mut data_info.data.borrow_mut()[..], &account_data)
93-
.map_err(|e| e.into())
96+
Ok(())
9497
}
9598

9699
RecordInstruction::CloseAccount => {
97100
msg!("RecordInstruction::CloseAccount");
98101
let data_info = next_account_info(account_info_iter)?;
99102
let authority_info = next_account_info(account_info_iter)?;
100103
let destination_info = next_account_info(account_info_iter)?;
101-
let mut account_data = RecordData::try_from_slice(&data_info.data.borrow())?;
104+
let raw_data = &mut data_info.data.borrow_mut();
105+
let account_data = pod_from_bytes_mut::<RecordData>(raw_data)?;
102106
if !account_data.is_initialized() {
103107
msg!("Record not initialized");
104108
return Err(ProgramError::UninitializedAccount);
@@ -111,8 +115,7 @@ pub fn process_instruction(
111115
.checked_add(data_lamports)
112116
.ok_or(RecordError::Overflow)?;
113117
account_data.data = Data::default();
114-
borsh::to_writer(&mut data_info.data.borrow_mut()[..], &account_data)
115-
.map_err(|e| e.into())
118+
Ok(())
116119
}
117120
}
118121
}

0 commit comments

Comments
 (0)