Skip to content

Commit

Permalink
feat/refactor(rlp): improve implementations (#182)
Browse files Browse the repository at this point in the history
* feat/refactor(rlp): improve implementations

* fix: update rlp derive macros and primitives impls

* chore: where clause order
  • Loading branch information
DaniPopes authored Jul 9, 2023
1 parent 1c2373e commit 5664d52
Show file tree
Hide file tree
Showing 13 changed files with 728 additions and 596 deletions.
12 changes: 8 additions & 4 deletions crates/primitives/src/bits/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ macro_rules! wrap_fixed_bytes {

$crate::impl_fixed_bytes_traits!($name, $n);
$crate::impl_getrandom!($name);
$crate::impl_rlp!($name);
$crate::impl_rlp!($name, $n);
$crate::impl_serde!($name);
$crate::impl_arbitrary!($name, $n);

Expand Down Expand Up @@ -377,10 +377,10 @@ macro_rules! impl_getrandom {
#[macro_export]
#[cfg(feature = "rlp")]
macro_rules! impl_rlp {
($t:ty) => {
($t:ty, $n:literal) => {
impl $crate::private::alloy_rlp::Decodable for $t {
#[inline]
fn decode(buf: &mut &[u8]) -> Result<Self, $crate::private::alloy_rlp::DecodeError> {
fn decode(buf: &mut &[u8]) -> $crate::private::alloy_rlp::Result<Self> {
$crate::private::alloy_rlp::Decodable::decode(buf).map(Self)
}
}
Expand All @@ -396,14 +396,18 @@ macro_rules! impl_rlp {
$crate::private::alloy_rlp::Encodable::encode(&self.0, out)
}
}

$crate::private::alloy_rlp::impl_max_encoded_len!($t, {
$n + $crate::private::alloy_rlp::length_of_length($n)
});
};
}

#[doc(hidden)]
#[macro_export]
#[cfg(not(feature = "rlp"))]
macro_rules! impl_rlp {
($t:ty) => {};
($t:ty, $n:literal) => {};
}

#[doc(hidden)]
Expand Down
10 changes: 7 additions & 3 deletions crates/primitives/src/bits/rlp.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use super::FixedBytes;
use alloy_rlp::{impl_max_encoded_len, length_of_length, Decodable, Encodable};
use alloy_rlp::{length_of_length, Decodable, Encodable, MaxEncodedLen, MaxEncodedLenAssoc};

impl<const N: usize> Decodable for FixedBytes<N> {
#[inline]
fn decode(buf: &mut &[u8]) -> Result<Self, alloy_rlp::DecodeError> {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Decodable::decode(buf).map(Self)
}
}
Expand All @@ -24,8 +24,12 @@ impl<const N: usize> Encodable for FixedBytes<N> {
// https://github.com/rust-lang/rust/issues/76560
macro_rules! fixed_bytes_max_encoded_len {
($($sz:literal),+) => {$(
impl_max_encoded_len!(FixedBytes<$sz>, $sz + length_of_length($sz));
unsafe impl MaxEncodedLen<{ $sz + length_of_length($sz) }> for FixedBytes<$sz> {}
)+};
}

fixed_bytes_max_encoded_len!(0, 1, 2, 4, 8, 16, 20, 32, 64, 128, 256, 512, 1024);

unsafe impl<const N: usize> MaxEncodedLenAssoc for FixedBytes<N> {
const LEN: usize = N + length_of_length(N);
}
7 changes: 5 additions & 2 deletions crates/primitives/src/bytes/rlp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ use super::Bytes;
use alloy_rlp::{Decodable, Encodable};

impl Encodable for Bytes {
#[inline]
fn length(&self) -> usize {
self.0.length()
}

#[inline]
fn encode(&self, out: &mut dyn bytes::BufMut) {
self.0.encode(out)
}
}

impl Decodable for Bytes {
fn decode(buf: &mut &[u8]) -> Result<Self, alloy_rlp::DecodeError> {
Ok(Self(bytes::Bytes::decode(buf)?))
#[inline]
fn decode(buf: &mut &[u8]) -> Result<Self, alloy_rlp::Error> {
bytes::Bytes::decode(buf).map(Self)
}
}
120 changes: 58 additions & 62 deletions crates/rlp-derive/src/de.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::utils::{attributes_include, field_ident, is_optional, parse_struct, EMPTY_STRING_CODE};
use crate::utils::{
attributes_include, field_ident, is_optional, make_generics, parse_struct, EMPTY_STRING_CODE,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Error, Result};
Expand All @@ -11,92 +13,86 @@ pub(crate) fn impl_decodable(ast: &syn::DeriveInput) -> Result<TokenStream> {
let supports_trailing_opt = attributes_include(&ast.attrs, "trailing");

let mut encountered_opt_item = false;
let mut stmts = Vec::with_capacity(body.fields.len());
let mut decode_stmts = Vec::with_capacity(body.fields.len());
for (i, field) in fields {
let is_opt = is_optional(field);
if is_opt {
if !supports_trailing_opt {
return Err(Error::new_spanned(field, "Optional fields are disabled. Add `#[rlp(trailing)]` attribute to the struct in order to enable"));
let msg = "optional fields are disabled.\nAdd the `#[rlp(trailing)]` attribute to the struct in order to enable optional fields";
return Err(Error::new_spanned(field, msg))
}
encountered_opt_item = true;
} else if encountered_opt_item && !attributes_include(&field.attrs, "default") {
return Err(Error::new_spanned(
field,
"All subsequent fields must be either optional or default.",
))
let msg =
"all the fields after the first optional field must be either optional or default";
return Err(Error::new_spanned(field, msg))
}

stmts.push(decodable_field(i, field, is_opt));
decode_stmts.push(decodable_field(i, field, is_opt));
}

let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

let impl_block = quote! {
impl #impl_generics alloy_rlp::Decodable for #name #ty_generics #where_clause {
fn decode(mut buf: &mut &[u8]) -> Result<Self, alloy_rlp::DecodeError> {
let b = &mut &**buf;
let rlp_head = alloy_rlp::Header::decode(b)?;

if !rlp_head.list {
return Err(alloy_rlp::DecodeError::UnexpectedString);
}
if alloy_rlp::Buf::remaining(b) < rlp_head.payload_length {
return Err(alloy_rlp::DecodeError::InputTooShort);
}

let started_len = b.len();
let this = Self {
#(#stmts)*
};

let consumed = started_len - b.len();
if consumed != rlp_head.payload_length {
return Err(alloy_rlp::DecodeError::ListLengthMismatch {
expected: rlp_head.payload_length,
got: consumed,
});
}

*buf = *b;

Ok(this)
}
}
};
let generics = make_generics(&ast.generics, quote!(alloy_rlp::Decodable));
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

Ok(quote! {
const _: () = {
extern crate alloy_rlp;
#impl_block

impl #impl_generics alloy_rlp::Decodable for #name #ty_generics #where_clause {
#[inline]
fn decode(b: &mut &[u8]) -> alloy_rlp::Result<Self> {
let alloy_rlp::Header { list, payload_length } = alloy_rlp::Header::decode(b)?;
if !list {
return Err(alloy_rlp::Error::UnexpectedString);
}

let started_len = b.len();
if started_len < payload_length {
return Err(alloy_rlp::DecodeError::InputTooShort);
}

let this = Self {
#(#decode_stmts)*
};

let consumed = started_len - b.len();
if consumed != payload_length {
return Err(alloy_rlp::Error::ListLengthMismatch {
expected: payload_length,
got: consumed,
});
}

Ok(this)
}
}
};
})
}

pub(crate) fn impl_decodable_wrapper(ast: &syn::DeriveInput) -> Result<TokenStream> {
let body = parse_struct(ast, "RlpEncodableWrapper")?;

assert_eq!(
body.fields.iter().count(),
1,
"#[derive(RlpEncodableWrapper)] is only defined for structs with one field."
);
if body.fields.iter().count() != 1 {
let msg = "`RlpEncodableWrapper` is only defined for structs with one field.";
return Err(Error::new(ast.ident.span(), msg))
}

let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

let impl_block = quote! {
impl #impl_generics alloy_rlp::Decodable for #name #ty_generics #where_clause {
fn decode(buf: &mut &[u8]) -> Result<Self, alloy_rlp::DecodeError> {
Ok(Self(alloy_rlp::Decodable::decode(buf)?))
}
}
};
let generics = make_generics(&ast.generics, quote!(alloy_rlp::Decodable));
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

Ok(quote! {
const _: () = {
extern crate alloy_rlp;
#impl_block

impl #impl_generics alloy_rlp::Decodable for #name #ty_generics #where_clause {
#[inline]
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
alloy_rlp::private::Result::map(alloy_rlp::Decodable::decode(buf), Self)
}
}
};
})
}
Expand All @@ -105,12 +101,12 @@ fn decodable_field(index: usize, field: &syn::Field, is_opt: bool) -> TokenStrea
let ident = field_ident(index, field);

if attributes_include(&field.attrs, "default") {
quote! { #ident: Default::default(), }
quote! { #ident: alloy_rlp::private::Default::default(), }
} else if is_opt {
quote! {
#ident: if started_len - b.len() < rlp_head.payload_length {
if b.first().map(|b| *b == #EMPTY_STRING_CODE).unwrap_or_default() {
bytes::Buf::advance(b, 1);
#ident: if started_len - b.len() < payload_length {
if alloy_rlp::private::Option::map_or(b.first(), false, |b| *b == #EMPTY_STRING_CODE) {
alloy_rlp::Buf::advance(b, 1);
None
} else {
Some(alloy_rlp::Decodable::decode(b)?)
Expand Down
Loading

0 comments on commit 5664d52

Please sign in to comment.