Skip to content

Commit

Permalink
Implements new encoding and adds attribute enum.
Browse files Browse the repository at this point in the history
  • Loading branch information
Lichtso committed Sep 27, 2021
1 parent 36c9da2 commit 4058b12
Show file tree
Hide file tree
Showing 2 changed files with 294 additions and 0 deletions.
293 changes: 293 additions & 0 deletions sdk/src/abi.rs
@@ -0,0 +1,293 @@
use byteorder::{ByteOrder, LittleEndian};
use std::mem::size_of;

// Encoding
//
// struct Map {
// number_of_entries: u16,
// attribute: [u16; number_of_entries],
// value_offset: [u32; number_of_entries],
// values: [Value],
// }
//
// struct FatPtr {
// address: u64,
// length: u32,
// }
//
// union Value {
// Direct([u8]),
// Indirect([FatPtr]),
// }

pub const INDIRECT_FATPTR_LENGTH: usize = 12;

pub fn predict_header_size(number_of_entries: usize) -> usize {
size_of::<u16>() + number_of_entries * (size_of::<u16>() + size_of::<u32>())
}

pub fn get_number_of_entries(encoded: &[u8]) -> usize {
LittleEndian::read_u16(encoded) as usize
}

pub fn get_attribute(encoded: &[u8], entry_index: usize) -> Attribute {
let attribute_id =
LittleEndian::read_u16(&encoded[size_of::<u16>() + entry_index * size_of::<u16>()..]) as u8;
unsafe { std::mem::transmute(attribute_id) }
}

pub fn resolve_attribute(encoded: &[u8], attribute: Attribute) -> (usize, usize) {
let number_of_entries = get_number_of_entries(encoded);
let mut entry_index = 1;
while entry_index <= number_of_entries {
entry_index =
(entry_index << 1) + (get_attribute(encoded, entry_index - 1) <= attribute) as usize;
}
entry_index >>= entry_index.trailing_zeros() + 1;
assert!(entry_index > 0 && entry_index <= number_of_entries);
entry_index -= 1;
assert_eq!(get_attribute(encoded, entry_index), attribute);
(entry_index, number_of_entries)
}

pub fn get_value_start_offset(
encoded: &[u8],
entry_index: usize,
number_of_entries: usize,
) -> usize {
LittleEndian::read_u32(
&encoded[(number_of_entries + 1) * size_of::<u16>() + entry_index * size_of::<u32>()..],
) as usize
}

pub fn get_value_end_offset(encoded: &[u8], entry_index: usize, number_of_entries: usize) -> usize {
if entry_index + 1 < number_of_entries {
LittleEndian::read_u32(
&encoded[(number_of_entries + 1) * size_of::<u16>()
+ (entry_index + 1) * size_of::<u32>()..],
) as usize
} else {
encoded.len()
}
}

macro_rules! get_value_slice(
($encoded:expr, $entry_index:expr, $number_of_entries:expr, $indirection_index:expr, $ptr_type:ty, $from_raw_parts:ident, $as_ref:ident, $as_ptr:ident $(,)?) => {{
let value_start_offset = get_value_start_offset($encoded, $entry_index, $number_of_entries);
let value_end_offset = get_value_end_offset($encoded, $entry_index, $number_of_entries);
if let Some(indirection_index) = $indirection_index {
let mut offset = value_start_offset + indirection_index * INDIRECT_FATPTR_LENGTH;
let address = LittleEndian::read_u64(&$encoded[offset..]) as usize as $ptr_type;
offset += size_of::<u64>();
let length = LittleEndian::read_u32(&$encoded[offset..]) as usize;
unsafe { std::slice::$from_raw_parts(address, length) }
} else {
unsafe { std::slice::$from_raw_parts($encoded.$as_ptr().add(value_start_offset), value_end_offset - value_start_offset) }
}
}}
);

pub fn get_value(
encoded: &[u8],
entry_index: usize,
number_of_entries: usize,
indirection_index: Option<usize>,
) -> &'static [u8] {
get_value_slice!(
encoded,
entry_index,
number_of_entries,
indirection_index,
*const u8,
from_raw_parts,
as_ref,
as_ptr,
)
}

pub fn get_value_mut(
encoded: &mut [u8],
entry_index: usize,
number_of_entries: usize,
indirection_index: Option<usize>,
) -> &'static mut [u8] {
get_value_slice!(
encoded,
entry_index,
number_of_entries,
indirection_index,
*mut u8,
from_raw_parts_mut,
as_mut,
as_mut_ptr,
)
}

macro_rules! get_value_by_path(
($encoded:expr, [$(($attribute:expr, $indirection_index:expr $(,)?)),* $(,)?] $(,)?) => {{
let encoded: &[u8] = $encoded;
$(
let (entry_index, number_of_entries) = resolve_attribute(encoded, $attribute);
let indirection_index: Option<usize> = $indirection_index;
let encoded = get_value(
encoded,
entry_index,
number_of_entries,
indirection_index,
);
)*
encoded
}}
);

macro_rules! get_value_by_path_mut(
($encoded:expr, [$(($attribute:expr, $indirection_index:expr $(,)?)),* $(,)?] $(,)?) => {{
let encoded: &mut [u8] = $encoded;
$(
let (entry_index, number_of_entries) = resolve_attribute(encoded, $attribute);
let indirection_index: Option<usize> = $indirection_index;
let encoded = get_value_mut(
encoded,
entry_index,
number_of_entries,
indirection_index,
);
)*
encoded
}}
);

fn construct_eytzinger_order(
encoded: &mut [u8],
attributes: &[Attribute],
mut in_index: usize,
out_index: usize,
) -> usize {
if out_index >= attributes.len() {
return in_index;
}
in_index = construct_eytzinger_order(encoded, attributes, in_index, 2 * out_index + 1);
LittleEndian::write_u16(
&mut encoded[out_index * size_of::<u16>()..],
attributes[in_index] as u16,
);
construct_eytzinger_order(encoded, attributes, in_index + 1, 2 * out_index + 2)
}

pub fn encode_header(encoded: &mut [u8], attributes: &mut [Attribute]) -> usize {
attributes.sort_unstable();
LittleEndian::write_u16(encoded, attributes.len() as u16);
let attributes_offset = size_of::<u16>();
construct_eytzinger_order(&mut encoded[attributes_offset..], attributes, 0, 0);
let value_offsets_offset = attributes_offset + attributes.len() * size_of::<u16>();
let value_start_offset = value_offsets_offset + attributes.len() * size_of::<u32>();
for (entry_index, attribute) in attributes.iter_mut().enumerate() {
*attribute = get_attribute(encoded, entry_index);
}
value_start_offset
}

pub fn update_value_start_offset(
encoded: &mut [u8],
entry_index: usize,
number_of_entries: usize,
value_start_offset: usize,
) {
let value_offsets_offset = size_of::<u16>() + number_of_entries * size_of::<u16>();
LittleEndian::write_u32(
&mut encoded[value_offsets_offset + entry_index * size_of::<u32>()..],
value_start_offset as u32,
);
}

pub fn update_value_fatptr(encoded: &mut [u8], mut value_start_offset: usize, slice: &[u8]) {
LittleEndian::write_u64(&mut encoded[value_start_offset..], slice.as_ptr() as u64);
value_start_offset += size_of::<u64>();
LittleEndian::write_u32(&mut encoded[value_start_offset..], slice.len() as u32);
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum Attribute {
NumberOfAccountsInTransaction = 0,
AccountKey = 1,
AccountIsExecutable = 2,
AccountOwner = 3,
AccountLamports = 4,
AccountData = 5,
ReturnData = 6,
InvocationDepth = 7,
InvocationDepthMax = 8,
InvocationStack = 9,
InstructionData = 10,
NumberOfAccountsInInstruction = 11,
NumberOfProgramsInInstruction = 12,
InstructionAccountIndices = 13,
AccountIsSigner = 14,
AccountIsWritable = 15,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_abi_encoding() {
let indirect_value = vec![2; 32];
let entries = [
(Attribute::NumberOfAccountsInTransaction, vec![], 4),
(
Attribute::AccountData,
vec![indirect_value.as_slice()],
INDIRECT_FATPTR_LENGTH,
),
(Attribute::AccountKey, vec![], 32),
];

// Encode
let mut attributes = entries
.iter()
.map(|(attribute, _, _)| *attribute)
.collect::<Vec<_>>();
let mut encoded = vec![0; predict_header_size(attributes.len())];
let mut value_start_offset = encode_header(&mut encoded, attributes.as_mut_slice());
for (reodered_entry_index, attribute) in attributes.iter().enumerate() {
let entry_index = entries
.iter()
.position(|(attribute_to_cmp, _, _)| attribute == attribute_to_cmp)
.unwrap();
update_value_start_offset(
&mut encoded,
reodered_entry_index,
entries.len(),
value_start_offset,
);
encoded.append(&mut vec![*attribute as u8; entries[entry_index].2]);
for slice in entries[entry_index].1.iter() {
update_value_fatptr(&mut encoded, value_start_offset, slice);
}
value_start_offset = encoded.len();
}

// Decode
let number_of_entries = get_number_of_entries(&encoded);
for (attribute, indirect_values, length) in entries.iter() {
let reodered_entry_index = resolve_attribute(&encoded, *attribute).0;
assert_eq!(*attribute, get_attribute(&encoded, reodered_entry_index));
if indirect_values.is_empty() {
let value_slice =
get_value(&encoded, reodered_entry_index, number_of_entries, None);
assert_eq!(*length, value_slice.len());
} else {
for (indirection_index, indirect_value) in indirect_values.iter().enumerate() {
let value_slice = get_value(
&encoded,
reodered_entry_index,
number_of_entries,
Some(indirection_index),
);
assert_eq!(indirect_value, &value_slice);
}
}
}
}
}
1 change: 1 addition & 0 deletions sdk/src/lib.rs
Expand Up @@ -9,6 +9,7 @@ extern crate self as solana_sdk;
pub use signer::signers;
pub use solana_program::*;

pub mod abi;
pub mod account;
pub mod account_utils;
pub mod builtins;
Expand Down

0 comments on commit 4058b12

Please sign in to comment.