From 96382a5f360bcf7c0e128d3b52c4d77a314c09d4 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 18 Jun 2024 00:36:34 +0200 Subject: [PATCH] feat: add `abi_packed_encoded_size` --- crates/dyn-abi/src/dynamic/event.rs | 2 +- crates/dyn-abi/src/dynamic/value.rs | 69 ++++++++------- crates/dyn-abi/src/eip712/resolver.rs | 2 +- crates/primitives/src/log.rs | 4 +- crates/sol-macro-expander/src/expand/enum.rs | 1 + .../sol-macro-expander/src/expand/struct.rs | 17 ++++ crates/sol-macro-expander/src/expand/udt.rs | 6 ++ crates/sol-types/src/lib.rs | 9 +- crates/sol-types/src/types/data_type.rs | 88 +++++++++++++++++-- crates/sol-types/src/types/event/topic.rs | 4 +- crates/sol-types/src/types/ty.rs | 16 +++- 11 files changed, 170 insertions(+), 48 deletions(-) diff --git a/crates/dyn-abi/src/dynamic/event.rs b/crates/dyn-abi/src/dynamic/event.rs index 2d1e5b74b..083165d4d 100644 --- a/crates/dyn-abi/src/dynamic/event.rs +++ b/crates/dyn-abi/src/dynamic/event.rs @@ -16,7 +16,7 @@ pub struct DynSolEvent { impl DynSolEvent { /// Creates a new event, without length-checking the indexed, or ensuring /// the body is a tuple. This allows creation of invalid events. - pub fn new_unchecked( + pub const fn new_unchecked( topic_0: Option, indexed: Vec, body: DynSolType, diff --git a/crates/dyn-abi/src/dynamic/value.rs b/crates/dyn-abi/src/dynamic/value.rs index fe0100fe0..c4eed1a37 100644 --- a/crates/dyn-abi/src/dynamic/value.rs +++ b/crates/dyn-abi/src/dynamic/value.rs @@ -667,7 +667,21 @@ impl DynSolValue { } } - /// Encodes the packed value and appends it to the end of a byte array. + /// Non-standard Packed Mode ABI encoding. + /// + /// Note that invalid value sizes will saturate to the maximum size, e.g. `Uint(x, 300)` will + /// behave the same as `Uint(x, 256)`. + /// + /// See [`SolType::abi_encode_packed`](alloy_sol_types::SolType::abi_encode_packed) for more + /// details. + #[inline] + pub fn abi_encode_packed(&self) -> Vec { + let mut buf = Vec::with_capacity(self.abi_packed_encoded_size()); + self.abi_encode_packed_to(&mut buf); + buf + } + + /// Non-standard Packed Mode ABI encoding. /// /// See [`abi_encode_packed`](Self::abi_encode_packed) for more details. pub fn abi_encode_packed_to(&self, buf: &mut Vec) { @@ -690,49 +704,38 @@ impl DynSolValue { } Self::FixedArray(inner) | Self::Array(inner) => { for val in inner { - let mut buf_inner = Vec::new(); - val.abi_encode_packed_to(&mut buf_inner); - - // Array elements are always padded - if buf_inner.len() < 32usize { - // Calculate the number of padding elements needed - let padding_needed = 32usize.saturating_sub(buf_inner.len()); - - // Extend the vector with the padding elements - buf_inner.resize(32usize, 0); - - // Rotate the vector left by the number of padding elements added - buf_inner.rotate_right(padding_needed); + // Array elements are left-padded to 32 bytes. + if let Some(padding_needed) = 32usize.checked_sub(val.abi_packed_encoded_size()) + { + buf.extend(core::iter::repeat(0).take(padding_needed)); } - buf.extend_from_slice(&buf_inner); - } - } - Self::Tuple(inner) => { - for val in inner { val.abi_encode_packed_to(buf); } } - #[cfg(feature = "eip712")] - Self::CustomStruct { tuple, .. } => { - for val in tuple { + as_tuple!(Self inner) => { + for val in inner { val.abi_encode_packed_to(buf); } } } } - /// Non-standard Packed Mode ABI encoding. + /// Returns the length of this value when ABI-encoded in Non-standard Packed Mode. /// - /// Note that invalid value sizes will saturate to the maximum size, e.g. `Uint(x, 300)` will - /// behave the same as `Uint(x, 256)`. - /// - /// See [`SolType::abi_encode_packed`](alloy_sol_types::SolType::abi_encode_packed) for more - /// details. - #[inline] - pub fn abi_encode_packed(&self) -> Vec { - let mut buf = Vec::new(); - self.abi_encode_packed_to(&mut buf); - buf + /// See [`abi_encode_packed`](Self::abi_encode_packed) for more details. + pub fn abi_packed_encoded_size(&self) -> usize { + match self { + Self::Address(_) | Self::Function(_) => 20, + Self::Bool(_) => 1, + Self::String(s) => s.len(), + Self::Bytes(b) => b.len(), + Self::FixedBytes(_, size) => (*size).min(32), + Self::Int(_, size) | Self::Uint(_, size) => (size / 8).min(32), + Self::FixedArray(inner) | Self::Array(inner) => { + inner.iter().map(|v| v.abi_packed_encoded_size().max(32)).sum() + } + as_tuple!(Self inner) => inner.iter().map(Self::abi_packed_encoded_size).sum(), + } } /// Tokenize this value into a [`DynToken`]. diff --git a/crates/dyn-abi/src/eip712/resolver.rs b/crates/dyn-abi/src/eip712/resolver.rs index d21795a93..929248fe6 100644 --- a/crates/dyn-abi/src/eip712/resolver.rs +++ b/crates/dyn-abi/src/eip712/resolver.rs @@ -122,7 +122,7 @@ impl TypeDef { /// Instantiate a new type definition, without checking that the type name /// is a valid root type. This may result in bad behavior in a resolver. #[inline] - pub fn new_unchecked(type_name: String, props: Vec) -> Self { + pub const fn new_unchecked(type_name: String, props: Vec) -> Self { Self { type_name, props } } diff --git a/crates/primitives/src/log.rs b/crates/primitives/src/log.rs index 5d91d3a96..3451b0e22 100644 --- a/crates/primitives/src/log.rs +++ b/crates/primitives/src/log.rs @@ -17,7 +17,7 @@ impl LogData { /// invalid logs. May be safely used when the length of the topic list is /// known to be 4 or less. #[inline] - pub fn new_unchecked(topics: Vec, data: Bytes) -> Self { + pub const fn new_unchecked(topics: Vec, data: Bytes) -> Self { Self { topics, data } } @@ -109,7 +109,7 @@ impl Log { /// Creates a new log. #[inline] - pub fn new_unchecked(address: Address, topics: Vec, data: Bytes) -> Self { + pub const fn new_unchecked(address: Address, topics: Vec, data: Bytes) -> Self { Self { address, data: LogData::new_unchecked(topics, data) } } diff --git a/crates/sol-macro-expander/src/expand/enum.rs b/crates/sol-macro-expander/src/expand/enum.rs index 7ea6f7e64..7fb9b76ab 100644 --- a/crates/sol-macro-expander/src/expand/enum.rs +++ b/crates/sol-macro-expander/src/expand/enum.rs @@ -143,6 +143,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, enumm: &ItemEnum) -> Result const SOL_NAME: &'static str = #uint8_st::SOL_NAME; const ENCODED_SIZE: ::core::option::Option = #uint8_st::ENCODED_SIZE; + const PACKED_ENCODED_SIZE: ::core::option::Option = #uint8_st::PACKED_ENCODED_SIZE; #[inline] fn valid_token(token: &Self::Token<'_>) -> bool { diff --git a/crates/sol-macro-expander/src/expand/struct.rs b/crates/sol-macro-expander/src/expand/struct.rs index d10884de8..00eb4d712 100644 --- a/crates/sol-macro-expander/src/expand/struct.rs +++ b/crates/sol-macro-expander/src/expand/struct.rs @@ -90,6 +90,10 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result { #[inline] fn stv_abi_encoded_size(&self) -> usize { + if let Some(size) = ::ENCODED_SIZE { + return size; + } + // TODO: Avoid cloning let tuple = as ::core::convert::From>::from(self.clone()); as alloy_sol_types::SolType>::abi_encoded_size(&tuple) @@ -106,6 +110,17 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result { let tuple = as ::core::convert::From>::from(self.clone()); as alloy_sol_types::SolType>::abi_encode_packed_to(&tuple, out) } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + if let Some(size) = ::PACKED_ENCODED_SIZE { + return size; + } + + // TODO: Avoid cloning + let tuple = as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_packed_encoded_size(&tuple) + } } #[automatically_derived] @@ -116,6 +131,8 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result { const SOL_NAME: &'static str = ::NAME; const ENCODED_SIZE: Option = as alloy_sol_types::SolType>::ENCODED_SIZE; + const PACKED_ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE; #[inline] fn valid_token(token: &Self::Token<'_>) -> bool { diff --git a/crates/sol-macro-expander/src/expand/udt.rs b/crates/sol-macro-expander/src/expand/udt.rs index 342362e70..06e9f7ff0 100644 --- a/crates/sol-macro-expander/src/expand/udt.rs +++ b/crates/sol-macro-expander/src/expand/udt.rs @@ -56,6 +56,11 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, udt: &ItemUdt) -> Result { fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec) { <#underlying_sol as alloy_sol_types::SolType>::abi_encode_packed_to(self, out) } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + <#underlying_sol as alloy_sol_types::SolType>::abi_encoded_size(self) + } } #[automatically_derived] @@ -97,6 +102,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, udt: &ItemUdt) -> Result { const SOL_NAME: &'static str = Self::NAME; const ENCODED_SIZE: Option = <#underlying_sol as alloy_sol_types::SolType>::ENCODED_SIZE; + const PACKED_ENCODED_SIZE: Option = <#underlying_sol as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE; #[inline] fn valid_token(token: &Self::Token<'_>) -> bool { diff --git a/crates/sol-types/src/lib.rs b/crates/sol-types/src/lib.rs index c9d6bfd9b..8e2a3dd2c 100644 --- a/crates/sol-types/src/lib.rs +++ b/crates/sol-types/src/lib.rs @@ -248,12 +248,19 @@ pub mod private { pub trait SolTypeValue { // Note: methods are prefixed with `stv_` to avoid name collisions with // the `SolValue` trait. - fn stv_to_tokens(&self) -> T::Token<'_>; + #[inline(always)] fn stv_abi_encoded_size(&self) -> usize { T::ENCODED_SIZE.unwrap() } + fn stv_to_tokens(&self) -> T::Token<'_>; + + #[inline(always)] + fn stv_abi_packed_encoded_size(&self) -> usize { + T::PACKED_ENCODED_SIZE.unwrap() + } fn stv_abi_encode_packed_to(&self, out: &mut Vec); + fn stv_eip712_data_word(&self) -> super::Word; } diff --git a/crates/sol-types/src/types/data_type.rs b/crates/sol-types/src/types/data_type.rs index 1cdcbaa56..4696954e1 100644 --- a/crates/sol-types/src/types/data_type.rs +++ b/crates/sol-types/src/types/data_type.rs @@ -45,6 +45,7 @@ impl SolType for Bool { const SOL_NAME: &'static str = "bool"; const ENCODED_SIZE: Option = Some(32); + const PACKED_ENCODED_SIZE: Option = Some(1); #[inline] fn valid_token(token: &Self::Token<'_>) -> bool { @@ -90,6 +91,7 @@ where const SOL_NAME: &'static str = IntBitCount::::INT_NAME; const ENCODED_SIZE: Option = Some(32); + const PACKED_ENCODED_SIZE: Option = Some(BITS / 8); #[inline] fn valid_token(token: &Self::Token<'_>) -> bool { @@ -143,6 +145,7 @@ where const SOL_NAME: &'static str = IntBitCount::::UINT_NAME; const ENCODED_SIZE: Option = Some(32); + const PACKED_ENCODED_SIZE: Option = Some(BITS / 8); #[inline] fn valid_token(token: &Self::Token<'_>) -> bool { @@ -190,6 +193,7 @@ where const SOL_NAME: &'static str = >::NAME; const ENCODED_SIZE: Option = Some(32); + const PACKED_ENCODED_SIZE: Option = Some(N); #[inline] fn valid_token(token: &Self::Token<'_>) -> bool { @@ -228,6 +232,7 @@ impl SolType for Address { const SOL_NAME: &'static str = "address"; const ENCODED_SIZE: Option = Some(32); + const PACKED_ENCODED_SIZE: Option = Some(20); #[inline] fn detokenize(token: Self::Token<'_>) -> Self::RustType { @@ -266,6 +271,7 @@ impl SolType for Function { const SOL_NAME: &'static str = "function"; const ENCODED_SIZE: Option = Some(32); + const PACKED_ENCODED_SIZE: Option = Some(24); #[inline] fn detokenize(token: Self::Token<'_>) -> Self::RustType { @@ -306,6 +312,11 @@ impl> SolTypeValue for T { fn stv_abi_encode_packed_to(&self, out: &mut Vec) { out.extend_from_slice(self.as_ref()); } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + self.as_ref().len() + } } impl SolType for Bytes { @@ -314,6 +325,7 @@ impl SolType for Bytes { const SOL_NAME: &'static str = "bytes"; const ENCODED_SIZE: Option = None; + const PACKED_ENCODED_SIZE: Option = None; #[inline] fn valid_token(_token: &Self::Token<'_>) -> bool { @@ -354,6 +366,11 @@ impl> SolTypeValue for T { fn stv_abi_encode_packed_to(&self, out: &mut Vec) { out.extend_from_slice(self.as_ref().as_ref()); } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + self.as_ref().len() + } } impl SolType for String { @@ -362,6 +379,7 @@ impl SolType for String { const SOL_NAME: &'static str = "string"; const ENCODED_SIZE: Option = None; + const PACKED_ENCODED_SIZE: Option = None; #[inline] fn valid_token(token: &Self::Token<'_>) -> bool { @@ -412,9 +430,18 @@ where #[inline] fn stv_abi_encode_packed_to(&self, out: &mut Vec) { for item in self { + // Array elements are left-padded to 32 bytes. + if let Some(padding_needed) = 32usize.checked_sub(item.stv_abi_packed_encoded_size()) { + out.extend(core::iter::repeat(0).take(padding_needed)); + } T::stv_abi_encode_packed_to(item, out); } } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + self.iter().map(|item| item.stv_abi_packed_encoded_size().max(32)).sum() + } } impl SolTypeValue> for &[T] @@ -441,6 +468,11 @@ where fn stv_abi_encode_packed_to(&self, out: &mut Vec) { (**self).stv_abi_encode_packed_to(out) } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + (**self).stv_abi_packed_encoded_size() + } } impl SolTypeValue> for &mut [T] @@ -467,6 +499,11 @@ where fn stv_abi_encode_packed_to(&self, out: &mut Vec) { (**self).stv_abi_encode_packed_to(out) } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + (**self).stv_abi_packed_encoded_size() + } } impl SolTypeValue> for Vec @@ -493,6 +530,11 @@ where fn stv_abi_encode_packed_to(&self, out: &mut Vec) { (**self).stv_abi_encode_packed_to(out) } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + (**self).stv_abi_packed_encoded_size() + } } impl SolType for Array { @@ -502,6 +544,7 @@ impl SolType for Array { const SOL_NAME: &'static str = NameBuffer::new().write_str(T::SOL_NAME).write_str("[]").as_str(); const ENCODED_SIZE: Option = None; + const PACKED_ENCODED_SIZE: Option = None; #[inline] fn valid_token(token: &Self::Token<'_>) -> bool { @@ -556,9 +599,18 @@ where #[inline] fn stv_abi_encode_packed_to(&self, out: &mut Vec) { for item in self { + // Array elements are left-padded to 32 bytes. + if let Some(padding_needed) = 32usize.checked_sub(item.stv_abi_packed_encoded_size()) { + out.extend(core::iter::repeat(0).take(padding_needed)); + } T::stv_abi_encode_packed_to(item, out); } } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + self.iter().map(|item| item.stv_abi_packed_encoded_size().max(32)).sum() + } } impl SolTypeValue> for &[T; N] @@ -585,6 +637,11 @@ where fn stv_abi_encode_packed_to(&self, out: &mut Vec) { SolTypeValue::>::stv_abi_encode_packed_to(&**self, out) } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + SolTypeValue::>::stv_abi_packed_encoded_size(&**self) + } } impl SolTypeValue> for &mut [T; N] @@ -611,6 +668,11 @@ where fn stv_abi_encode_packed_to(&self, out: &mut Vec) { SolTypeValue::>::stv_abi_encode_packed_to(&**self, out) } + + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + SolTypeValue::>::stv_abi_packed_encoded_size(&**self) + } } impl SolType for FixedArray { @@ -623,12 +685,11 @@ impl SolType for FixedArray { .write_usize(N) .write_byte(b']') .as_str(); - const ENCODED_SIZE: Option = { - match T::ENCODED_SIZE { - Some(size) => Some(size * N), - None => None, - } + const ENCODED_SIZE: Option = match T::ENCODED_SIZE { + Some(size) => Some(size * N), + None => None, }; + const PACKED_ENCODED_SIZE: Option = None; #[inline] fn valid_token(token: &Self::Token<'_>) -> bool { @@ -667,7 +728,6 @@ macro_rules! tuple_encodable_impls { fn stv_abi_encode_packed_to(&self, out: &mut Vec) { let ($($ty,)+) = self; - // TODO: Reserve $( $ty.stv_abi_encode_packed_to(out); )+ @@ -682,6 +742,11 @@ macro_rules! tuple_encodable_impls { let encoding: &[u8] = unsafe { core::slice::from_raw_parts(encoding.as_ptr().cast(), $count * 32) }; keccak256(encoding).into() } + + fn stv_abi_packed_encoded_size(&self) -> usize { + let ($($ty,)+) = self; + 0 $(+ $ty.stv_abi_packed_encoded_size())+ + } } }; } @@ -712,6 +777,16 @@ macro_rules! tuple_impls { )+ Some(acc) }; + const PACKED_ENCODED_SIZE: Option = 'l: { + let mut acc = 0; + $( + match <$ty as SolType>::PACKED_ENCODED_SIZE { + Some(size) => acc += size, + None => break 'l None, + } + )+ + Some(acc) + }; fn valid_token(token: &Self::Token<'_>) -> bool { let ($($ty,)+) = token; @@ -749,6 +824,7 @@ impl SolType for () { const SOL_NAME: &'static str = "()"; const ENCODED_SIZE: Option = Some(0); + const PACKED_ENCODED_SIZE: Option = Some(0); #[inline] fn valid_token((): &()) -> bool { diff --git a/crates/sol-types/src/types/event/topic.rs b/crates/sol-types/src/types/event/topic.rs index 3e1c54728..c6e32ffc9 100644 --- a/crates/sol-types/src/types/event/topic.rs +++ b/crates/sol-types/src/types/event/topic.rs @@ -208,8 +208,6 @@ all_the_tuples!(tuple_impls); fn encode_topic_bytes(sl: &[u8], out: &mut Vec) { let padding = 32 - sl.len() % 32; out.reserve(sl.len() + padding); - - static PAD: [u8; 32] = [0; 32]; out.extend_from_slice(sl); - out.extend_from_slice(&PAD[..padding]); + out.extend(core::iter::repeat(0).take(padding)); } diff --git a/crates/sol-types/src/types/ty.rs b/crates/sol-types/src/types/ty.rs index c58b272a9..606ca3821 100644 --- a/crates/sol-types/src/types/ty.rs +++ b/crates/sol-types/src/types/ty.rs @@ -112,6 +112,12 @@ pub trait SolType: Sized { /// encoded size is dynamic. const ENCODED_SIZE: Option; + /// The statically-known Non-standard Packed Mode ABI-encoded size of the type. + /// + /// If this is not known at compile time, this should be `None`, which indicates that the + /// encoded size is dynamic. + const PACKED_ENCODED_SIZE: Option; + /// Whether the ABI-encoded size is dynamic. /// /// There should be no need to override the default implementation. @@ -171,6 +177,14 @@ pub trait SolType: Sized { rust.stv_eip712_data_word() } + /// Returns the length of this value when ABI-encoded in Non-standard Packed Mode. + /// + /// See [`abi_encode_packed`][SolType::abi_encode_packed] for more details. + #[inline] + fn abi_packed_encoded_size>(rust: &E) -> usize { + rust.stv_abi_packed_encoded_size() + } + /// Non-standard Packed Mode ABI encoding. /// /// See [`abi_encode_packed`][SolType::abi_encode_packed] for more details. @@ -189,7 +203,7 @@ pub trait SolType: Sized { /// More information can be found in the [Solidity docs](https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode). #[inline] fn abi_encode_packed>(rust: &E) -> Vec { - let mut out = Vec::new(); + let mut out = Vec::with_capacity(Self::abi_packed_encoded_size(rust)); Self::abi_encode_packed_to(rust, &mut out); out }