Skip to main content

gemachain_bpf_loader_program/
serialization.rs

1use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
2use gemachain_rbpf::{aligned_memory::AlignedMemory, ebpf::HOST_ALIGN};
3use gemachain_sdk::{
4    account::{ReadableAccount, WritableAccount},
5    bpf_loader_deprecated,
6    entrypoint::MAX_PERMITTED_DATA_INCREASE,
7    instruction::InstructionError,
8    keyed_account::KeyedAccount,
9    pubkey::Pubkey,
10};
11use std::{
12    io::prelude::*,
13    mem::{align_of, size_of},
14};
15
16/// Look for a duplicate account and return its position if found
17pub fn is_dup(accounts: &[KeyedAccount], keyed_account: &KeyedAccount) -> (bool, usize) {
18    for (i, account) in accounts.iter().enumerate() {
19        if account == keyed_account {
20            return (true, i);
21        }
22    }
23    (false, 0)
24}
25
26pub fn serialize_parameters(
27    loader_id: &Pubkey,
28    program_id: &Pubkey,
29    keyed_accounts: &[KeyedAccount],
30    data: &[u8],
31) -> Result<(AlignedMemory, Vec<usize>), InstructionError> {
32    if *loader_id == bpf_loader_deprecated::id() {
33        serialize_parameters_unaligned(program_id, keyed_accounts, data)
34    } else {
35        serialize_parameters_aligned(program_id, keyed_accounts, data)
36    }
37    .and_then(|buffer| {
38        let account_lengths = keyed_accounts
39            .iter()
40            .map(|keyed_account| keyed_account.data_len())
41            .collect::<Result<Vec<usize>, InstructionError>>()?;
42        Ok((buffer, account_lengths))
43    })
44}
45
46pub fn deserialize_parameters(
47    loader_id: &Pubkey,
48    keyed_accounts: &[KeyedAccount],
49    buffer: &[u8],
50    account_lengths: &[usize],
51) -> Result<(), InstructionError> {
52    if *loader_id == bpf_loader_deprecated::id() {
53        deserialize_parameters_unaligned(keyed_accounts, buffer, account_lengths)
54    } else {
55        deserialize_parameters_aligned(keyed_accounts, buffer, account_lengths)
56    }
57}
58
59pub fn get_serialized_account_size_unaligned(
60    keyed_account: &KeyedAccount,
61) -> Result<usize, InstructionError> {
62    let data_len = keyed_account.data_len()?;
63    Ok(
64        size_of::<u8>() // is_signer
65            + size_of::<u8>() // is_writable
66            + size_of::<Pubkey>() // key
67            + size_of::<u64>()  // carats
68            + size_of::<u64>()  // data len
69            + data_len // data
70            + size_of::<Pubkey>() // owner
71            + size_of::<u8>() // executable
72            + size_of::<u64>(), // rent_epoch
73    )
74}
75
76pub fn serialize_parameters_unaligned(
77    program_id: &Pubkey,
78    keyed_accounts: &[KeyedAccount],
79    instruction_data: &[u8],
80) -> Result<AlignedMemory, InstructionError> {
81    // Calculate size in order to alloc once
82    let mut size = size_of::<u64>();
83    for (i, keyed_account) in keyed_accounts.iter().enumerate() {
84        let (is_dup, _) = is_dup(&keyed_accounts[..i], keyed_account);
85        size += 1; // dup
86        if !is_dup {
87            size += get_serialized_account_size_unaligned(keyed_account)?;
88        }
89    }
90    size += size_of::<u64>() // instruction data len
91         + instruction_data.len() // instruction data
92         + size_of::<Pubkey>(); // program id
93    let mut v = AlignedMemory::new(size, HOST_ALIGN);
94
95    v.write_u64::<LittleEndian>(keyed_accounts.len() as u64)
96        .map_err(|_| InstructionError::InvalidArgument)?;
97    for (i, keyed_account) in keyed_accounts.iter().enumerate() {
98        let (is_dup, position) = is_dup(&keyed_accounts[..i], keyed_account);
99        if is_dup {
100            v.write_u8(position as u8)
101                .map_err(|_| InstructionError::InvalidArgument)?;
102        } else {
103            v.write_u8(std::u8::MAX)
104                .map_err(|_| InstructionError::InvalidArgument)?;
105            v.write_u8(keyed_account.signer_key().is_some() as u8)
106                .map_err(|_| InstructionError::InvalidArgument)?;
107            v.write_u8(keyed_account.is_writable() as u8)
108                .map_err(|_| InstructionError::InvalidArgument)?;
109            v.write_all(keyed_account.unsigned_key().as_ref())
110                .map_err(|_| InstructionError::InvalidArgument)?;
111            v.write_u64::<LittleEndian>(keyed_account.carats()?)
112                .map_err(|_| InstructionError::InvalidArgument)?;
113            v.write_u64::<LittleEndian>(keyed_account.data_len()? as u64)
114                .map_err(|_| InstructionError::InvalidArgument)?;
115            v.write_all(keyed_account.try_account_ref()?.data())
116                .map_err(|_| InstructionError::InvalidArgument)?;
117            v.write_all(keyed_account.owner()?.as_ref())
118                .map_err(|_| InstructionError::InvalidArgument)?;
119            v.write_u8(keyed_account.executable()? as u8)
120                .map_err(|_| InstructionError::InvalidArgument)?;
121            v.write_u64::<LittleEndian>(keyed_account.rent_epoch()? as u64)
122                .map_err(|_| InstructionError::InvalidArgument)?;
123        }
124    }
125    v.write_u64::<LittleEndian>(instruction_data.len() as u64)
126        .map_err(|_| InstructionError::InvalidArgument)?;
127    v.write_all(instruction_data)
128        .map_err(|_| InstructionError::InvalidArgument)?;
129    v.write_all(program_id.as_ref())
130        .map_err(|_| InstructionError::InvalidArgument)?;
131    Ok(v)
132}
133
134pub fn deserialize_parameters_unaligned(
135    keyed_accounts: &[KeyedAccount],
136    buffer: &[u8],
137    account_lengths: &[usize],
138) -> Result<(), InstructionError> {
139    let mut start = size_of::<u64>(); // number of accounts
140    for (i, (keyed_account, _pre_len)) in keyed_accounts
141        .iter()
142        .zip(account_lengths.iter())
143        .enumerate()
144    {
145        let (is_dup, _) = is_dup(&keyed_accounts[..i], keyed_account);
146        start += 1; // is_dup
147        if !is_dup {
148            start += size_of::<u8>(); // is_signer
149            start += size_of::<u8>(); // is_writable
150            start += size_of::<Pubkey>(); // key
151            keyed_account
152                .try_account_ref_mut()?
153                .set_carats(LittleEndian::read_u64(&buffer[start..]));
154            start += size_of::<u64>() // carats
155                + size_of::<u64>(); // data length
156            let end = start + keyed_account.data_len()?;
157            keyed_account
158                .try_account_ref_mut()?
159                .set_data_from_slice(&buffer[start..end]);
160            start += keyed_account.data_len()? // data
161                + size_of::<Pubkey>() // owner
162                + size_of::<u8>() // executable
163                + size_of::<u64>(); // rent_epoch
164        }
165    }
166    Ok(())
167}
168
169pub fn get_serialized_account_size_aligned(
170    keyed_account: &KeyedAccount,
171) -> Result<usize, InstructionError> {
172    let data_len = keyed_account.data_len()?;
173    Ok(
174        size_of::<u8>() // is_signer
175            + size_of::<u8>() // is_writable
176            + size_of::<u8>() // executable
177            + 4 // padding to 128-bit aligned
178            + size_of::<Pubkey>()  // key
179            + size_of::<Pubkey>() // owner
180            + size_of::<u64>()  // carats
181            + size_of::<u64>()  // data len
182            + data_len
183            + MAX_PERMITTED_DATA_INCREASE
184            + (data_len as *const u8).align_offset(align_of::<u128>())
185            + size_of::<u64>(), // rent epoch
186    )
187}
188
189pub fn serialize_parameters_aligned(
190    program_id: &Pubkey,
191    keyed_accounts: &[KeyedAccount],
192    instruction_data: &[u8],
193) -> Result<AlignedMemory, InstructionError> {
194    // Calculate size in order to alloc once
195    let mut size = size_of::<u64>();
196    for (i, keyed_account) in keyed_accounts.iter().enumerate() {
197        let (is_dup, _) = is_dup(&keyed_accounts[..i], keyed_account);
198        size += 1; // dup
199        if is_dup {
200            size += 7; // padding to 64-bit aligned
201        } else {
202            size += get_serialized_account_size_aligned(keyed_account)?;
203        }
204    }
205    size += size_of::<u64>() // data len
206    + instruction_data.len()
207    + size_of::<Pubkey>(); // program id;
208    let mut v = AlignedMemory::new(size, HOST_ALIGN);
209
210    // Serialize into the buffer
211    v.write_u64::<LittleEndian>(keyed_accounts.len() as u64)
212        .map_err(|_| InstructionError::InvalidArgument)?;
213    for (i, keyed_account) in keyed_accounts.iter().enumerate() {
214        let (is_dup, position) = is_dup(&keyed_accounts[..i], keyed_account);
215        if is_dup {
216            v.write_u8(position as u8)
217                .map_err(|_| InstructionError::InvalidArgument)?;
218            v.write_all(&[0u8, 0, 0, 0, 0, 0, 0])
219                .map_err(|_| InstructionError::InvalidArgument)?; // 7 bytes of padding to make 64-bit aligned
220        } else {
221            v.write_u8(std::u8::MAX)
222                .map_err(|_| InstructionError::InvalidArgument)?;
223            v.write_u8(keyed_account.signer_key().is_some() as u8)
224                .map_err(|_| InstructionError::InvalidArgument)?;
225            v.write_u8(keyed_account.is_writable() as u8)
226                .map_err(|_| InstructionError::InvalidArgument)?;
227            v.write_u8(keyed_account.executable()? as u8)
228                .map_err(|_| InstructionError::InvalidArgument)?;
229            v.write_all(&[0u8, 0, 0, 0])
230                .map_err(|_| InstructionError::InvalidArgument)?; // 4 bytes of padding to make 128-bit aligned
231            v.write_all(keyed_account.unsigned_key().as_ref())
232                .map_err(|_| InstructionError::InvalidArgument)?;
233            v.write_all(keyed_account.owner()?.as_ref())
234                .map_err(|_| InstructionError::InvalidArgument)?;
235            v.write_u64::<LittleEndian>(keyed_account.carats()?)
236                .map_err(|_| InstructionError::InvalidArgument)?;
237            v.write_u64::<LittleEndian>(keyed_account.data_len()? as u64)
238                .map_err(|_| InstructionError::InvalidArgument)?;
239            v.write_all(keyed_account.try_account_ref()?.data())
240                .map_err(|_| InstructionError::InvalidArgument)?;
241            v.resize(
242                MAX_PERMITTED_DATA_INCREASE
243                    + (v.write_index() as *const u8).align_offset(align_of::<u128>()),
244                0,
245            )
246            .map_err(|_| InstructionError::InvalidArgument)?;
247            v.write_u64::<LittleEndian>(keyed_account.rent_epoch()? as u64)
248                .map_err(|_| InstructionError::InvalidArgument)?;
249        }
250    }
251    v.write_u64::<LittleEndian>(instruction_data.len() as u64)
252        .map_err(|_| InstructionError::InvalidArgument)?;
253    v.write_all(instruction_data)
254        .map_err(|_| InstructionError::InvalidArgument)?;
255    v.write_all(program_id.as_ref())
256        .map_err(|_| InstructionError::InvalidArgument)?;
257    Ok(v)
258}
259
260pub fn deserialize_parameters_aligned(
261    keyed_accounts: &[KeyedAccount],
262    buffer: &[u8],
263    account_lengths: &[usize],
264) -> Result<(), InstructionError> {
265    let mut start = size_of::<u64>(); // number of accounts
266    for (i, (keyed_account, pre_len)) in keyed_accounts
267        .iter()
268        .zip(account_lengths.iter())
269        .enumerate()
270    {
271        let (is_dup, _) = is_dup(&keyed_accounts[..i], keyed_account);
272        start += size_of::<u8>(); // position
273        if is_dup {
274            start += 7; // padding to 64-bit aligned
275        } else {
276            let mut account = keyed_account.try_account_ref_mut()?;
277            start += size_of::<u8>() // is_signer
278                + size_of::<u8>() // is_writable
279                + size_of::<u8>() // executable
280                + 4 // padding to 128-bit aligned
281                + size_of::<Pubkey>(); // key
282            account.copy_into_owner_from_slice(&buffer[start..start + size_of::<Pubkey>()]);
283            start += size_of::<Pubkey>(); // owner
284            account.set_carats(LittleEndian::read_u64(&buffer[start..]));
285            start += size_of::<u64>(); // carats
286            let post_len = LittleEndian::read_u64(&buffer[start..]) as usize;
287            start += size_of::<u64>(); // data length
288            let mut data_end = start + *pre_len;
289            if post_len != *pre_len
290                && (post_len.saturating_sub(*pre_len)) <= MAX_PERMITTED_DATA_INCREASE
291            {
292                data_end = start + post_len;
293            }
294
295            account.set_data_from_slice(&buffer[start..data_end]);
296            start += *pre_len + MAX_PERMITTED_DATA_INCREASE; // data
297            start += (start as *const u8).align_offset(align_of::<u128>());
298            start += size_of::<u64>(); // rent_epoch
299        }
300    }
301    Ok(())
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307    use gemachain_sdk::{
308        account::{Account, AccountSharedData},
309        account_info::AccountInfo,
310        bpf_loader,
311        entrypoint::deserialize,
312    };
313    use std::{
314        cell::RefCell,
315        rc::Rc,
316        // Hide Result from bindgen gets confused about generics in non-generic type declarations
317        slice::{from_raw_parts, from_raw_parts_mut},
318    };
319
320    #[test]
321    fn test_serialize_parameters() {
322        let program_id = gemachain_sdk::pubkey::new_rand();
323        let dup_key = gemachain_sdk::pubkey::new_rand();
324        let dup_key2 = gemachain_sdk::pubkey::new_rand();
325        let keys = vec![
326            dup_key,
327            dup_key,
328            gemachain_sdk::pubkey::new_rand(),
329            gemachain_sdk::pubkey::new_rand(),
330            dup_key2,
331            dup_key2,
332            gemachain_sdk::pubkey::new_rand(),
333            gemachain_sdk::pubkey::new_rand(),
334        ];
335        let accounts = [
336            RefCell::new(AccountSharedData::from(Account {
337                carats: 1,
338                data: vec![1u8, 2, 3, 4, 5],
339                owner: bpf_loader::id(),
340                executable: false,
341                rent_epoch: 100,
342            })),
343            // dup
344            RefCell::new(AccountSharedData::from(Account {
345                carats: 1,
346                data: vec![1u8, 2, 3, 4, 5],
347                owner: bpf_loader::id(),
348                executable: false,
349                rent_epoch: 100,
350            })),
351            RefCell::new(AccountSharedData::from(Account {
352                carats: 2,
353                data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
354                owner: bpf_loader::id(),
355                executable: true,
356                rent_epoch: 200,
357            })),
358            RefCell::new(AccountSharedData::from(Account {
359                carats: 3,
360                data: vec![],
361                owner: bpf_loader::id(),
362                executable: false,
363                rent_epoch: 3100,
364            })),
365            RefCell::new(AccountSharedData::from(Account {
366                carats: 4,
367                data: vec![1u8, 2, 3, 4, 5],
368                owner: bpf_loader::id(),
369                executable: false,
370                rent_epoch: 100,
371            })),
372            // dup
373            RefCell::new(AccountSharedData::from(Account {
374                carats: 4,
375                data: vec![1u8, 2, 3, 4, 5],
376                owner: bpf_loader::id(),
377                executable: false,
378                rent_epoch: 100,
379            })),
380            RefCell::new(AccountSharedData::from(Account {
381                carats: 5,
382                data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
383                owner: bpf_loader::id(),
384                executable: true,
385                rent_epoch: 200,
386            })),
387            RefCell::new(AccountSharedData::from(Account {
388                carats: 6,
389                data: vec![],
390                owner: bpf_loader::id(),
391                executable: false,
392                rent_epoch: 3100,
393            })),
394        ];
395
396        let keyed_accounts: Vec<_> = keys
397            .iter()
398            .zip(&accounts)
399            .enumerate()
400            .map(|(i, (key, account))| {
401                if i <= accounts.len() / 2 {
402                    KeyedAccount::new_readonly(key, false, account)
403                } else {
404                    KeyedAccount::new(key, false, account)
405                }
406            })
407            .collect();
408        let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
409
410        // check serialize_parameters_aligned
411
412        let (mut serialized, account_lengths) = serialize_parameters(
413            &bpf_loader::id(),
414            &program_id,
415            &keyed_accounts,
416            &instruction_data,
417        )
418        .unwrap();
419
420        let (de_program_id, de_accounts, de_instruction_data) =
421            unsafe { deserialize(&mut serialized.as_slice_mut()[0] as *mut u8) };
422
423        assert_eq!(&program_id, de_program_id);
424        assert_eq!(instruction_data, de_instruction_data);
425        assert_eq!(
426            (&de_instruction_data[0] as *const u8).align_offset(align_of::<u128>()),
427            0
428        );
429        for ((account, account_info), key) in accounts.iter().zip(de_accounts).zip(keys.clone()) {
430            assert_eq!(key, *account_info.key);
431            let account = account.borrow();
432            assert_eq!(account.carats(), account_info.carats());
433            assert_eq!(account.data(), &account_info.data.borrow()[..]);
434            assert_eq!(account.owner(), account_info.owner);
435            assert_eq!(account.executable(), account_info.executable);
436            assert_eq!(account.rent_epoch(), account_info.rent_epoch);
437
438            assert_eq!(
439                (*account_info.carats.borrow() as *const u64).align_offset(align_of::<u64>()),
440                0
441            );
442            assert_eq!(
443                account_info
444                    .data
445                    .borrow()
446                    .as_ptr()
447                    .align_offset(align_of::<u128>()),
448                0
449            );
450        }
451
452        let de_accounts = accounts.clone();
453        let de_keyed_accounts: Vec<_> = keys
454            .iter()
455            .zip(&de_accounts)
456            .enumerate()
457            .map(|(i, (key, account))| {
458                if i <= accounts.len() / 2 {
459                    KeyedAccount::new_readonly(key, false, account)
460                } else {
461                    KeyedAccount::new(key, false, account)
462                }
463            })
464            .collect();
465        deserialize_parameters(
466            &bpf_loader::id(),
467            &de_keyed_accounts,
468            serialized.as_slice(),
469            &account_lengths,
470        )
471        .unwrap();
472        for ((account, de_keyed_account), key) in
473            accounts.iter().zip(de_keyed_accounts).zip(keys.clone())
474        {
475            assert_eq!(key, *de_keyed_account.unsigned_key());
476            let account = account.borrow();
477            assert_eq!(account.executable(), de_keyed_account.executable().unwrap());
478            assert_eq!(account.rent_epoch(), de_keyed_account.rent_epoch().unwrap());
479        }
480
481        // check serialize_parameters_unaligned
482
483        let (mut serialized, account_lengths) = serialize_parameters(
484            &bpf_loader_deprecated::id(),
485            &program_id,
486            &keyed_accounts,
487            &instruction_data,
488        )
489        .unwrap();
490
491        let (de_program_id, de_accounts, de_instruction_data) =
492            unsafe { deserialize_unaligned(&mut serialized.as_slice_mut()[0] as *mut u8) };
493        assert_eq!(&program_id, de_program_id);
494        assert_eq!(instruction_data, de_instruction_data);
495        for ((account, account_info), key) in accounts.iter().zip(de_accounts).zip(keys.clone()) {
496            assert_eq!(key, *account_info.key);
497            let account = account.borrow();
498            assert_eq!(account.carats(), account_info.carats());
499            assert_eq!(account.data(), &account_info.data.borrow()[..]);
500            assert_eq!(account.owner(), account_info.owner);
501            assert_eq!(account.executable(), account_info.executable);
502            assert_eq!(account.rent_epoch(), account_info.rent_epoch);
503        }
504
505        let de_accounts = accounts.clone();
506        let de_keyed_accounts: Vec<_> = keys
507            .iter()
508            .zip(&de_accounts)
509            .enumerate()
510            .map(|(i, (key, account))| {
511                if i < accounts.len() / 2 {
512                    KeyedAccount::new_readonly(key, false, account)
513                } else {
514                    KeyedAccount::new(key, false, account)
515                }
516            })
517            .collect();
518        deserialize_parameters(
519            &bpf_loader_deprecated::id(),
520            &de_keyed_accounts,
521            serialized.as_slice(),
522            &account_lengths,
523        )
524        .unwrap();
525        for ((account, de_keyed_account), key) in
526            accounts.iter().zip(de_keyed_accounts).zip(keys.clone())
527        {
528            assert_eq!(key, *de_keyed_account.unsigned_key());
529            let account = account.borrow();
530            assert_eq!(account.carats(), de_keyed_account.carats().unwrap());
531            assert_eq!(
532                account.data(),
533                de_keyed_account.try_account_ref().unwrap().data()
534            );
535            assert_eq!(*account.owner(), de_keyed_account.owner().unwrap());
536            assert_eq!(account.executable(), de_keyed_account.executable().unwrap());
537            assert_eq!(account.rent_epoch(), de_keyed_account.rent_epoch().unwrap());
538        }
539    }
540
541    // the old bpf_loader in-program deserializer bpf_loader::id()
542    #[allow(clippy::type_complexity)]
543    pub unsafe fn deserialize_unaligned<'a>(
544        input: *mut u8,
545    ) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
546        let mut offset: usize = 0;
547
548        // number of accounts present
549
550        #[allow(clippy::cast_ptr_alignment)]
551        let num_accounts = *(input.add(offset) as *const u64) as usize;
552        offset += size_of::<u64>();
553
554        // account Infos
555
556        let mut accounts = Vec::with_capacity(num_accounts);
557        for _ in 0..num_accounts {
558            let dup_info = *(input.add(offset) as *const u8);
559            offset += size_of::<u8>();
560            if dup_info == std::u8::MAX {
561                #[allow(clippy::cast_ptr_alignment)]
562                let is_signer = *(input.add(offset) as *const u8) != 0;
563                offset += size_of::<u8>();
564
565                #[allow(clippy::cast_ptr_alignment)]
566                let is_writable = *(input.add(offset) as *const u8) != 0;
567                offset += size_of::<u8>();
568
569                let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
570                offset += size_of::<Pubkey>();
571
572                #[allow(clippy::cast_ptr_alignment)]
573                let carats = Rc::new(RefCell::new(&mut *(input.add(offset) as *mut u64)));
574                offset += size_of::<u64>();
575
576                #[allow(clippy::cast_ptr_alignment)]
577                let data_len = *(input.add(offset) as *const u64) as usize;
578                offset += size_of::<u64>();
579
580                let data = Rc::new(RefCell::new({
581                    from_raw_parts_mut(input.add(offset), data_len)
582                }));
583                offset += data_len;
584
585                let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
586                offset += size_of::<Pubkey>();
587
588                #[allow(clippy::cast_ptr_alignment)]
589                let executable = *(input.add(offset) as *const u8) != 0;
590                offset += size_of::<u8>();
591
592                #[allow(clippy::cast_ptr_alignment)]
593                let rent_epoch = *(input.add(offset) as *const u64);
594                offset += size_of::<u64>();
595
596                accounts.push(AccountInfo {
597                    key,
598                    is_signer,
599                    is_writable,
600                    carats,
601                    data,
602                    owner,
603                    executable,
604                    rent_epoch,
605                });
606            } else {
607                // duplicate account, clone the original
608                accounts.push(accounts[dup_info as usize].clone());
609            }
610        }
611
612        // instruction data
613
614        #[allow(clippy::cast_ptr_alignment)]
615        let instruction_data_len = *(input.add(offset) as *const u64) as usize;
616        offset += size_of::<u64>();
617
618        let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
619        offset += instruction_data_len;
620
621        // program Id
622
623        let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
624
625        (program_id, accounts, instruction_data)
626    }
627}