Skip to main content

sbpf_runtime/
serialize.rs

1use {
2    crate::{
3        cpi::validate::check_account_change,
4        errors::{RuntimeError, RuntimeResult},
5    },
6    solana_account::Account,
7    solana_address::Address,
8    solana_instruction::AccountMeta,
9    std::{collections::HashMap, mem::size_of},
10};
11
12const ALIGN_OF_U64: usize = 8;
13const MAX_PERMITTED_DATA_INCREASE: usize = 10240;
14const NON_DUP_MARKER: u8 = 0xff;
15
16struct Serializer {
17    buffer: Vec<u8>,
18}
19
20impl Serializer {
21    fn new() -> Self {
22        Self { buffer: Vec::new() }
23    }
24
25    fn write<T>(&mut self, value: T) {
26        let bytes =
27            unsafe { std::slice::from_raw_parts(&value as *const T as *const u8, size_of::<T>()) };
28        self.buffer.extend_from_slice(bytes);
29    }
30
31    fn write_all(&mut self, data: &[u8]) {
32        self.buffer.extend_from_slice(data);
33    }
34
35    fn write_account_data(&mut self, data: &[u8]) {
36        self.write_all(data);
37        self.buffer
38            .extend(std::iter::repeat_n(0u8, MAX_PERMITTED_DATA_INCREASE));
39        let alignment_needed = (ALIGN_OF_U64 - (self.buffer.len() % ALIGN_OF_U64)) % ALIGN_OF_U64;
40        self.buffer
41            .extend(std::iter::repeat_n(0u8, alignment_needed));
42    }
43
44    fn finish(self) -> Vec<u8> {
45        self.buffer
46    }
47}
48
49enum SerializeAccount {
50    Account(Address, Account, bool, bool),
51    Duplicate(u8),
52}
53
54pub fn serialize_parameters(
55    accounts: &HashMap<Address, Account>,
56    account_metas: &[AccountMeta],
57    instruction_data: &[u8],
58    program_id: &Address,
59) -> RuntimeResult<(Vec<u8>, Vec<usize>, usize)> {
60    let mut seen: HashMap<Address, usize> = HashMap::new();
61    let mut serialize_accounts = Vec::with_capacity(account_metas.len());
62
63    for (i, meta) in account_metas.iter().enumerate() {
64        if let Some(&first_idx) = seen.get(&meta.pubkey) {
65            serialize_accounts.push(SerializeAccount::Duplicate(first_idx as u8));
66        } else {
67            seen.insert(meta.pubkey, i);
68            let account = accounts.get(&meta.pubkey).ok_or_else(|| {
69                RuntimeError::MissingAccount(format!("Missing account data for {}", meta.pubkey))
70            })?;
71            serialize_accounts.push(SerializeAccount::Account(
72                meta.pubkey,
73                account.clone(),
74                meta.is_signer,
75                meta.is_writable,
76            ));
77        }
78    }
79
80    let mut s = Serializer::new();
81    let mut pre_lens: Vec<usize> = Vec::new();
82    s.write::<u64>((serialize_accounts.len() as u64).to_le());
83
84    for account in serialize_accounts {
85        match account {
86            SerializeAccount::Account(pubkey, acct, is_signer, is_writable) => {
87                s.write::<u8>(NON_DUP_MARKER);
88                s.write::<u8>(is_signer as u8);
89                s.write::<u8>(is_writable as u8);
90                s.write::<u8>(acct.executable as u8);
91                s.write_all(&[0u8; 4]);
92                s.write_all(pubkey.as_ref());
93                s.write_all(acct.owner.as_ref());
94                s.write::<u64>(acct.lamports.to_le());
95                pre_lens.push(acct.data.len());
96                s.write::<u64>((acct.data.len() as u64).to_le());
97                s.write_account_data(&acct.data);
98                s.write::<u64>(acct.rent_epoch.to_le());
99            }
100            SerializeAccount::Duplicate(position) => {
101                s.write::<u8>(position);
102                s.write_all(&[0u8; 7]);
103            }
104        }
105    }
106
107    s.write::<u64>((instruction_data.len() as u64).to_le());
108    let instruction_data_offset = s.buffer.len();
109    s.write_all(instruction_data);
110    s.write_all(program_id.as_ref());
111
112    Ok((s.finish(), pre_lens, instruction_data_offset))
113}
114
115struct Deserializer<'a> {
116    buffer: &'a [u8],
117    offset: usize,
118}
119
120impl<'a> Deserializer<'a> {
121    fn new(buffer: &'a [u8]) -> Self {
122        Self { buffer, offset: 0 }
123    }
124
125    fn read_u64(&mut self) -> u64 {
126        let bytes: [u8; 8] = self.buffer[self.offset..self.offset + 8]
127            .try_into()
128            .unwrap_or([0u8; 8]);
129        self.offset += 8;
130        u64::from_le_bytes(bytes)
131    }
132
133    fn skip(&mut self, n: usize) {
134        self.offset += n;
135    }
136
137    fn read_address(&mut self) -> Address {
138        let bytes: [u8; 32] = self.buffer[self.offset..self.offset + 32]
139            .try_into()
140            .unwrap_or([0u8; 32]);
141        self.offset += 32;
142        Address::from(bytes)
143    }
144
145    fn read_account_data(&mut self, post_len: usize, pre_len: usize) -> &'a [u8] {
146        let data = &self.buffer[self.offset..self.offset + post_len];
147        // Skip using original length to stay aligned with the serialized layout.
148        self.offset += pre_len;
149        self.offset += MAX_PERMITTED_DATA_INCREASE;
150        let align = (ALIGN_OF_U64 - (self.offset % ALIGN_OF_U64)) % ALIGN_OF_U64;
151        self.offset += align;
152        data
153    }
154}
155
156pub fn deserialize_parameters(
157    accounts: &mut HashMap<Address, Account>,
158    account_metas: &[AccountMeta],
159    input: &[u8],
160    pre_lens: &[usize],
161    callee_program_id: &Address,
162) -> RuntimeResult<()> {
163    let mut d = Deserializer::new(input);
164    let _num_accounts = d.read_u64();
165    let mut seen: HashMap<Address, usize> = HashMap::new();
166    let mut pre_len_idx = 0;
167
168    for (i, meta) in account_metas.iter().enumerate() {
169        if seen.contains_key(&meta.pubkey) {
170            d.skip(8); // duplicate: index(1) + padding(7)
171            continue;
172        }
173        seen.insert(meta.pubkey, i);
174
175        let pre_len = pre_lens.get(pre_len_idx).copied().unwrap_or(0);
176        pre_len_idx += 1;
177
178        d.skip(8); // dup_marker + is_signer + is_writable + executable + padding
179        d.skip(32); // pubkey
180        let owner = d.read_address();
181        let lamports = d.read_u64();
182        let data_len = d.read_u64() as usize;
183        let data = d.read_account_data(data_len, pre_len);
184        let _rent_epoch = d.read_u64();
185
186        if meta.is_writable
187            && let Some(account) = accounts.get_mut(&meta.pubkey)
188        {
189            check_account_change(
190                callee_program_id,
191                &meta.pubkey,
192                account,
193                &owner,
194                lamports,
195                data,
196            )?;
197            account.lamports = lamports;
198            account.data = data.to_vec();
199            account.owner = owner;
200        }
201    }
202    Ok(())
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    const PROGRAM: Address = Address::new_from_array([1u8; 32]);
210    const OTHER: Address = Address::new_from_array([2u8; 32]);
211    const ACCT_1: Address = Address::new_from_array([10u8; 32]);
212    const ACCT_2: Address = Address::new_from_array([11u8; 32]);
213
214    fn make_account(owner: Address, lamports: u64, data: &[u8]) -> Account {
215        Account {
216            lamports,
217            data: data.to_vec(),
218            owner,
219            executable: false,
220            rent_epoch: 0,
221        }
222    }
223
224    fn accounts_map(list: &[(Address, Account)]) -> HashMap<Address, Account> {
225        list.iter().cloned().collect()
226    }
227
228    #[test]
229    fn e2e_accounts_preserved() {
230        let mut accounts = accounts_map(&[
231            (ACCT_1, make_account(PROGRAM, 100, b"hello")),
232            (ACCT_2, make_account(PROGRAM, 200, b"world")),
233        ]);
234        let metas = vec![
235            AccountMeta {
236                pubkey: ACCT_1,
237                is_signer: false,
238                is_writable: true,
239            },
240            AccountMeta {
241                pubkey: ACCT_2,
242                is_signer: false,
243                is_writable: true,
244            },
245        ];
246        let (input, pre_lens, _) =
247            serialize_parameters(&accounts, &metas, b"ix", &PROGRAM).unwrap();
248        deserialize_parameters(&mut accounts, &metas, &input, &pre_lens, &PROGRAM).unwrap();
249
250        assert_eq!(accounts[&ACCT_1].lamports, 100);
251        assert_eq!(accounts[&ACCT_1].data, b"hello");
252        assert_eq!(accounts[&ACCT_2].lamports, 200);
253        assert_eq!(accounts[&ACCT_2].data, b"world");
254    }
255
256    #[test]
257    fn read_only_account_not_updated() {
258        let mut accounts = accounts_map(&[(ACCT_1, make_account(PROGRAM, 100, b"data"))]);
259        let metas = vec![AccountMeta {
260            pubkey: ACCT_1,
261            is_signer: false,
262            is_writable: false,
263        }];
264        let (mut input, pre_lens, _) =
265            serialize_parameters(&accounts, &metas, b"", &PROGRAM).unwrap();
266
267        // Try to update lamports.
268        let lamport_offset = 8 + 8 + 32 + 32;
269        input[lamport_offset..lamport_offset + 8].copy_from_slice(&999u64.to_le_bytes());
270
271        deserialize_parameters(&mut accounts, &metas, &input, &pre_lens, &PROGRAM).unwrap();
272        assert_eq!(accounts[&ACCT_1].lamports, 100); // lamports unchanged
273    }
274
275    #[test]
276    fn non_owner_data_change_rejected() {
277        let mut accounts = accounts_map(&[(ACCT_1, make_account(OTHER, 100, b"original"))]);
278        let metas = vec![AccountMeta {
279            pubkey: ACCT_1,
280            is_signer: false,
281            is_writable: true,
282        }];
283        let (mut input, pre_lens, _) =
284            serialize_parameters(&accounts, &metas, b"", &PROGRAM).unwrap();
285
286        // Try modifying data.
287        let data_offset = 8 + 8 + 32 + 32 + 8 + 8;
288        input[data_offset..data_offset + 4].copy_from_slice(b"fake");
289
290        let result = deserialize_parameters(&mut accounts, &metas, &input, &pre_lens, &PROGRAM);
291        assert!(result.is_err());
292        assert_eq!(accounts[&ACCT_1].data, b"original");
293    }
294
295    #[test]
296    fn non_owner_lamport_debit_rejected() {
297        let mut accounts = accounts_map(&[(ACCT_1, make_account(OTHER, 100, b""))]);
298        let metas = vec![AccountMeta {
299            pubkey: ACCT_1,
300            is_signer: false,
301            is_writable: true,
302        }];
303        let (mut input, pre_lens, _) =
304            serialize_parameters(&accounts, &metas, b"", &PROGRAM).unwrap();
305
306        let lamport_offset = 8 + 8 + 32 + 32;
307        input[lamport_offset..lamport_offset + 8].copy_from_slice(&50u64.to_le_bytes());
308
309        let result = deserialize_parameters(&mut accounts, &metas, &input, &pre_lens, &PROGRAM);
310        assert!(result.is_err());
311        assert_eq!(accounts[&ACCT_1].lamports, 100);
312    }
313
314    #[test]
315    fn missing_account_errors() {
316        let accounts = accounts_map(&[]);
317        let metas = vec![AccountMeta {
318            pubkey: ACCT_1,
319            is_signer: false,
320            is_writable: false,
321        }];
322        let result = serialize_parameters(&accounts, &metas, b"", &PROGRAM);
323        assert!(result.is_err());
324    }
325
326    #[test]
327    fn check_instruction_data_offset() {
328        let accounts = accounts_map(&[(ACCT_1, make_account(PROGRAM, 100, b"data"))]);
329        let metas = vec![AccountMeta {
330            pubkey: ACCT_1,
331            is_signer: false,
332            is_writable: true,
333        }];
334        let ix_data = b"instruction";
335        let (input, _, offset) =
336            serialize_parameters(&accounts, &metas, ix_data, &PROGRAM).unwrap();
337
338        // Check offset points to instruction data
339        assert_eq!(&input[offset..(offset + ix_data.len())], ix_data);
340    }
341
342    #[test]
343    fn check_instruction_data_offset_no_data() {
344        let accounts = accounts_map(&[(ACCT_1, make_account(PROGRAM, 100, b"data"))]);
345        let metas = vec![AccountMeta {
346            pubkey: ACCT_1,
347            is_signer: false,
348            is_writable: true,
349        }];
350        let ix_data = b""; // no data
351        let (input, _, offset) =
352            serialize_parameters(&accounts, &metas, ix_data, &PROGRAM).unwrap();
353
354        // Check offset points to program ID (since data is empty)
355        assert_eq!(&input[offset..offset + 32], PROGRAM.as_ref());
356    }
357}