Skip to content

Commit

Permalink
feat: add support for multibit fields and multibyte structs
Browse files Browse the repository at this point in the history
  • Loading branch information
Grigoriy Simonov committed Aug 10, 2023
1 parent 042edc3 commit d45a1f7
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 139 deletions.
143 changes: 86 additions & 57 deletions procedural/src/abi_derive/derive_flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,129 @@ use proc_macro2::TokenStream;
use quote::quote;

use super::extract_docs;
use crate::structs::common::{
get_left_and_mask, get_right_and_mask, BitMath, FieldInfo, StructInfo,
};

const ABI_TYPE_SIZE: usize = 4;
use crate::structs::common::{BitMath, FieldInfo, StructInfo};

pub fn impl_can_be_placed_in_vec(ident: &syn::Ident) -> TokenStream {
quote! {
impl ::evm_coder::sealed::CanBePlacedInVec for #ident {}
}
}

pub fn impl_struct_abi_type(name: &syn::Ident) -> TokenStream {
quote! {
fn align_size(name: &syn::Ident, total_bytes: usize) -> syn::Result<usize> {
Ok(match total_bytes {
1 => 1,
2..=4 => 4,
5..=8 => 8,
_ => {
return Err(syn::Error::new(
name.span(),
format!("Unsupported struct size: {total_bytes}"),
))
}
})
}

pub fn impl_struct_abi_type(name: &syn::Ident, total_bytes: usize) -> syn::Result<TokenStream> {
let sub_type = match total_bytes {
1 => quote! { u8 },
2..=4 => quote! { u32 },
5..=8 => quote! { u64 },
_ => {
return Err(syn::Error::new(
name.span(),
format!("Unsupported struct size: {total_bytes}"),
))
}
};

Ok(quote! {
impl ::evm_coder::abi::AbiType for #name {
const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = <(u32) as ::evm_coder::abi::AbiType>::SIGNATURE;
const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = <(#sub_type) as ::evm_coder::abi::AbiType>::SIGNATURE;
fn is_dynamic() -> bool {
<(u32) as ::evm_coder::abi::AbiType>::is_dynamic()
<(#sub_type) as ::evm_coder::abi::AbiType>::is_dynamic()
}
fn size() -> usize {
<(u32) as ::evm_coder::abi::AbiType>::size()
<(#sub_type) as ::evm_coder::abi::AbiType>::size()
}
}
}
})
}

pub fn impl_struct_abi_read(name: &syn::Ident, total_bytes: usize) -> TokenStream {
pub fn impl_struct_abi_read(name: &syn::Ident, total_bytes: usize) -> syn::Result<TokenStream> {
let aligned_size = align_size(name, total_bytes)?;
let bytes = (0..total_bytes).map(|i| {
let index = ABI_TYPE_SIZE - i - 1;
let index = total_bytes - i - 1;
quote! { value[#index] }
});
quote!(
Ok(quote!(
impl ::evm_coder::abi::AbiRead for #name {
fn abi_read(reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::abi::Result<Self> {
let value = reader.uint32()?.to_le_bytes();
let value = reader.bytes_padleft::<#aligned_size>()?;
Ok(#name::from_bytes([#(#bytes),*]))
}
}
)
))
}

pub fn impl_struct_abi_write(name: &syn::Ident, total_bytes: usize) -> TokenStream {
let bytes = (0..ABI_TYPE_SIZE).map(|i| {
let index = ABI_TYPE_SIZE - i - 1;
if i < ABI_TYPE_SIZE - total_bytes {
pub fn impl_struct_abi_write(name: &syn::Ident, total_bytes: usize) -> syn::Result<TokenStream> {
let aligned_size = align_size(name, total_bytes)?;
let bytes = (0..aligned_size).map(|i| {
if total_bytes < 1 + i {
quote! { 0 }
} else {
quote! { bytes[#index] }
let index = total_bytes - 1 - i;
quote! { value[#index] }
}
});
quote!(
Ok(quote!(
impl ::evm_coder::abi::AbiWrite for #name {
fn abi_write(&self, writer: &mut ::evm_coder::abi::AbiWriter) {
let bytes = self.clone().into_bytes();
let value = u32::from_le_bytes([#(#bytes),*]);
<(u32) as ::evm_coder::abi::AbiWrite>::abi_write(&(value), writer)
let value = self.clone().into_bytes();
writer.bytes_padleft(&[#(#bytes),*]);
}
}
)
))
}

pub fn impl_struct_solidity_type<'a>(
name: &syn::Ident,
docs: &[String],
total_bytes: usize,
fields: impl Iterator<Item = &'a FieldInfo> + Clone,
) -> TokenStream {
let solidity_name = name.to_string();
let solidity_fields = fields.map(|f| {
let name = f.ident.as_ref().to_string();
let docs = f.docs.clone();
let value = apply_le_math_to_mask(f);
quote! {
SolidityConstant {
docs: &[#(#docs),*],
name: #name,
value: #value,
let (amount_of_bits, zeros_on_left, _, _) = BitMath::from_field(f)
.map(|math| math.into_tuple())
.unwrap_or((0, 0, 0, 0));
let zeros_on_right = 8 - (zeros_on_left + amount_of_bits);
if amount_of_bits == 0 {
quote! {
SolidityFlagsField::Bool(SolidityFlagsBool {
docs: &[#(#docs),*],
name: #name,
value: 0,
})
}
} else if amount_of_bits == 1 {
let value: u8 = 1 << zeros_on_right;
quote! {
SolidityFlagsField::Bool(SolidityFlagsBool {
docs: &[#(#docs),*],
name: #name,
value: #value,
})
}
} else {
quote! {
SolidityFlagsField::Number(SolidityFlagsNumber {
docs: &[#(#docs),*],
name: #name,
start_bit: #zeros_on_right,
amount_of_bits: #amount_of_bits,
})
}
}
});
Expand All @@ -91,6 +138,7 @@ pub fn impl_struct_solidity_type<'a>(
let interface = SolidityLibrary {
docs: &[#(#docs),*],
name: #solidity_name,
total_bytes: #total_bytes,
fields: Vec::from([#(
#solidity_fields,
)*]),
Expand All @@ -104,26 +152,6 @@ pub fn impl_struct_solidity_type<'a>(
}
}

fn apply_le_math_to_mask(field: &FieldInfo) -> TokenStream {
let (amount_of_bits, zeros_on_left, available_bits_in_first_byte, ..) =
if let Ok(math) = BitMath::from_field(field) {
math.into_tuple()
} else {
return quote! { 0 };
};
if 8 < (zeros_on_left + amount_of_bits) {
return quote! { 0 };
}
let zeros_on_right = 8 - (zeros_on_left + amount_of_bits);
// combining the left and right masks will give us a mask that keeps the amount og bytes we
// have in the position we need them to be in for this byte. we use available_bytes for
// right mask because param is amount of 1's on the side specified (right), and
// available_bytes is (8 - zeros_on_left) which is equal to ones_on_right.
let mask =
get_right_and_mask(available_bits_in_first_byte) & get_left_and_mask(8 - zeros_on_right);
quote! { #mask }
}

pub fn impl_struct_solidity_type_name(name: &syn::Ident) -> TokenStream {
quote! {
#[cfg(feature = "stubgen")]
Expand All @@ -143,7 +171,7 @@ pub fn impl_struct_solidity_type_name(name: &syn::Ident) -> TokenStream {
writer: &mut impl ::core::fmt::Write,
tc: &::evm_coder::solidity::TypeCollector,
) -> ::core::fmt::Result {
write!(writer, "{}(0)", tc.collect_struct::<Self>())
write!(writer, "{}.wrap(0)", tc.collect_struct::<Self>())
}
}
}
Expand Down Expand Up @@ -173,10 +201,11 @@ pub fn expand_flags(ds: &syn::DataStruct, ast: &syn::DeriveInput) -> syn::Result

let total_bytes = struct_info.total_bytes();
let can_be_plcaed_in_vec = impl_can_be_placed_in_vec(name);
let abi_type = impl_struct_abi_type(name);
let abi_read = impl_struct_abi_read(name, total_bytes);
let abi_write = impl_struct_abi_write(name, total_bytes);
let solidity_type = impl_struct_solidity_type(name, &docs, struct_info.fields.iter());
let abi_type = impl_struct_abi_type(name, total_bytes)?;
let abi_read = impl_struct_abi_read(name, total_bytes)?;
let abi_write = impl_struct_abi_write(name, total_bytes)?;
let solidity_type =
impl_struct_solidity_type(name, &docs, total_bytes, struct_info.fields.iter());
let solidity_type_name = impl_struct_solidity_type_name(name);
Ok(quote! {
#can_be_plcaed_in_vec
Expand Down
30 changes: 0 additions & 30 deletions procedural/src/structs/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,6 @@ use crate::{
},
};

/// Returns a u8 mask with provided `num` amount of 1's on the left side (most significant bit)
pub fn get_left_and_mask(num: usize) -> u8 {
match num {
8 => 0b11111111,
7 => 0b11111110,
6 => 0b11111100,
5 => 0b11111000,
4 => 0b11110000,
3 => 0b11100000,
2 => 0b11000000,
1 => 0b10000000,
_ => 0b00000000,
}
}

/// Returns a u8 mask with provided `num` amount of 1's on the right side (least significant bit)
pub fn get_right_and_mask(num: usize) -> u8 {
match num {
8 => 0b11111111,
7 => 0b01111111,
6 => 0b00111111,
5 => 0b00011111,
4 => 0b00001111,
3 => 0b00000111,
2 => 0b00000011,
1 => 0b00000001,
_ => 0b00000000,
}
}

pub struct BitMath {
pub amount_of_bits: usize,
pub zeros_on_left: usize,
Expand Down
19 changes: 19 additions & 0 deletions src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,20 @@ impl<'i> AbiReader<'i> {
self.read_padright()
}

/// Read [`[u8; S]`] padded left at current position, then advance
pub fn bytes_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {
let offset = self.offset;
self.offset += ABI_ALIGNMENT;
Self::read_pad(
self.buf,
offset,
offset,
offset + ABI_ALIGNMENT - S,
offset + ABI_ALIGNMENT - S,
offset + ABI_ALIGNMENT,
)
}

/// Read [`Vec<u8>`] at current position, then advance
pub fn bytes(&mut self) -> Result<Vec<u8>> {
let mut subresult = self.subresult(None)?;
Expand Down Expand Up @@ -357,6 +371,11 @@ impl AbiWriter {
self.memory(value);
}

/// Write [`bytes`] to end of buffer
pub fn bytes_padleft(&mut self, block: &[u8]) {
self.write_padleft(block);
}

/// Finish writer, concatenating all internal buffers
#[must_use]
pub fn finish(mut self) -> Vec<u8> {
Expand Down
Loading

0 comments on commit d45a1f7

Please sign in to comment.