From 07f087ce7b6780155209a7ffc012dcd39c3b1ed3 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 12 Jul 2023 12:00:49 +0200 Subject: [PATCH] chore: move rlp crates to a separate repo (#187) --- .gitignore | 6 +- Cargo.toml | 13 +- README.md | 5 - crates/rlp-derive/Cargo.toml | 26 -- crates/rlp-derive/README.md | 18 - crates/rlp-derive/src/de.rs | 121 ------- crates/rlp-derive/src/en.rs | 222 ------------ crates/rlp-derive/src/lib.rs | 80 ----- crates/rlp-derive/src/utils.rs | 76 ---- crates/rlp/Cargo.toml | 34 -- crates/rlp/README.md | 51 --- crates/rlp/src/decode.rs | 340 ------------------ crates/rlp/src/encode.rs | 610 --------------------------------- crates/rlp/src/error.rs | 56 --- crates/rlp/src/header.rs | 155 --------- crates/rlp/src/lib.rs | 65 ---- crates/rlp/tests/derive.rs | 62 ---- 17 files changed, 7 insertions(+), 1933 deletions(-) delete mode 100644 crates/rlp-derive/Cargo.toml delete mode 100644 crates/rlp-derive/README.md delete mode 100644 crates/rlp-derive/src/de.rs delete mode 100644 crates/rlp-derive/src/en.rs delete mode 100644 crates/rlp-derive/src/lib.rs delete mode 100644 crates/rlp-derive/src/utils.rs delete mode 100644 crates/rlp/Cargo.toml delete mode 100644 crates/rlp/README.md delete mode 100644 crates/rlp/src/decode.rs delete mode 100644 crates/rlp/src/encode.rs delete mode 100644 crates/rlp/src/error.rs delete mode 100644 crates/rlp/src/header.rs delete mode 100644 crates/rlp/src/lib.rs delete mode 100644 crates/rlp/tests/derive.rs diff --git a/.gitignore b/.gitignore index 36d6b5abc1..76dead0f8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target +/target/ /Cargo.lock -.vscode -.idea \ No newline at end of file +/.vscode/ +/.idea/ diff --git a/Cargo.toml b/Cargo.toml index 327a367bf7..c4833f701e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,16 +21,11 @@ rustdoc-args = ["--cfg", "docsrs"] alloy-dyn-abi = { version = "0.2.0", path = "crates/dyn-abi", default-features = false } alloy-json-abi = { version = "0.2.0", path = "crates/json-abi", default-features = false } alloy-primitives = { version = "0.2.0", path = "crates/primitives", default-features = false } -alloy-rlp = { version = "0.2.0", path = "crates/rlp", default-features = false } -alloy-rlp-derive = { version = "0.2.0", path = "crates/rlp-derive", default-features = false } alloy-sol-macro = { version = "0.2.0", path = "crates/sol-macro", default-features = false } alloy-sol-type-parser = { version = "0.2.0", path = "crates/sol-type-parser", default-features = false } alloy-sol-types = { version = "0.2.0", path = "crates/sol-types", default-features = false } syn-solidity = { version = "0.2.0", path = "crates/syn-solidity", default-features = false } -ruint = { version = "1.9.0", package = "ruint2", default-features = false } -ruint-macro = { version = "1.0.3", package = "ruint2-macro", default-features = false } - # serde serde = { version = "1.0", default-features = false, features = ["alloc"] } serde_json = { version = "1.0", default-features = false, features = ["alloc"] } @@ -47,6 +42,8 @@ num_enum = "0.6" thiserror = "1.0" # misc +alloy-rlp = { version = "0.3.0", default-features = false } +alloy-rlp-derive = { version = "0.3.0", default-features = false } arbitrary = "1.3" arrayvec = { version = "0.7", default-features = false } bytes = { version = "1.4", default-features = false } @@ -58,8 +55,6 @@ itoa = "1" once_cell = "1" proptest = "1" proptest-derive = "0.3" +ruint = { version = "1.9.0", package = "ruint2", default-features = false } +ruint-macro = { version = "1.0.3", package = "ruint2-macro", default-features = false } tiny-keccak = "2.0" - -# make ruint2 depend on the local alloy-rlp -[patch.crates-io] -alloy-rlp = { path = "crates/rlp" } diff --git a/README.md b/README.md index 398c858446..9eb1ca7515 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,6 @@ No action is needed from devs. This repository contains the following crates: - [`alloy-primitives`] - Primitive integer and byte types -- [`alloy-rlp`] - Implementation of [Ethereum RLP serialization][rlp] -- [`alloy-rlp-derive`] - Derive macros for `alloy-rlp` - [`alloy-sol-type-parser`] - A simple parser for Solidity type strings - [`alloy-json-abi`] - [JSON-ABI] implementation - [`alloy-dyn-abi`] - Run-time ABI and [EIP-712] implementations @@ -30,15 +28,12 @@ This repository contains the following crates: - [`syn-solidity`] - [`syn`]-powered Solidity parser, used by `alloy-sol-macro` [`alloy-primitives`]: ./crates/primitives -[`alloy-rlp`]: ./crates/rlp -[`alloy-rlp-derive`]: ./crates/rlp-derive [`alloy-json-abi`]: ./crates/json-abi [`alloy-sol-type-parser`]: ./crates/sol-type-parser [`alloy-dyn-abi`]: ./crates/dyn-abi [`alloy-sol-types`]: ./crates/sol-types [`alloy-sol-macro`]: ./crates/sol-macro [`syn-solidity`]: ./crates/syn-solidity -[rlp]: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp [EIP-712]: https://eips.ethereum.org/EIPS/eip-712 [`syn`]: https://github.com/dtolnay/syn diff --git a/crates/rlp-derive/Cargo.toml b/crates/rlp-derive/Cargo.toml deleted file mode 100644 index 2a7e9718e9..0000000000 --- a/crates/rlp-derive/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "alloy-rlp-derive" -description = "Derive macros for alloy-rlp" -keywords = ["ethereum", "rlp", "serialization"] -categories = ["encoding", "cryptography::cryptocurrencies"] -homepage = "https://github.com/alloy-rs/core/tree/main/crates/rlp-derive" - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -repository.workspace = true -exclude.workspace = true - -[lib] -proc-macro = true - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[dependencies] -proc-macro2.workspace = true -quote.workspace = true -syn.workspace = true diff --git a/crates/rlp-derive/README.md b/crates/rlp-derive/README.md deleted file mode 100644 index a6d56d2161..0000000000 --- a/crates/rlp-derive/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# alloy-rlp-derive - -This crate provides derive macros for traits defined in -[`alloy-rlp`](https://docs.rs/alloy-rlp). See that crate's documentation for -more information. - -## Provenance note - -This crate was originally part of the [reth] project, as [`reth_rlp_derive`]. - -This was forked from an earlier Apache-licensed version of the -[`fastrlp`] crate, before it changed licence to GPL. The Rust `fastrlp` -implementation is itself a port of the [Golang Apache-licensed fastrlp][gofastrlp]. - -[reth]: https://github.com/paradigmxyz/reth -[`reth_rlp_derive`]: https://github.com/paradigmxyz/reth/tree/99a314c59bbd94a34a285369da95fb5604883c65/crates/rlp/rlp-derive -[`fastrlp`]: https://github.com/vorot93/fastrlp -[gofastrlp]: https://github.com/umbracle/fastrlp diff --git a/crates/rlp-derive/src/de.rs b/crates/rlp-derive/src/de.rs deleted file mode 100644 index e78166b597..0000000000 --- a/crates/rlp-derive/src/de.rs +++ /dev/null @@ -1,121 +0,0 @@ -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}; - -pub(crate) fn impl_decodable(ast: &syn::DeriveInput) -> Result { - let body = parse_struct(ast, "RlpDecodable")?; - - let fields = body.fields.iter().enumerate(); - - let supports_trailing_opt = attributes_include(&ast.attrs, "trailing"); - - let mut encountered_opt_item = false; - 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 { - 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") { - let msg = - "all the fields after the first optional field must be either optional or default"; - return Err(Error::new_spanned(field, msg)) - } - - decode_stmts.push(decodable_field(i, field, is_opt)); - } - - let name = &ast.ident; - 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 #impl_generics alloy_rlp::Decodable for #name #ty_generics #where_clause { - #[inline] - fn decode(b: &mut &[u8]) -> alloy_rlp::Result { - 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 { - let body = parse_struct(ast, "RlpEncodableWrapper")?; - - 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 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 #impl_generics alloy_rlp::Decodable for #name #ty_generics #where_clause { - #[inline] - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - alloy_rlp::private::Result::map(alloy_rlp::Decodable::decode(buf), Self) - } - } - }; - }) -} - -fn decodable_field(index: usize, field: &syn::Field, is_opt: bool) -> TokenStream { - let ident = field_ident(index, field); - - if attributes_include(&field.attrs, "default") { - quote! { #ident: alloy_rlp::private::Default::default(), } - } else if is_opt { - quote! { - #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)?) - } - } else { - None - }, - } - } else { - quote! { #ident: alloy_rlp::Decodable::decode(b)?, } - } -} diff --git a/crates/rlp-derive/src/en.rs b/crates/rlp-derive/src/en.rs deleted file mode 100644 index b5e0060274..0000000000 --- a/crates/rlp-derive/src/en.rs +++ /dev/null @@ -1,222 +0,0 @@ -use crate::utils::{ - attributes_include, field_ident, is_optional, make_generics, parse_struct, EMPTY_STRING_CODE, -}; -use proc_macro2::TokenStream; -use quote::quote; -use std::iter::Peekable; -use syn::{Error, Result}; - -pub(crate) fn impl_encodable(ast: &syn::DeriveInput) -> Result { - let body = parse_struct(ast, "RlpEncodable")?; - - let mut fields = body - .fields - .iter() - .enumerate() - .filter(|(_, field)| !attributes_include(&field.attrs, "skip")) - .peekable(); - - let supports_trailing_opt = attributes_include(&ast.attrs, "trailing"); - - let mut encountered_opt_item = false; - let mut length_exprs = Vec::with_capacity(body.fields.len()); - let mut encode_exprs = Vec::with_capacity(body.fields.len()); - - while let Some((i, field)) = fields.next() { - let is_opt = is_optional(field); - if is_opt { - if !supports_trailing_opt { - 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 { - let msg = "all the fields after the first optional field must be optional"; - return Err(Error::new_spanned(field, msg)) - } - - length_exprs.push(encodable_length(i, field, is_opt, fields.clone())); - encode_exprs.push(encodable_field(i, field, is_opt, fields.clone())); - } - - let name = &ast.ident; - let generics = make_generics(&ast.generics, quote!(alloy_rlp::Encodable)); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - Ok(quote! { - const _: () = { - extern crate alloy_rlp; - - impl #impl_generics alloy_rlp::Encodable for #name #ty_generics #where_clause { - #[inline] - fn length(&self) -> usize { - let payload_length = self._alloy_rlp_payload_length(); - payload_length + alloy_rlp::length_of_length(payload_length) - } - - #[inline] - fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - alloy_rlp::Header { - list: true, - payload_length: self._alloy_rlp_payload_length(), - } - .encode(out); - #(#encode_exprs)* - } - } - - impl #impl_generics #name #ty_generics #where_clause { - #[allow(unused_parens)] - #[inline] - fn _alloy_rlp_payload_length(&self) -> usize { - 0usize #( + #length_exprs)* - } - } - }; - }) -} - -pub(crate) fn impl_encodable_wrapper(ast: &syn::DeriveInput) -> Result { - let body = parse_struct(ast, "RlpEncodableWrapper")?; - - let name = &ast.ident; - let generics = make_generics(&ast.generics, quote!(alloy_rlp::Encodable)); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - let ident = { - let fields: Vec<_> = body.fields.iter().collect(); - if let [field] = fields[..] { - field_ident(0, field) - } else { - let msg = "`RlpEncodableWrapper` is only derivable for structs with one field"; - return Err(Error::new(name.span(), msg)) - } - }; - - Ok(quote! { - const _: () = { - extern crate alloy_rlp; - - impl #impl_generics alloy_rlp::Encodable for #name #ty_generics #where_clause { - #[inline] - fn length(&self) -> usize { - alloy_rlp::Encodable::length(&self.#ident) - } - - #[inline] - fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - alloy_rlp::Encodable::encode(&self.#ident, out) - } - } - }; - }) -} - -pub(crate) fn impl_max_encoded_len(ast: &syn::DeriveInput) -> Result { - let body = parse_struct(ast, "RlpMaxEncodedLen")?; - - let tys = body - .fields - .iter() - .filter(|field| !attributes_include(&field.attrs, "skip")) - .map(|field| &field.ty); - - let name = &ast.ident; - - let generics = make_generics(&ast.generics, quote!(alloy_rlp::MaxEncodedLenAssoc)); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - let imp = quote! {{ - let _sz = 0usize #( + <#tys as alloy_rlp::MaxEncodedLenAssoc>::LEN )*; - _sz + alloy_rlp::length_of_length(_sz) - }}; - - // can't do operations with const generic params / associated consts in the - // non-associated impl - let can_derive_non_assoc = ast - .generics - .params - .iter() - .all(|g| !matches!(g, syn::GenericParam::Type(_) | syn::GenericParam::Const(_))); - let non_assoc_impl = can_derive_non_assoc.then(|| { - quote! { - unsafe impl #impl_generics alloy_rlp::MaxEncodedLen<#imp> for #name #ty_generics #where_clause {} - } - }); - - Ok(quote! { - #[allow(unsafe_code)] - const _: () = { - extern crate alloy_rlp; - - #non_assoc_impl - - unsafe impl #impl_generics alloy_rlp::MaxEncodedLenAssoc for #name #ty_generics #where_clause { - const LEN: usize = #imp; - } - }; - }) -} - -fn encodable_length<'a>( - index: usize, - field: &syn::Field, - is_opt: bool, - mut remaining: Peekable>, -) -> TokenStream { - let ident = field_ident(index, field); - - if is_opt { - let default = if remaining.peek().is_some() { - let condition = remaining_opt_fields_some_condition(remaining); - quote! { (#condition) as usize } - } else { - quote! { 0 } - }; - - quote! { self.#ident.as_ref().map(|val| alloy_rlp::Encodable::length(val)).unwrap_or(#default) } - } else { - quote! { alloy_rlp::Encodable::length(&self.#ident) } - } -} - -fn encodable_field<'a>( - index: usize, - field: &syn::Field, - is_opt: bool, - mut remaining: Peekable>, -) -> TokenStream { - let ident = field_ident(index, field); - - if is_opt { - let if_some_encode = quote! { - if let Some(val) = self.#ident.as_ref() { - alloy_rlp::Encodable::encode(val, out) - } - }; - - if remaining.peek().is_some() { - let condition = remaining_opt_fields_some_condition(remaining); - quote! { - #if_some_encode - else if #condition { - out.put_u8(#EMPTY_STRING_CODE); - } - } - } else { - quote! { #if_some_encode } - } - } else { - quote! { alloy_rlp::Encodable::encode(&self.#ident, out); } - } -} - -fn remaining_opt_fields_some_condition<'a>( - remaining: impl Iterator, -) -> TokenStream { - let conditions = remaining.map(|(index, field)| { - let ident = field_ident(index, field); - quote! { self.#ident.is_some() } - }); - quote! { #(#conditions)||* } -} diff --git a/crates/rlp-derive/src/lib.rs b/crates/rlp-derive/src/lib.rs deleted file mode 100644 index 5a2d921bf8..0000000000 --- a/crates/rlp-derive/src/lib.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Derive macro for `#[derive(RlpEncodable, RlpDecodable)]`. -//! -//! For example of usage see `./tests/rlp.rs`. -//! -//! This library also supports up to 1 `#[rlp(default)]` in a struct, -//! which is similar to [`#[serde(default)]`](https://serde.rs/field-attrs.html#default) -//! with the caveat that we use the `Default` value if -//! the field deserialization fails, as we don't serialize field -//! names and there is no way to tell if it is present or not. - -#![doc( - html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg", - html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico" -)] -#![warn(missing_docs, unreachable_pub, rustdoc::all)] -#![deny(unused_must_use, unused_crate_dependencies)] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -extern crate proc_macro; - -mod de; -mod en; -mod utils; - -use de::*; -use en::*; -use proc_macro::TokenStream; - -/// Derives `Encodable` for the type which encodes the all fields as list: -/// `` -#[proc_macro_derive(RlpEncodable, attributes(rlp))] -pub fn encodable(input: TokenStream) -> TokenStream { - syn::parse(input) - .and_then(|ast| impl_encodable(&ast)) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - -/// Derives `Encodable` for the type which encodes the fields as-is, without a -/// header: `` -#[proc_macro_derive(RlpEncodableWrapper, attributes(rlp))] -pub fn encodable_wrapper(input: TokenStream) -> TokenStream { - syn::parse(input) - .and_then(|ast| impl_encodable_wrapper(&ast)) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - -/// Derives `MaxEncodedLen` for types of constant size. -#[proc_macro_derive(RlpMaxEncodedLen, attributes(rlp))] -pub fn max_encoded_len(input: TokenStream) -> TokenStream { - syn::parse(input) - .and_then(|ast| impl_max_encoded_len(&ast)) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - -/// Derives `Decodable` for the type whose implementation expects an rlp-list -/// input: `` -/// -/// This is the inverse of `RlpEncodable`. -#[proc_macro_derive(RlpDecodable, attributes(rlp))] -pub fn decodable(input: TokenStream) -> TokenStream { - syn::parse(input) - .and_then(|ast| impl_decodable(&ast)) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - -/// Derives `Decodable` for the type whose implementation expects only the -/// individual fields encoded: `` -/// -/// This is the inverse of `RlpEncodableWrapper`. -#[proc_macro_derive(RlpDecodableWrapper, attributes(rlp))] -pub fn decodable_wrapper(input: TokenStream) -> TokenStream { - syn::parse(input) - .and_then(|ast| impl_decodable_wrapper(&ast)) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} diff --git a/crates/rlp-derive/src/utils.rs b/crates/rlp-derive/src/utils.rs deleted file mode 100644 index c1641cdd53..0000000000 --- a/crates/rlp-derive/src/utils.rs +++ /dev/null @@ -1,76 +0,0 @@ -use proc_macro2::TokenStream; -use quote::quote; -use syn::{ - parse_quote, Attribute, DataStruct, Error, Field, GenericParam, Generics, Meta, Result, Type, - TypePath, -}; - -pub(crate) const EMPTY_STRING_CODE: u8 = 0x80; - -pub(crate) fn parse_struct<'a>( - ast: &'a syn::DeriveInput, - derive_attr: &str, -) -> Result<&'a DataStruct> { - if let syn::Data::Struct(s) = &ast.data { - Ok(s) - } else { - Err(Error::new_spanned( - ast, - format!("#[derive({derive_attr})] is only defined for structs."), - )) - } -} - -pub(crate) fn attributes_include(attrs: &[Attribute], attr_name: &str) -> bool { - for attr in attrs.iter() { - if attr.path().is_ident("rlp") { - if let Meta::List(meta) = &attr.meta { - let mut is_attr = false; - let _ = meta.parse_nested_meta(|meta| { - is_attr = meta.path.is_ident(attr_name); - Ok(()) - }); - if is_attr { - return true - } - } - } - } - false -} - -pub(crate) fn is_optional(field: &Field) -> bool { - if let Type::Path(TypePath { qself, path }) = &field.ty { - qself.is_none() - && path.leading_colon.is_none() - && path.segments.len() == 1 - && path.segments.first().unwrap().ident == "Option" - } else { - false - } -} - -pub(crate) fn field_ident(index: usize, field: &syn::Field) -> TokenStream { - if let Some(ident) = &field.ident { - quote! { #ident } - } else { - let index = syn::Index::from(index); - quote! { #index } - } -} - -pub(crate) fn make_generics(generics: &Generics, trait_name: TokenStream) -> Generics { - let mut generics = generics.clone(); - generics.make_where_clause(); - let mut where_clause = generics.where_clause.take().unwrap(); - - for generic in &generics.params { - if let GenericParam::Type(ty) = &generic { - let t = &ty.ident; - let pred = parse_quote!(#t: #trait_name); - where_clause.predicates.push(pred); - } - } - generics.where_clause = Some(where_clause); - generics -} diff --git a/crates/rlp/Cargo.toml b/crates/rlp/Cargo.toml deleted file mode 100644 index d43d2fda79..0000000000 --- a/crates/rlp/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "alloy-rlp" -description = "Implementation of Ethereum RLP serialization" -keywords = ["ethereum", "rlp", "serialization"] -categories = ["no-std", "encoding", "cryptography::cryptocurrencies"] -homepage = "https://github.com/alloy-rs/core/tree/main/crates/rlp" - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -repository.workspace = true -exclude.workspace = true - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[dependencies] -bytes.workspace = true -alloy-rlp-derive = { workspace = true, optional = true } -arrayvec = { workspace = true, optional = true } -smol_str = { version = "0.2", default-features = false, optional = true } - -[dev-dependencies] -hex-literal.workspace = true - -[features] -default = ["std"] -std = ["bytes/std", "arrayvec?/std", "smol_str?/std"] -derive = ["dep:alloy-rlp-derive"] -arrayvec = ["dep:arrayvec"] -smol_str = ["dep:smol_str"] diff --git a/crates/rlp/README.md b/crates/rlp/README.md deleted file mode 100644 index ed61ded7e1..0000000000 --- a/crates/rlp/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# alloy-rlp - -This crate provides Ethereum RLP (de)serialization functionality. RLP is -commonly used for Ethereum EL datastructures, and its documentation can be -found [at ethereum.org][ref]. - -[ref]: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ - -## Usage - -We strongly recommend deriving RLP traits via the `RlpEncodable` and -`RlpDecodable` derive macros. - -Trait methods can then be accessed via the `Encodable` and `Decodable` traits. - -## Example - -```rust -# #[cfg(feature = "derive")] { -use alloy_rlp::{RlpEncodable, RlpDecodable, Decodable, Encodable}; - -#[derive(Debug, RlpEncodable, RlpDecodable, PartialEq)] -pub struct MyStruct { - pub a: u64, - pub b: Vec, -} - -let my_struct = MyStruct { - a: 42, - b: vec![1, 2, 3], -}; - -let mut buffer = Vec::::new(); -let encoded = my_struct.encode(&mut buffer); -let decoded = MyStruct::decode(&mut buffer.as_slice()).unwrap(); -assert_eq!(my_struct, decoded); -# } -``` - -## Provenance note - -This crate was originally part of the [reth] project, as [`reth_rlp`]. - -This was forked from an earlier Apache-licensed version of the [`fastrlp`] -crate, before it changed licence to GPL. The Rust `fastrlp` implementation is -itself a port of the [Golang Apache-licensed fastrlp][gofastrlp]. - -[reth]: https://github.com/paradigmxyz/reth -[`reth_rlp`]: https://github.com/paradigmxyz/reth/tree/99a314c59bbd94a34a285369da95fb5604883c65/crates/rlp -[`fastrlp`]: https://github.com/vorot93/fastrlp -[gofastrlp]: https://github.com/umbracle/fastrlp diff --git a/crates/rlp/src/decode.rs b/crates/rlp/src/decode.rs deleted file mode 100644 index 9677d68287..0000000000 --- a/crates/rlp/src/decode.rs +++ /dev/null @@ -1,340 +0,0 @@ -use crate::{Error, Header, Result}; -use bytes::{Bytes, BytesMut}; - -/// A type that can be decoded from an RLP blob. -pub trait Decodable: Sized { - /// Decode the blob into the appropriate type. - fn decode(buf: &mut &[u8]) -> Result; -} - -/// An active RLP decoder, with a specific slice of a payload. -pub struct Rlp<'a> { - payload_view: &'a [u8], -} - -impl<'a> Rlp<'a> { - /// Instantiate an RLP decoder with a payload slice. - pub fn new(mut payload: &'a [u8]) -> Result { - let payload_view = Header::decode_bytes(&mut payload, true)?; - Ok(Self { payload_view }) - } - - /// Decode the next item from the buffer. - #[inline] - pub fn get_next(&mut self) -> Result> { - if self.payload_view.is_empty() { - Ok(None) - } else { - T::decode(&mut self.payload_view).map(Some) - } - } -} - -impl Decodable for bool { - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - Ok(match u8::decode(buf)? { - 0 => false, - 1 => true, - _ => return Err(Error::Custom("invalid bool value, must be 0 or 1")), - }) - } -} - -impl Decodable for [u8; N] { - #[inline] - fn decode(from: &mut &[u8]) -> Result { - let bytes = Header::decode_bytes(from, false)?; - Self::try_from(bytes).map_err(|_| Error::UnexpectedLength) - } -} - -macro_rules! decode_integer { - ($($t:ty),+ $(,)?) => {$( - impl Decodable for $t { - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - let bytes = Header::decode_bytes(buf, false)?; - static_left_pad(bytes).map(<$t>::from_be_bytes) - } - } - )+}; -} - -decode_integer!(u8, u16, u32, u64, usize, u128); - -impl Decodable for Bytes { - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - Header::decode_bytes(buf, false).map(|x| Self::from(x.to_vec())) - } -} - -impl Decodable for BytesMut { - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - Header::decode_bytes(buf, false).map(Self::from) - } -} - -impl Decodable for alloc::string::String { - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - Header::decode_str(buf).map(Into::into) - } -} - -impl Decodable for alloc::vec::Vec { - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - let mut bytes = Header::decode_bytes(buf, true)?; - let mut vec = Self::new(); - let payload_view = &mut bytes; - while !payload_view.is_empty() { - vec.push(T::decode(payload_view)?); - } - Ok(vec) - } -} - -#[cfg(feature = "smol_str")] -impl Decodable for smol_str::SmolStr { - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - Header::decode_str(buf).map(Into::into) - } -} - -macro_rules! wrap_impl { - ($($(#[$attr:meta])* [$($gen:tt)*] <$t:ty>::$new:ident($t2:ty)),+ $(,)?) => {$( - $(#[$attr])* - impl<$($gen)*> Decodable for $t { - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - <$t2 as Decodable>::decode(buf).map(<$t>::$new) - } - } - )+}; -} - -wrap_impl! { - #[cfg(feature = "arrayvec")] - [const N: usize] >::from([u8; N]), - [T: ?Sized + Decodable] >::new(T), - [T: ?Sized + Decodable] >::new(T), - [T: ?Sized + Decodable] >::new(T), -} - -impl Decodable for alloc::borrow::Cow<'_, T> -where - T::Owned: Decodable, -{ - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - T::Owned::decode(buf).map(Self::Owned) - } -} - -#[cfg(feature = "std")] -mod std_impl { - use super::*; - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - - impl Decodable for IpAddr { - fn decode(buf: &mut &[u8]) -> Result { - let bytes = Header::decode_bytes(buf, false)?; - match bytes.len() { - 4 => static_left_pad(bytes).map(|octets| Self::V4(Ipv4Addr::from(octets))), - 16 => static_left_pad(bytes).map(|octets| Self::V6(Ipv6Addr::from(octets))), - _ => Err(Error::UnexpectedLength), - } - } - } - - impl Decodable for Ipv4Addr { - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - let bytes = Header::decode_bytes(buf, false)?; - static_left_pad::<4>(bytes).map(Self::from) - } - } - - impl Decodable for Ipv6Addr { - #[inline] - fn decode(buf: &mut &[u8]) -> Result { - let bytes = Header::decode_bytes(buf, false)?; - static_left_pad::<16>(bytes).map(Self::from) - } - } -} - -/// Left-pads a slice to a statically known size array. -/// -/// # Errors -/// -/// Returns an error if the slice is too long or if the first byte is 0. -#[inline] -pub(crate) fn static_left_pad(data: &[u8]) -> Result<[u8; N]> { - if data.len() > N { - return Err(Error::Overflow) - } - - let mut v = [0; N]; - - if data.is_empty() { - return Ok(v) - } - - if data[0] == 0 { - return Err(Error::LeadingZero) - } - - // SAFETY: length checked above - unsafe { v.get_unchecked_mut(N - data.len()..) }.copy_from_slice(data); - Ok(v) -} - -#[cfg(test)] -mod tests { - use super::*; - use alloc::{string::String, vec::Vec}; - use core::fmt::Debug; - use hex_literal::hex; - - fn check_decode<'a, T, IT>(fixtures: IT) - where - T: Decodable + PartialEq + Debug, - IT: IntoIterator, &'a [u8])>, - { - for (expected, mut input) in fixtures { - assert_eq!(T::decode(&mut input), expected); - if expected.is_ok() { - assert_eq!(input, &[]); - } - } - } - - fn check_decode_list(fixtures: IT) - where - T: Decodable + PartialEq + Debug, - IT: IntoIterator>, &'static [u8])>, - { - for (expected, mut input) in fixtures { - assert_eq!(Vec::::decode(&mut input), expected); - if expected.is_ok() { - assert_eq!(input, &[]); - } - } - } - - #[test] - fn rlp_strings() { - check_decode::(vec![ - (Ok(hex!("00")[..].to_vec().into()), &hex!("00")[..]), - ( - Ok(hex!("6f62636465666768696a6b6c6d")[..].to_vec().into()), - &hex!("8D6F62636465666768696A6B6C6D")[..], - ), - (Err(Error::UnexpectedList), &hex!("C0")[..]), - ]) - } - - #[cfg(feature = "smol_str")] - #[test] - fn rlp_smol_str() { - use crate::Encodable; - use alloc::string::ToString; - use smol_str::SmolStr; - - let mut b = BytesMut::new(); - "test smol str".to_string().encode(&mut b); - check_decode::(vec![ - (Ok(SmolStr::new("test smol str")), b.as_ref()), - (Err(Error::UnexpectedList), &hex!("C0")[..]), - ]) - } - - #[test] - fn rlp_fixed_length() { - check_decode(vec![ - ( - Ok(hex!("6f62636465666768696a6b6c6d")), - &hex!("8D6F62636465666768696A6B6C6D")[..], - ), - ( - Err(Error::UnexpectedLength), - &hex!("8C6F62636465666768696A6B6C")[..], - ), - ( - Err(Error::UnexpectedLength), - &hex!("8E6F62636465666768696A6B6C6D6E")[..], - ), - ]) - } - - #[test] - fn rlp_u64() { - check_decode(vec![ - (Ok(9_u64), &hex!("09")[..]), - (Ok(0_u64), &hex!("80")[..]), - (Ok(0x0505_u64), &hex!("820505")[..]), - (Ok(0xCE05050505_u64), &hex!("85CE05050505")[..]), - (Err(Error::Overflow), &hex!("8AFFFFFFFFFFFFFFFFFF7C")[..]), - ( - Err(Error::InputTooShort), - &hex!("8BFFFFFFFFFFFFFFFFFF7C")[..], - ), - (Err(Error::UnexpectedList), &hex!("C0")[..]), - (Err(Error::LeadingZero), &hex!("00")[..]), - (Err(Error::NonCanonicalSingleByte), &hex!("8105")[..]), - (Err(Error::LeadingZero), &hex!("8200F4")[..]), - (Err(Error::NonCanonicalSize), &hex!("B8020004")[..]), - ( - Err(Error::Overflow), - &hex!("A101000000000000000000000000000000000000008B000000000000000000000000")[..], - ), - ]) - } - - #[test] - fn rlp_vectors() { - check_decode_list(vec![ - (Ok(vec![]), &hex!("C0")[..]), - ( - Ok(vec![0xBBCCB5_u64, 0xFFC0B5_u64]), - &hex!("C883BBCCB583FFC0B5")[..], - ), - ]) - } - - #[test] - fn malformed_rlp() { - check_decode::(vec![ - (Err(Error::InputTooShort), &hex!("C1")[..]), - (Err(Error::InputTooShort), &hex!("D7")[..]), - ]); - check_decode::<[u8; 5], _>(vec![ - (Err(Error::InputTooShort), &hex!("C1")[..]), - (Err(Error::InputTooShort), &hex!("D7")[..]), - ]); - #[cfg(feature = "std")] - check_decode::(vec![ - (Err(Error::InputTooShort), &hex!("C1")[..]), - (Err(Error::InputTooShort), &hex!("D7")[..]), - ]); - check_decode::, _>(vec![ - (Err(Error::InputTooShort), &hex!("C1")[..]), - (Err(Error::InputTooShort), &hex!("D7")[..]), - ]); - check_decode::(vec![ - (Err(Error::InputTooShort), &hex!("C1")[..]), - (Err(Error::InputTooShort), &hex!("D7")[..]), - ]); - check_decode::(vec![ - (Err(Error::InputTooShort), &hex!("C1")[..]), - (Err(Error::InputTooShort), &hex!("D7")[..]), - ]); - check_decode::(vec![(Err(Error::InputTooShort), &hex!("82")[..])]); - check_decode::(vec![(Err(Error::InputTooShort), &hex!("82")[..])]); - } -} diff --git a/crates/rlp/src/encode.rs b/crates/rlp/src/encode.rs deleted file mode 100644 index 8059c0917b..0000000000 --- a/crates/rlp/src/encode.rs +++ /dev/null @@ -1,610 +0,0 @@ -use crate::{Header, EMPTY_STRING_CODE}; -use bytes::{BufMut, Bytes, BytesMut}; -use core::borrow::Borrow; - -#[cfg(feature = "arrayvec")] -use arrayvec::ArrayVec; - -/// A type that can be encoded via RLP. -pub trait Encodable { - /// Encodes the type into the `out` buffer. - fn encode(&self, out: &mut dyn BufMut); - - /// Returns the length of the encoding of this type in bytes. - /// - /// The default implementation computes this by encoding the type. - /// When possible, we recommender implementers override this with a - /// specialized implementation. - #[inline] - fn length(&self) -> usize { - let mut out = alloc::vec::Vec::new(); - self.encode(&mut out); - out.len() - } -} - -// The existence of this function makes the compiler catch if the Encodable -// trait is "object-safe" or not. -fn _assert_trait_object(_b: &dyn Encodable) {} - -/// Defines the max length of an [`Encodable`] type as a const generic. -/// -/// # Safety -/// -/// An invalid value can cause the encoder to panic. -pub unsafe trait MaxEncodedLen: Encodable {} - -/// Defines the max length of an [`Encodable`] type as an associated constant. -/// -/// # Safety -/// -/// An invalid value can cause the encoder to panic. -pub unsafe trait MaxEncodedLenAssoc: Encodable { - /// The maximum length. - const LEN: usize; -} - -/// Implement [`MaxEncodedLen`] and [`MaxEncodedLenAssoc`] for a type. -/// -/// # Safety -/// -/// An invalid value can cause the encoder to panic. -#[macro_export] -macro_rules! impl_max_encoded_len { - ($t:ty, $len:expr) => { - unsafe impl $crate::MaxEncodedLen<{ $len }> for $t {} - unsafe impl $crate::MaxEncodedLenAssoc for $t { - const LEN: usize = $len; - } - }; -} - -macro_rules! to_be_bytes_trimmed { - ($be:ident, $x:expr) => {{ - $be = $x.to_be_bytes(); - &$be[($x.leading_zeros() / 8) as usize..] - }}; -} -pub(crate) use to_be_bytes_trimmed; - -impl Encodable for [u8] { - #[inline] - fn length(&self) -> usize { - let mut len = self.len(); - if len != 1 || self[0] >= EMPTY_STRING_CODE { - len += length_of_length(len); - } - len - } - - #[inline] - fn encode(&self, out: &mut dyn BufMut) { - if self.len() != 1 || self[0] >= EMPTY_STRING_CODE { - Header { - list: false, - payload_length: self.len(), - } - .encode(out); - } - out.put_slice(self); - } -} - -impl Encodable for [u8; N] { - #[inline] - fn length(&self) -> usize { - self[..].length() - } - - #[inline] - fn encode(&self, out: &mut dyn BufMut) { - self[..].encode(out); - } -} - -unsafe impl MaxEncodedLenAssoc for [u8; N] { - const LEN: usize = N + length_of_length(N); -} - -impl Encodable for str { - #[inline] - fn length(&self) -> usize { - self.as_bytes().length() - } - - #[inline] - fn encode(&self, out: &mut dyn BufMut) { - self.as_bytes().encode(out) - } -} - -impl Encodable for bool { - #[inline] - fn length(&self) -> usize { - // a `bool` is always `< EMPTY_STRING_CODE` - 1 - } - - #[inline] - fn encode(&self, out: &mut dyn BufMut) { - // inlined `(*self as u8).encode(out)` - out.put_u8(if *self { 1 } else { EMPTY_STRING_CODE }); - } -} - -impl_max_encoded_len!(bool, ::LEN); - -macro_rules! uint_impl { - ($($t:ty),+ $(,)?) => {$( - impl Encodable for $t { - #[inline] - fn length(&self) -> usize { - let x = *self; - if x < EMPTY_STRING_CODE as $t { - 1 - } else { - 1 + (<$t>::BITS as usize / 8) - (x.leading_zeros() as usize / 8) - } - } - - #[inline] - fn encode(&self, out: &mut dyn BufMut) { - let x = *self; - if x == 0 { - out.put_u8(EMPTY_STRING_CODE); - } else if x < EMPTY_STRING_CODE as $t { - out.put_u8(x as u8); - } else { - let be; - let be = to_be_bytes_trimmed!(be, x); - out.put_u8(EMPTY_STRING_CODE + be.len() as u8); - out.put_slice(be); - } - } - } - - impl_max_encoded_len!($t, { - let bytes = <$t>::BITS as usize / 8; - bytes + length_of_length(bytes) - }); - )+}; -} - -uint_impl!(u8, u16, u32, u64, usize, u128); - -impl Encodable for alloc::vec::Vec { - #[inline] - fn length(&self) -> usize { - list_length(self) - } - - #[inline] - fn encode(&self, out: &mut dyn BufMut) { - encode_list(self, out) - } -} - -macro_rules! deref_impl { - ($($(#[$attr:meta])* [$($gen:tt)*] $t:ty),+ $(,)?) => {$( - $(#[$attr])* - impl<$($gen)*> Encodable for $t { - #[inline] - fn length(&self) -> usize { - (**self).length() - } - - #[inline] - fn encode(&self, out: &mut dyn BufMut) { - (**self).encode(out) - } - } - )+}; -} - -deref_impl! { - [] alloc::string::String, - [] Bytes, - [] BytesMut, - #[cfg(feature = "smol_str")] - [] smol_str::SmolStr, - #[cfg(feature = "arrayvec")] - [const N: usize] ArrayVec, - [T: ?Sized + Encodable] &T, - [T: ?Sized + Encodable] &mut T, - [T: ?Sized + Encodable] alloc::boxed::Box, - [T: ?Sized + alloc::borrow::ToOwned + Encodable] alloc::borrow::Cow<'_, T>, - [T: ?Sized + Encodable] alloc::rc::Rc, - [T: ?Sized + Encodable] alloc::sync::Arc, -} - -#[cfg(feature = "std")] -mod std_support { - use super::*; - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - - impl Encodable for IpAddr { - #[inline] - fn length(&self) -> usize { - match self { - IpAddr::V4(ip) => ip.length(), - IpAddr::V6(ip) => ip.length(), - } - } - - #[inline] - fn encode(&self, out: &mut dyn BufMut) { - match self { - IpAddr::V4(ip) => ip.encode(out), - IpAddr::V6(ip) => ip.encode(out), - } - } - } - - impl Encodable for Ipv4Addr { - #[inline] - fn length(&self) -> usize { - self.octets().length() - } - - #[inline] - fn encode(&self, out: &mut dyn BufMut) { - self.octets().encode(out) - } - } - - impl Encodable for Ipv6Addr { - #[inline] - fn length(&self) -> usize { - self.octets().length() - } - - #[inline] - fn encode(&self, out: &mut dyn BufMut) { - self.octets().encode(out) - } - } -} - -#[inline] -fn rlp_list_header(v: &[T]) -> Header -where - T: Borrow, - E: ?Sized + Encodable, -{ - let mut h = Header { - list: true, - payload_length: 0, - }; - for x in v { - h.payload_length += x.borrow().length(); - } - h -} - -/// Calculate the length of a list. -pub fn list_length(list: &[T]) -> usize -where - T: Borrow, - E: ?Sized + Encodable, -{ - let payload_length = rlp_list_header(list).payload_length; - length_of_length(payload_length) + payload_length -} - -/// Encode a list of items. -pub fn encode_list(list: &[T], out: &mut dyn BufMut) -where - T: Borrow, - E: ?Sized + Encodable, -{ - let h = rlp_list_header(list); - h.encode(out); - for t in list { - t.borrow().encode(out); - } -} - -/// Encode all items from an iterator. -/// -/// This clones the iterator. Prefer [`encode_list`] if possible. -pub fn encode_iter(iter: I, out: &mut dyn BufMut) -where - I: Iterator + Clone, - T: Borrow, - E: ?Sized + Encodable, -{ - let mut h = Header { - list: true, - payload_length: 0, - }; - for t in iter.clone() { - h.payload_length += t.borrow().length(); - } - - h.encode(out); - for t in iter { - t.borrow().encode(out); - } -} - -/// Encode a type with a known maximum size. -#[cfg(feature = "arrayvec")] -pub fn encode_fixed_size, const LEN: usize>(v: &E) -> ArrayVec { - let mut out = ArrayVec::from([0_u8; LEN]); - - let mut s = out.as_mut_slice(); - - v.encode(&mut s); - - let final_len = LEN - s.len(); - out.truncate(final_len); - - out -} - -/// Determine the length in bytes of the length prefix of an RLP item. -#[inline] -pub const fn length_of_length(payload_length: usize) -> usize { - if payload_length < 56 { - 1 - } else { - 1 + 8 - payload_length.leading_zeros() as usize / 8 - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bytes::BytesMut; - use hex_literal::hex; - - fn encoded(t: T) -> BytesMut { - let mut out = BytesMut::new(); - t.encode(&mut out); - out - } - - fn encoded_list(t: &[T]) -> BytesMut { - let mut out1 = BytesMut::new(); - encode_list(t, &mut out1); - - let v = t.to_vec(); - assert_eq!(out1.len(), v.length()); - - let mut out2 = BytesMut::new(); - v.encode(&mut out2); - assert_eq!(out1, out2); - - out1 - } - - fn encoded_iter(iter: impl Iterator + Clone) -> BytesMut { - let mut out = BytesMut::new(); - encode_iter(iter, &mut out); - out - } - - #[test] - fn rlp_str() { - assert_eq!(encoded("")[..], hex!("80")[..]); - assert_eq!(encoded("{")[..], hex!("7b")[..]); - assert_eq!(encoded("test str")[..], hex!("887465737420737472")[..]); - } - - #[cfg(feature = "smol_str")] - #[test] - fn rlp_smol_str() { - use alloc::string::ToString; - use smol_str::SmolStr; - - assert_eq!(encoded(SmolStr::new(""))[..], hex!("80")[..]); - let mut b = BytesMut::new(); - "test smol str".to_string().encode(&mut b); - assert_eq!(&encoded(SmolStr::new("test smol str"))[..], b.as_ref()); - let mut b = BytesMut::new(); - "abcdefgh".to_string().encode(&mut b); - assert_eq!(&encoded(SmolStr::new("abcdefgh"))[..], b.as_ref()); - } - - #[test] - fn rlp_strings() { - assert_eq!(encoded(hex!(""))[..], hex!("80")[..]); - assert_eq!(encoded(hex!("7B"))[..], hex!("7b")[..]); - assert_eq!(encoded(hex!("80"))[..], hex!("8180")[..]); - assert_eq!(encoded(hex!("ABBA"))[..], hex!("82abba")[..]); - } - - fn c>( - it: impl IntoIterator, - ) -> impl Iterator { - it.into_iter().map(|(k, v)| (k.into(), v)) - } - - fn u8_fixtures() -> impl IntoIterator { - vec![ - (0, &hex!("80")[..]), - (1, &hex!("01")[..]), - (0x7F, &hex!("7F")[..]), - (0x80, &hex!("8180")[..]), - ] - } - - fn u16_fixtures() -> impl IntoIterator { - c(u8_fixtures()).chain(vec![(0x400, &hex!("820400")[..])]) - } - - fn u32_fixtures() -> impl IntoIterator { - c(u16_fixtures()).chain(vec![ - (0xFFCCB5, &hex!("83ffccb5")[..]), - (0xFFCCB5DD, &hex!("84ffccb5dd")[..]), - ]) - } - - fn u64_fixtures() -> impl IntoIterator { - c(u32_fixtures()).chain(vec![ - (0xFFCCB5DDFF, &hex!("85ffccb5ddff")[..]), - (0xFFCCB5DDFFEE, &hex!("86ffccb5ddffee")[..]), - (0xFFCCB5DDFFEE14, &hex!("87ffccb5ddffee14")[..]), - (0xFFCCB5DDFFEE1483, &hex!("88ffccb5ddffee1483")[..]), - ]) - } - - fn u128_fixtures() -> impl IntoIterator { - c(u64_fixtures()).chain(vec![( - 0x10203E405060708090A0B0C0D0E0F2, - &hex!("8f10203e405060708090a0b0c0d0e0f2")[..], - )]) - } - - macro_rules! uint_rlp_test { - ($fixtures:expr) => { - for (input, output) in $fixtures { - assert_eq!(encoded(input), output); - } - }; - } - - #[test] - fn rlp_uints() { - uint_rlp_test!(u8_fixtures()); - uint_rlp_test!(u16_fixtures()); - uint_rlp_test!(u32_fixtures()); - uint_rlp_test!(u64_fixtures()); - uint_rlp_test!(u128_fixtures()); - // #[cfg(feature = "ethnum")] - // uint_rlp_test!(u256_fixtures()); - } - - /* - #[cfg(feature = "ethnum")] - fn u256_fixtures() -> impl IntoIterator { - c(u128_fixtures()).chain(vec![( - ethnum::U256::from_str_radix( - "0100020003000400050006000700080009000A0B4B000C000D000E01", - 16, - ) - .unwrap(), - &hex!("9c0100020003000400050006000700080009000a0b4b000c000d000e01")[..], - )]) - } - - #[cfg(feature = "ethereum-types")] - fn eth_u64_fixtures() -> impl IntoIterator { - c(u64_fixtures()).chain(vec![ - ( - ethereum_types::U64::from_str_radix("FFCCB5DDFF", 16).unwrap(), - &hex!("85ffccb5ddff")[..], - ), - ( - ethereum_types::U64::from_str_radix("FFCCB5DDFFEE", 16).unwrap(), - &hex!("86ffccb5ddffee")[..], - ), - ( - ethereum_types::U64::from_str_radix("FFCCB5DDFFEE14", 16).unwrap(), - &hex!("87ffccb5ddffee14")[..], - ), - ( - ethereum_types::U64::from_str_radix("FFCCB5DDFFEE1483", 16).unwrap(), - &hex!("88ffccb5ddffee1483")[..], - ), - ]) - } - - fn eth_u128_fixtures() -> impl IntoIterator { - c(u128_fixtures()).chain(vec![( - ethereum_types::U128::from_str_radix("10203E405060708090A0B0C0D0E0F2", 16).unwrap(), - &hex!("8f10203e405060708090a0b0c0d0e0f2")[..], - )]) - } - - fn eth_u256_fixtures() -> impl IntoIterator { - c(u128_fixtures()).chain(vec![( - ethereum_types::U256::from_str_radix( - "0100020003000400050006000700080009000A0B4B000C000D000E01", - 16, - ) - .unwrap(), - &hex!("9c0100020003000400050006000700080009000a0b4b000c000d000e01")[..], - )]) - } - - #[cfg(feature = "ethereum-types")] - fn eth_u512_fixtures() -> impl IntoIterator { - c(eth_u256_fixtures()).chain(vec![( - ethereum_types::U512::from_str_radix( - "0100020003000400050006000700080009000A0B4B000C000D000E010100020003000400050006000700080009000A0B4B000C000D000E01", - 16, - ) - .unwrap(), - &hex!("b8380100020003000400050006000700080009000A0B4B000C000D000E010100020003000400050006000700080009000A0B4B000C000D000E01")[..], - )]) - } - - #[cfg(feature = "ethereum-types")] - #[test] - fn rlp_eth_uints() { - uint_rlp_test!(eth_u64_fixtures()); - uint_rlp_test!(eth_u128_fixtures()); - uint_rlp_test!(eth_u256_fixtures()); - uint_rlp_test!(eth_u512_fixtures()); - } - */ - - #[test] - fn rlp_list() { - assert_eq!(encoded_list::(&[]), &hex!("c0")[..]); - assert_eq!(encoded_list::(&[0x00u8]), &hex!("c180")[..]); - assert_eq!( - encoded_list(&[0xFFCCB5_u64, 0xFFC0B5_u64]), - &hex!("c883ffccb583ffc0b5")[..] - ); - } - - #[test] - fn rlp_iter() { - assert_eq!(encoded_iter::([].into_iter()), &hex!("c0")[..]); - assert_eq!( - encoded_iter([0xFFCCB5_u64, 0xFFC0B5_u64].iter()), - &hex!("c883ffccb583ffc0b5")[..] - ); - } - - #[test] - fn to_be_bytes_trimmed() { - macro_rules! test_to_be_bytes_trimmed { - ($($x:expr => $expected:expr),+ $(,)?) => {$( - let be; - assert_eq!(to_be_bytes_trimmed!(be, $x), $expected); - )+}; - } - - test_to_be_bytes_trimmed! { - 0u8 => [], - 0u16 => [], - 0u32 => [], - 0u64 => [], - 0usize => [], - 0u128 => [], - - 1u8 => [1], - 1u16 => [1], - 1u32 => [1], - 1u64 => [1], - 1usize => [1], - 1u128 => [1], - - u8::MAX => [0xff], - u16::MAX => [0xff, 0xff], - u32::MAX => [0xff, 0xff, 0xff, 0xff], - u64::MAX => [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], - u128::MAX => [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], - - 1u8 => [1], - 255u8 => [255], - 256u16 => [1, 0], - 65535u16 => [255, 255], - 65536u32 => [1, 0, 0], - 65536u64 => [1, 0, 0], - } - } -} diff --git a/crates/rlp/src/error.rs b/crates/rlp/src/error.rs deleted file mode 100644 index 3ea1d6f197..0000000000 --- a/crates/rlp/src/error.rs +++ /dev/null @@ -1,56 +0,0 @@ -use core::fmt; - -/// RLP result type. -pub type Result = core::result::Result; - -/// RLP error type. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Error { - /// Numeric Overflow. - Overflow, - /// Leading zero disallowed. - LeadingZero, - /// Overran input while decoding. - InputTooShort, - /// Expected single byte, but got invalid value. - NonCanonicalSingleByte, - /// Expected size, but got invalid value. - NonCanonicalSize, - /// Expected a payload of a specific size, got an unexpected size. - UnexpectedLength, - /// Expected another type, got a string instead. - UnexpectedString, - /// Expected another type, got a list instead. - UnexpectedList, - /// Got an unexpected number of items in a list. - ListLengthMismatch { - /// Expected length. - expected: usize, - /// Actual length. - got: usize, - }, - /// Custom Err. - Custom(&'static str), -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Overflow => f.write_str("overflow"), - Error::LeadingZero => f.write_str("leading zero"), - Error::InputTooShort => f.write_str("input too short"), - Error::NonCanonicalSingleByte => f.write_str("non-canonical single byte"), - Error::NonCanonicalSize => f.write_str("non-canonical size"), - Error::UnexpectedLength => f.write_str("unexpected length"), - Error::UnexpectedString => f.write_str("unexpected string"), - Error::UnexpectedList => f.write_str("unexpected list"), - Error::ListLengthMismatch { got, expected } => { - write!(f, "unexpected list length (got {got}, expected {expected})") - } - Error::Custom(err) => f.write_str(err), - } - } -} diff --git a/crates/rlp/src/header.rs b/crates/rlp/src/header.rs deleted file mode 100644 index 8b8abc8081..0000000000 --- a/crates/rlp/src/header.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::{decode::static_left_pad, Error, Result, EMPTY_LIST_CODE, EMPTY_STRING_CODE}; -use bytes::{Buf, BufMut}; -use core::hint::unreachable_unchecked; - -/// The header of an RLP item. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct Header { - /// True if list, false otherwise. - pub list: bool, - /// Length of the payload in bytes. - pub payload_length: usize, -} - -impl Header { - /// Decodes an RLP header from the given buffer. - /// - /// # Errors - /// - /// Returns an error if the buffer is too short or the header is invalid. - #[inline] - pub fn decode(buf: &mut &[u8]) -> Result { - let payload_length; - let mut list = false; - match get_next_byte(buf)? { - 0..=0x7F => payload_length = 1, - - b @ EMPTY_STRING_CODE..=0xB7 => { - buf.advance(1); - payload_length = (b - EMPTY_STRING_CODE) as usize; - if payload_length == 1 && get_next_byte(buf)? < EMPTY_STRING_CODE { - return Err(Error::NonCanonicalSingleByte) - } - } - - b @ (0xB8..=0xBF | 0xF8..=0xFF) => { - buf.advance(1); - - list = b >= 0xF8; // second range - let code = if list { 0xF7 } else { 0xB7 }; - - // SAFETY: `b - code` is always in the range `1..=8` in the current match arm. - // The compiler/LLVM apparently cannot prove this because of the `|` pattern + - // the above `if`, since it can do it in the other arms with only 1 range. - let len_of_len = unsafe { b.checked_sub(code).unwrap_unchecked() } as usize; - if len_of_len == 0 || len_of_len > 8 { - unsafe { unreachable_unchecked() } - } - - if buf.len() < len_of_len { - return Err(Error::InputTooShort) - } - // SAFETY: length checked above - let len = unsafe { buf.get_unchecked(..len_of_len) }; - buf.advance(len_of_len); - - let len = u64::from_be_bytes(static_left_pad(len)?); - payload_length = - usize::try_from(len).map_err(|_| Error::Custom("Input too big"))?; - if payload_length < 56 { - return Err(Error::NonCanonicalSize) - } - } - - b @ EMPTY_LIST_CODE..=0xF7 => { - buf.advance(1); - list = true; - payload_length = (b - EMPTY_LIST_CODE) as usize; - } - } - - if buf.remaining() < payload_length { - return Err(Error::InputTooShort) - } - - Ok(Self { - list, - payload_length, - }) - } - - /// Decodes the next payload from the given buffer, advancing it. - /// - /// # Errors - /// - /// Returns an error if the buffer is too short or the header is invalid. - #[inline] - pub fn decode_bytes<'a>(buf: &mut &'a [u8], is_list: bool) -> Result<&'a [u8]> { - let Self { - list, - payload_length, - } = Self::decode(buf)?; - - if list != is_list { - return Err(if is_list { - Error::UnexpectedString - } else { - Error::UnexpectedList - }) - } - - // SAFETY: this is already checked in `decode` - if buf.remaining() < payload_length { - unsafe { unreachable_unchecked() } - } - let bytes = unsafe { buf.get_unchecked(..payload_length) }; - buf.advance(payload_length); - Ok(bytes) - } - - /// Decodes a string slice from the given buffer, advancing it. - /// - /// # Errors - /// - /// Returns an error if the buffer is too short or the header is invalid. - #[inline] - pub fn decode_str<'a>(buf: &mut &'a [u8]) -> Result<&'a str> { - let bytes = Self::decode_bytes(buf, false)?; - core::str::from_utf8(bytes).map_err(|_| Error::Custom("invalid string")) - } - - /// Encodes the header into the `out` buffer. - #[inline] - pub fn encode(&self, out: &mut dyn BufMut) { - if self.payload_length < 56 { - let code = if self.list { - EMPTY_LIST_CODE - } else { - EMPTY_STRING_CODE - }; - out.put_u8(code + self.payload_length as u8); - } else { - let len_be; - let len_be = crate::encode::to_be_bytes_trimmed!(len_be, self.payload_length); - let code = if self.list { 0xF7 } else { 0xB7 }; - out.put_u8(code + len_be.len() as u8); - out.put_slice(len_be); - } - } - - /// Returns the length of the encoded header. - #[inline] - pub const fn length(&self) -> usize { - crate::length_of_length(self.payload_length) - } -} - -/// Same as `buf.first().ok_or(Error::InputTooShort)`. -#[inline(always)] -fn get_next_byte(buf: &[u8]) -> Result { - if buf.is_empty() { - return Err(Error::InputTooShort) - } - // SAFETY: length checked above - Ok(*unsafe { buf.get_unchecked(0) }) -} diff --git a/crates/rlp/src/lib.rs b/crates/rlp/src/lib.rs deleted file mode 100644 index 74309cf41e..0000000000 --- a/crates/rlp/src/lib.rs +++ /dev/null @@ -1,65 +0,0 @@ -#![doc = include_str!("../README.md")] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg", - html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico" -)] -#![warn( - missing_docs, - unreachable_pub, - unused_crate_dependencies, - clippy::missing_const_for_fn, - rustdoc::all -)] -#![deny(unused_must_use, rust_2018_idioms)] -#![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -#[macro_use] -#[allow(unused_imports)] -extern crate alloc; - -mod decode; -pub use decode::{Decodable, Rlp}; - -mod error; -pub use error::{Error, Result}; - -mod encode; -#[cfg(feature = "arrayvec")] -pub use encode::encode_fixed_size; -pub use encode::{ - encode_iter, encode_list, length_of_length, list_length, Encodable, MaxEncodedLen, - MaxEncodedLenAssoc, -}; - -mod header; -pub use header::Header; - -#[doc(no_inline)] -pub use bytes::{self, Buf, BufMut, Bytes, BytesMut}; - -#[cfg(feature = "derive")] -#[doc(no_inline)] -pub use alloy_rlp_derive::{ - RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper, RlpMaxEncodedLen, -}; - -/// RLP prefix byte for 0-length string. -pub const EMPTY_STRING_CODE: u8 = 0x80; - -/// RLP prefix byte for a 0-length array. -pub const EMPTY_LIST_CODE: u8 = 0xC0; - -// Not public API. -#[doc(hidden)] -#[deprecated(since = "0.3.0", note = "use `Error` instead")] -pub type DecodeError = Error; - -#[doc(hidden)] -pub mod private { - pub use core::{ - default::Default, - option::Option::{self, None, Some}, - result::Result::{self, Err, Ok}, - }; -} diff --git a/crates/rlp/tests/derive.rs b/crates/rlp/tests/derive.rs deleted file mode 100644 index 61782483d9..0000000000 --- a/crates/rlp/tests/derive.rs +++ /dev/null @@ -1,62 +0,0 @@ -#![cfg(feature = "derive")] - -use alloy_rlp::*; - -#[test] -fn simple_derive() { - #[derive(RlpEncodable, RlpDecodable, RlpMaxEncodedLen, PartialEq, Debug)] - struct MyThing(#[rlp] [u8; 12]); - - let thing = MyThing([0; 12]); - - // roundtrip fidelity - let mut buf = Vec::new(); - thing.encode(&mut buf); - let decoded = MyThing::decode(&mut buf.as_slice()).unwrap(); - assert_eq!(thing, decoded); - - // does not panic on short input - assert_eq!( - Err(Error::InputTooShort), - MyThing::decode(&mut [0x8c; 11].as_ref()) - ) -} - -#[test] -fn wrapper() { - #[derive(RlpEncodableWrapper, RlpDecodableWrapper, RlpMaxEncodedLen, PartialEq, Debug)] - struct Wrapper([u8; 8]); - - #[derive(RlpEncodableWrapper, RlpDecodableWrapper, PartialEq, Debug)] - struct ConstWrapper([u8; N]); -} - -#[test] -fn generics() { - trait LT<'a> {} - - #[derive(RlpEncodable, RlpDecodable, RlpMaxEncodedLen)] - struct Generic LT<'a>, V: Default, const N: usize>(T, usize, U, V, [u8; N]) - where - U: std::fmt::Display; - - #[derive(RlpEncodableWrapper, RlpDecodableWrapper, RlpMaxEncodedLen)] - struct GenericWrapper(T) - where - T: Sized; -} - -#[test] -fn opt() { - #[derive(RlpEncodable, RlpDecodable)] - #[rlp(trailing)] - struct Options(Option>); - - #[derive(RlpEncodable, RlpDecodable)] - #[rlp(trailing)] - struct Options2 { - a: Option, - #[rlp(default)] - b: Option, - } -}