diff --git a/crates/dyn-abi/src/arbitrary.rs b/crates/dyn-abi/src/arbitrary.rs index 7baa07032..a27219f8c 100644 --- a/crates/dyn-abi/src/arbitrary.rs +++ b/crates/dyn-abi/src/arbitrary.rs @@ -10,7 +10,7 @@ #![allow(clippy::arc_with_non_send_sync)] use crate::{DynSolType, DynSolValue}; -use alloy_primitives::{Address, B256, I256, U256}; +use alloy_primitives::{Address, Function, B256, I256, U256}; use arbitrary::{size_hint, Unstructured}; use core::ops::RangeInclusive; use proptest::{ @@ -133,6 +133,7 @@ enum Choice { Int, Uint, Address, + Function, FixedBytes, Bytes, String, @@ -151,6 +152,7 @@ impl<'a> arbitrary::Arbitrary<'a> for DynSolType { Choice::Int => u.arbitrary().map(int_size).map(Self::Int), Choice::Uint => u.arbitrary().map(int_size).map(Self::Uint), Choice::Address => Ok(Self::Address), + Choice::Function => Ok(Self::Function), Choice::FixedBytes => Ok(Self::FixedBytes(u.int_in_range(1..=32)?)), Choice::Bytes => Ok(Self::Bytes), Choice::String => Ok(Self::String), @@ -359,6 +361,7 @@ impl DynSolValue { match ty { DynSolType::Bool => u.arbitrary().map(Self::Bool), DynSolType::Address => u.arbitrary().map(Self::Address), + DynSolType::Function => u.arbitrary().map(Self::Function), &DynSolType::Int(sz) => u.arbitrary().map(|x| Self::Int(x, sz)), &DynSolType::Uint(sz) => u.arbitrary().map(|x| Self::Uint(x, sz)), &DynSolType::FixedBytes(sz) => u.arbitrary().map(|x| Self::FixedBytes(x, sz)), @@ -410,6 +413,7 @@ impl DynSolValue { match ty { DynSolType::Bool => any::().prop_map(Self::Bool).sboxed(), DynSolType::Address => any::
().prop_map(Self::Address).sboxed(), + DynSolType::Function => any::().prop_map(Self::Function).sboxed(), &DynSolType::Int(sz) => any::().prop_map(move |x| Self::Int(x, sz)).sboxed(), &DynSolType::Uint(sz) => any::().prop_map(move |x| Self::Uint(x, sz)).sboxed(), &DynSolType::FixedBytes(sz) => any::() diff --git a/crates/dyn-abi/src/eip712/coerce.rs b/crates/dyn-abi/src/eip712/coerce.rs index 014d80f00..bff239c2a 100644 --- a/crates/dyn-abi/src/eip712/coerce.rs +++ b/crates/dyn-abi/src/eip712/coerce.rs @@ -4,13 +4,14 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use alloy_primitives::{Address, I256, U256}; +use alloy_primitives::{Address, Function, I256, U256}; impl DynSolType { /// Coerce a [`serde_json::Value`] to a [`DynSolValue`] via this type. pub fn coerce(&self, value: &serde_json::Value) -> DynAbiResult { match self { DynSolType::Address => address(value), + DynSolType::Function => function(value), DynSolType::Bool => bool(value), DynSolType::Int(n) => int(*n, value), DynSolType::Uint(n) => uint(*n, value), @@ -41,6 +42,18 @@ fn address(value: &serde_json::Value) -> DynAbiResult { Ok(DynSolValue::Address(address)) } +fn function(value: &serde_json::Value) -> DynAbiResult { + let function = value + .as_str() + .map(|s| { + s.parse::() + .map_err(|_| DynAbiError::type_mismatch(DynSolType::Function, value)) + }) + .ok_or_else(|| DynAbiError::type_mismatch(DynSolType::Function, value))??; + + Ok(DynSolValue::Function(function)) +} + fn bool(value: &serde_json::Value) -> DynAbiResult { if let Some(bool) = value.as_bool() { return Ok(DynSolValue::Bool(bool)) diff --git a/crates/dyn-abi/src/resolve.rs b/crates/dyn-abi/src/resolve.rs index f9085c7f6..4223c319b 100644 --- a/crates/dyn-abi/src/resolve.rs +++ b/crates/dyn-abi/src/resolve.rs @@ -51,6 +51,7 @@ impl ResolveSolType for RootType<'_> { fn resolve(&self) -> DynAbiResult { match self.span() { "address" => Ok(DynSolType::Address), + "function" => Ok(DynSolType::Function), "bool" => Ok(DynSolType::Bool), "string" => Ok(DynSolType::String), "bytes" => Ok(DynSolType::Bytes), diff --git a/crates/dyn-abi/src/token.rs b/crates/dyn-abi/src/token.rs index 1cac3dbb8..1264eb674 100644 --- a/crates/dyn-abi/src/token.rs +++ b/crates/dyn-abi/src/token.rs @@ -7,7 +7,7 @@ use alloy_sol_types::token::{PackedSeqToken, TokenType, WordToken}; // NOTE: do not derive `Hash` for this type. The derived version is not // compatible with the current `PartialEq` implementation. If manually // implementing `Hash`, ignore the `template` prop in the `DynSeq` variant -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum DynToken<'a> { /// A single word. Word(Word), @@ -135,10 +135,10 @@ impl<'a> DynToken<'a> { /// Decodes from a decoder, populating the structure with the decoded data. #[inline] pub(crate) fn decode_populate(&mut self, dec: &mut Decoder<'a>) -> Result<()> { - let dynamic = self.is_dynamic(); match self { Self::Word(w) => *w = WordToken::decode_from(dec)?.0, Self::FixedSeq(..) => { + let dynamic = self.is_dynamic(); let mut child = if dynamic { dec.take_indirection()? } else { @@ -154,23 +154,31 @@ impl<'a> DynToken<'a> { Self::DynSeq { contents, template } => { let mut child = dec.take_indirection()?; let size = child.take_u32()? as usize; + if size == 0 { + // should already be empty from `empty_dyn_token` + debug_assert!(contents.is_empty()); + return Ok(()) + } + // This appears to be an unclarity in the solidity spec. The // spec specifies that offsets are relative to the beginning of // `enc(X)`. But known-good test vectors have it relative to the // word AFTER the array size let mut child = child.raw_child(); - let mut new_tokens: Vec<_> = Vec::with_capacity(size); // This expect is safe because this is only invoked after // `empty_dyn_token()` which always sets template - let t = template - .take() - .expect("No template. This is a bug, please report it."); - new_tokens.resize(size, *t); + let t = template.take().expect("no template for dynamic sequence"); + let mut new_tokens = if size == 1 { + // re-use the box allocation + unsafe { Vec::from_raw_parts(Box::into_raw(t), 1, 1) } + } else { + vec![*t; size] + }; - new_tokens - .iter_mut() - .try_for_each(|t| t.decode_populate(&mut child))?; + for t in &mut new_tokens { + t.decode_populate(&mut child)?; + } *contents = new_tokens.into(); } @@ -184,12 +192,11 @@ impl<'a> DynToken<'a> { #[inline] pub(crate) fn decode_sequence_populate(&mut self, dec: &mut Decoder<'a>) -> Result<()> { match self { - Self::FixedSeq(buf, size) => { - for item in buf.to_mut().iter_mut().take(*size) { - item.decode_populate(dec)?; - } - Ok(()) - } + Self::FixedSeq(buf, size) => buf + .to_mut() + .iter_mut() + .take(*size) + .try_for_each(|item| item.decode_populate(dec)), Self::DynSeq { .. } => self.decode_populate(dec), _ => Err(Error::custom( "Called decode_sequence_populate on non-sequence token", diff --git a/crates/dyn-abi/src/ty.rs b/crates/dyn-abi/src/ty.rs index 31a395fec..1bb3960fd 100644 --- a/crates/dyn-abi/src/ty.rs +++ b/crates/dyn-abi/src/ty.rs @@ -85,6 +85,8 @@ struct StructProp { pub enum DynSolType { /// Address. Address, + /// Function. + Function, /// Boolean. Bool, /// Signed Integer. @@ -222,6 +224,7 @@ impl DynSolType { pub fn matches(&self, value: &DynSolValue) -> bool { match self { Self::Address => matches!(value, DynSolValue::Address(_)), + Self::Function => matches!(value, DynSolValue::Function(_)), Self::Bytes => matches!(value, DynSolValue::Bytes(_)), Self::Int(size) => matches!(value, DynSolValue::Int(_, s) if s == size), Self::Uint(size) => matches!(value, DynSolValue::Uint(_, s) if s == size), @@ -272,6 +275,9 @@ impl DynSolType { (Self::Address, DynToken::Word(word)) => Ok(DynSolValue::Address( sol_data::Address::detokenize(word.into()), )), + (Self::Function, DynToken::Word(word)) => Ok(DynSolValue::Function( + sol_data::Function::detokenize(word.into()), + )), (Self::Bool, DynToken::Word(word)) => { Ok(DynSolValue::Bool(sol_data::Bool::detokenize(word.into()))) } @@ -362,6 +368,7 @@ impl DynSolType { fn sol_type_name_simple(&self) -> Option<&str> { match self { Self::Address => Some("address"), + Self::Function => Some("function"), Self::Bool => Some("bool"), Self::Bytes => Some("bytes"), Self::String => Some("string"), @@ -375,11 +382,16 @@ impl DynSolType { fn sol_type_name_raw(&self, out: &mut String) { match self { #[cfg(feature = "eip712")] - Self::Address | Self::Bool | Self::Bytes | Self::String | Self::CustomStruct { .. } => { + Self::Address + | Self::Function + | Self::Bool + | Self::Bytes + | Self::String + | Self::CustomStruct { .. } => { out.push_str(unsafe { self.sol_type_name_simple().unwrap_unchecked() }); } #[cfg(not(feature = "eip712"))] - Self::Address | Self::Bool | Self::Bytes | Self::String => { + Self::Address | Self::Function | Self::Bool | Self::Bytes | Self::String => { out.push_str(unsafe { self.sol_type_name_simple().unwrap_unchecked() }); } @@ -450,9 +462,12 @@ impl DynSolType { /// Instantiate an empty dyn token, to be decoded into. pub(crate) fn empty_dyn_token(&self) -> DynToken<'_> { match self { - Self::Address | Self::Bool | Self::FixedBytes(_) | Self::Int(_) | Self::Uint(_) => { - DynToken::Word(Word::ZERO) - } + Self::Address + | Self::Function + | Self::Bool + | Self::FixedBytes(_) + | Self::Int(_) + | Self::Uint(_) => DynToken::Word(Word::ZERO), Self::Bytes | Self::String => DynToken::PackedSeq(&[]), diff --git a/crates/dyn-abi/src/value.rs b/crates/dyn-abi/src/value.rs index 27eee8511..3c68addd9 100644 --- a/crates/dyn-abi/src/value.rs +++ b/crates/dyn-abi/src/value.rs @@ -1,6 +1,6 @@ use crate::{DynSolType, DynToken, Word}; use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec}; -use alloy_primitives::{Address, I256, U256}; +use alloy_primitives::{Address, Function, I256, U256}; use alloy_sol_types::{utils::words_for_len, Encoder}; #[cfg(feature = "eip712")] @@ -39,6 +39,8 @@ macro_rules! as_fixed_seq { pub enum DynSolValue { /// An address. Address(Address), + /// A function pointer. + Function(Function), /// A boolean. Bool(bool), /// A signed integer. @@ -172,6 +174,7 @@ impl DynSolValue { pub fn as_type(&self) -> Option { let ty = match self { Self::Address(_) => DynSolType::Address, + Self::Function(_) => DynSolType::Function, Self::Bool(_) => DynSolType::Bool, Self::Bytes(_) => DynSolType::Bytes, Self::FixedBytes(_, size) => DynSolType::FixedBytes(*size), @@ -211,6 +214,7 @@ impl DynSolValue { fn sol_type_name_simple(&self) -> Option<&str> { match self { Self::Address(_) => Some("address"), + Self::Function(_) => Some("function"), Self::Bool(_) => Some("bool"), Self::Bytes(_) => Some("bytes"), Self::String(_) => Some("string"), @@ -224,11 +228,16 @@ impl DynSolValue { fn sol_type_name_raw(&self, out: &mut String) -> bool { match self { #[cfg(not(feature = "eip712"))] - Self::Address(_) | Self::Bool(_) | Self::Bytes(_) | Self::String(_) => { + Self::Address(_) + | Self::Function(_) + | Self::Bool(_) + | Self::Bytes(_) + | Self::String(_) => { out.push_str(unsafe { self.sol_type_name_simple().unwrap_unchecked() }); } #[cfg(feature = "eip712")] Self::Address(_) + | Self::Function(_) | Self::Bool(_) | Self::Bytes(_) | Self::String(_) @@ -326,6 +335,7 @@ impl DynSolValue { pub fn as_word(&self) -> Option { match *self { Self::Address(a) => Some(a.into_word()), + Self::Function(f) => Some(f.into_word()), Self::Bool(b) => Some(Word::with_last_byte(b as u8)), Self::FixedBytes(w, _) => Some(w), Self::Int(i, _) => Some(i.into()), @@ -490,6 +500,7 @@ impl DynSolValue { pub fn is_dynamic(&self) -> bool { match self { Self::Address(_) + | Self::Function(_) | Self::Bool(_) | Self::Int(..) | Self::Uint(..) @@ -527,6 +538,7 @@ impl DynSolValue { match self { // `self.is_word()` Self::Address(_) + | Self::Function(_) | Self::Bool(_) | Self::FixedBytes(..) | Self::Int(..) @@ -570,6 +582,7 @@ impl DynSolValue { pub fn head_append(&self, enc: &mut Encoder) { match self { Self::Address(_) + | Self::Function(_) | Self::Bool(_) | Self::FixedBytes(..) | Self::Int(..) @@ -592,6 +605,7 @@ impl DynSolValue { pub fn tail_append(&self, enc: &mut Encoder) { match self { Self::Address(_) + | Self::Function(_) | Self::Bool(_) | Self::FixedBytes(..) | Self::Int(..) @@ -617,6 +631,7 @@ impl DynSolValue { pub fn encode_packed_to(&self, buf: &mut Vec) { match self { Self::Address(addr) => buf.extend_from_slice(addr.as_slice()), + Self::Function(func) => buf.extend_from_slice(func.as_slice()), Self::Bool(b) => buf.push(*b as u8), Self::String(s) => buf.extend_from_slice(s.as_bytes()), Self::Bytes(bytes) => buf.extend_from_slice(bytes), @@ -653,6 +668,7 @@ impl DynSolValue { pub fn tokenize(&self) -> DynToken<'_> { match self { Self::Address(a) => a.into_word().into(), + Self::Function(f) => f.into_word().into(), Self::Bool(b) => Word::with_last_byte(*b as u8).into(), Self::Bytes(buf) => DynToken::PackedSeq(buf), Self::FixedBytes(buf, _) => (*buf).into(), @@ -669,11 +685,15 @@ impl DynSolValue { let head_words = contents.iter().map(Self::head_words).sum::(); enc.push_offset(head_words as u32); - contents.iter().for_each(|t| { + for t in contents { t.head_append(enc); enc.bump_offset(t.tail_words() as u32); - }); - contents.iter().for_each(|t| t.tail_append(enc)); + } + + for t in contents { + t.tail_append(enc); + } + enc.pop_offset(); } diff --git a/crates/json-abi/src/abi.rs b/crates/json-abi/src/abi.rs index 1b7d43c39..1627b1333 100644 --- a/crates/json-abi/src/abi.rs +++ b/crates/json-abi/src/abi.rs @@ -52,8 +52,14 @@ impl JsonAbi { /// Returns an iterator over all of the items in the ABI. #[inline] pub fn items(&self) -> Items<'_> { + self.items_with_len(self.len()) + } + + // `len` must be `self.len()` + #[inline] + fn items_with_len(&self, len: usize) -> Items<'_> { Items { - len: self.len(), + len, constructor: self.constructor.as_ref(), fallback: self.fallback.as_ref(), receive: self.receive.as_ref(), @@ -78,6 +84,7 @@ impl JsonAbi { } /// Creates constructor call builder. + #[inline] pub const fn constructor(&self) -> Option<&Constructor> { self.constructor.as_ref() } @@ -85,6 +92,7 @@ impl JsonAbi { /// Parse the ABI json from a `str`. This is a convenience wrapper around /// [`serde_json::from_str`]. #[cfg(feature = "serde_json")] + #[inline] pub fn from_json_str(json: &str) -> Result { serde_json::from_str(json) } @@ -104,31 +112,37 @@ impl JsonAbi { } /// Gets all the functions with the given name. + #[inline] pub fn function(&self, name: &str) -> Option<&[Function]> { self.functions.get(name).map(Vec::as_slice) } /// Gets all the events with the given name. + #[inline] pub fn event(&self, name: &str) -> Option<&[Event]> { self.events.get(name).map(Vec::as_slice) } /// Gets all the errors with the given name. + #[inline] pub fn error(&self, name: &str) -> Option<&[Error]> { self.errors.get(name).map(Vec::as_slice) } /// Iterates over all the functions of the contract in arbitrary order. + #[inline] pub fn functions(&self) -> Flatten>> { self.functions.values().flatten() } /// Iterates over all the events of the contract in arbitrary order. + #[inline] pub fn events(&self) -> Flatten>> { self.events.values().flatten() } /// Iterates over all the errors of the contract in arbitrary order. + #[inline] pub fn errors(&self) -> Flatten>> { self.errors.values().flatten() } @@ -137,8 +151,7 @@ impl JsonAbi { macro_rules! next_item { ($self:ident; $($ident:ident.$f:ident()),* $(,)?) => {$( if let Some(next) = $self.$ident.$f() { - // SAFETY: length is valid - $self.len = unsafe { $self.len.checked_sub(1).unwrap_unchecked() }; + $self.len -= 1; return Some(next.into()) } )*}; @@ -261,37 +274,12 @@ impl<'de> Deserialize<'de> for JsonAbi { } impl Serialize for JsonAbi { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut seq = serializer.serialize_seq(Some(self.len()))?; - - if let Some(constructor) = &self.constructor { - seq.serialize_element(constructor)?; - } - if let Some(fallback) = &self.fallback { - seq.serialize_element(fallback)?; - } - if let Some(receive) = &self.receive { - seq.serialize_element(receive)?; + fn serialize(&self, serializer: S) -> Result { + let len = self.len(); + let mut seq = serializer.serialize_seq(Some(len))?; + for item in self.items_with_len(len) { + seq.serialize_element(&item)?; } - - self.functions - .values() - .flatten() - .try_for_each(|f| seq.serialize_element(f))?; - - self.events - .values() - .flatten() - .try_for_each(|e| seq.serialize_element(e))?; - - self.errors - .values() - .flatten() - .try_for_each(|e| seq.serialize_element(e))?; - seq.end() } } diff --git a/crates/json-abi/src/param.rs b/crates/json-abi/src/param.rs index 189297fd0..e97e707a3 100644 --- a/crates/json-abi/src/param.rs +++ b/crates/json-abi/src/param.rs @@ -40,9 +40,12 @@ pub struct Param { impl fmt::Display for Param { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(internal_type) = &self.internal_type { - write!(f, "{} ", internal_type)?; - } + if let Some(it) = &self.internal_type { + it.fmt(f) + } else { + f.write_str(&self.ty) + }?; + f.write_str(" ")?; f.write_str(&self.name) } } @@ -69,6 +72,7 @@ impl<'de> Deserialize<'de> for Param { } impl Serialize for Param { + #[inline] fn serialize(&self, serializer: S) -> Result { self.as_inner().serialize(serializer) } @@ -195,12 +199,13 @@ impl Param { } } + #[inline] fn borrowed_internal_type(&self) -> Option> { self.internal_type().as_ref().map(|it| it.as_borrowed()) } #[inline] - fn as_inner(&self) -> BorrowedParam<'_, Param> { + fn as_inner(&self) -> BorrowedParam<'_> { BorrowedParam { name: &self.name, ty: &self.ty, @@ -241,9 +246,12 @@ pub struct EventParam { impl fmt::Display for EventParam { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(internal_type) = &self.internal_type { - write!(f, "{} ", internal_type)?; - } + if let Some(it) = &self.internal_type { + it.fmt(f) + } else { + f.write_str(&self.ty) + }?; + f.write_str(" ")?; f.write_str(&self.name) } } @@ -271,6 +279,7 @@ impl<'de> Deserialize<'de> for EventParam { } impl Serialize for EventParam { + #[inline] fn serialize(&self, serializer: S) -> Result { self.as_inner().serialize(serializer) } @@ -397,12 +406,13 @@ impl EventParam { } } + #[inline] fn borrowed_internal_type(&self) -> Option> { self.internal_type().as_ref().map(|it| it.as_borrowed()) } #[inline] - fn as_inner(&self) -> BorrowedParam<'_, Param> { + fn as_inner(&self) -> BorrowedParam<'_> { BorrowedParam { name: &self.name, ty: &self.ty, @@ -413,9 +423,8 @@ impl EventParam { } } -#[derive(Deserialize, Serialize, Debug)] -#[serde(bound(deserialize = "<[T] as ToOwned>::Owned: Default + Deserialize<'de>"))] -struct BorrowedParam<'a, T: Clone> { +#[derive(Deserialize, Serialize)] +struct BorrowedParam<'a> { name: &'a str, #[serde(rename = "type")] ty: &'a str, @@ -424,12 +433,11 @@ struct BorrowedParam<'a, T: Clone> { #[serde( rename = "internalType", default, - skip_serializing_if = "Option::is_none", - borrow + skip_serializing_if = "Option::is_none" )] internal_type: Option>, #[serde(default, skip_serializing_if = "<[_]>::is_empty")] - components: Cow<'a, [T]>, + components: Cow<'a, [Param]>, } #[cfg(test)] diff --git a/crates/primitives/src/bits/address.rs b/crates/primitives/src/bits/address.rs index 21eea91d3..520cb12f2 100644 --- a/crates/primitives/src/bits/address.rs +++ b/crates/primitives/src/bits/address.rs @@ -16,13 +16,22 @@ pub enum AddressError { } impl From for AddressError { + #[inline] fn from(value: hex::FromHexError) -> Self { Self::Hex(value) } } #[cfg(feature = "std")] -impl std::error::Error for AddressError {} +impl std::error::Error for AddressError { + #[inline] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Hex(err) => Some(err), + Self::InvalidChecksum => None, + } + } +} impl fmt::Display for AddressError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -173,23 +182,21 @@ impl Address { s: S, chain_id: Option, ) -> Result { - fn inner(s: &str, chain_id: Option) -> Result { + fn parse_checksummed(s: &str, chain_id: Option) -> Result { // checksummed addresses always start with the "0x" prefix if !s.starts_with("0x") { return Err(AddressError::Hex(hex::FromHexError::InvalidStringLength)) } let address: Address = s.parse()?; - let buf = &mut [0; 42]; - let expected = address.to_checksum_raw(buf, chain_id); - if s == expected { + if s == address.to_checksum_raw(&mut [0; 42], chain_id) { Ok(address) } else { Err(AddressError::InvalidChecksum) } } - inner(s.as_ref(), chain_id) + parse_checksummed(s.as_ref(), chain_id) } /// Encodes an Ethereum address to its [EIP-55] checksum. @@ -292,8 +299,7 @@ impl Address { #[inline] #[must_use] pub fn to_checksum(&self, chain_id: Option) -> String { - let mut buf = [0u8; 42]; - self.to_checksum_raw(&mut buf, chain_id).to_string() + self.to_checksum_raw(&mut [0u8; 42], chain_id).to_string() } /// Computes the `create` address for this address and nonce: diff --git a/crates/primitives/src/bits/function.rs b/crates/primitives/src/bits/function.rs new file mode 100644 index 000000000..3fb253c96 --- /dev/null +++ b/crates/primitives/src/bits/function.rs @@ -0,0 +1,59 @@ +use crate::FixedBytes; +use core::borrow::Borrow; + +wrap_fixed_bytes! { + /// An Ethereum ABI function pointer, 24 bytes in length. + /// + /// An address (20 bytes), followed by a function selector (4 bytes). + /// Encoded identical to `bytes24`. + pub struct Function<24>; +} + +impl From<(A, S)> for Function +where + A: Borrow<[u8; 20]>, + S: Borrow<[u8; 4]>, +{ + #[inline] + fn from((address, selector): (A, S)) -> Self { + Self::from_address_and_selector(address, selector) + } +} + +impl Function { + /// Creates an Ethereum function from an EVM word's lower 24 bytes + /// (`word[..24]`). + /// + /// Note that this is different from `Address::from_word`, which uses the + /// upper 20 bytes. + #[inline] + #[must_use] + pub fn from_word(word: FixedBytes<32>) -> Self { + Self(FixedBytes(word[..24].try_into().unwrap())) + } + + /// Right-pads the function to 32 bytes (EVM word size). + /// + /// Note that this is different from `Address::into_word`, which left-pads + /// the address. + #[inline] + #[must_use] + pub fn into_word(&self) -> FixedBytes<32> { + let mut word = [0; 32]; + word[..24].copy_from_slice(self.as_slice()); + FixedBytes(word) + } + + /// Creates an Ethereum function from an address and selector. + #[inline] + pub fn from_address_and_selector(address: A, selector: S) -> Self + where + A: Borrow<[u8; 20]>, + S: Borrow<[u8; 4]>, + { + let mut bytes = [0; 24]; + bytes[..20].copy_from_slice(address.borrow()); + bytes[20..].copy_from_slice(selector.borrow()); + Self(FixedBytes(bytes)) + } +} diff --git a/crates/primitives/src/bits/mod.rs b/crates/primitives/src/bits/mod.rs index 85071a1e6..28e5a91f0 100644 --- a/crates/primitives/src/bits/mod.rs +++ b/crates/primitives/src/bits/mod.rs @@ -10,6 +10,9 @@ pub use bloom::{Bloom, BloomInput, BLOOM_BITS_PER_ITEM, BLOOM_SIZE_BITS, BLOOM_S mod fixed; pub use fixed::FixedBytes; +mod function; +pub use function::Function; + #[cfg(feature = "rlp")] mod rlp; diff --git a/crates/primitives/src/bits/serde.rs b/crates/primitives/src/bits/serde.rs index 3cc5b2462..08f6f23d0 100644 --- a/crates/primitives/src/bits/serde.rs +++ b/crates/primitives/src/bits/serde.rs @@ -39,20 +39,16 @@ impl<'de, const N: usize> Deserialize<'de> for FixedBytes { } fn visit_seq>(self, mut seq: A) -> Result { + let len_error = + |i| de::Error::invalid_length(i, &format!("exactly {N} bytes").as_str()); let mut bytes = [0u8; N]; - bytes.iter_mut().enumerate().try_for_each(|(i, b)| { - *b = seq.next_element()?.ok_or_else(|| { - de::Error::invalid_length(i, &format!("exactly {} bytes", N).as_str()) - })?; - Ok(()) - })?; + for (i, byte) in bytes.iter_mut().enumerate() { + *byte = seq.next_element()?.ok_or_else(|| len_error(i))?; + } if let Ok(Some(_)) = seq.next_element::() { - return Err(de::Error::invalid_length( - N + 1, - &format!("exactly {} bytes", N).as_str(), - )) + return Err(len_error(N + 1)) } Ok(FixedBytes(bytes)) diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index ee0214309..133163867 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -33,8 +33,8 @@ pub use aliases::{ mod bits; pub use bits::{ - Address, AddressError, Bloom, BloomInput, FixedBytes, BLOOM_BITS_PER_ITEM, BLOOM_SIZE_BITS, - BLOOM_SIZE_BYTES, + Address, AddressError, Bloom, BloomInput, FixedBytes, Function, BLOOM_BITS_PER_ITEM, + BLOOM_SIZE_BITS, BLOOM_SIZE_BYTES, }; mod bytes; diff --git a/crates/sol-macro/src/expand/ty.rs b/crates/sol-macro/src/expand/ty.rs index 77eaee85f..efb43df13 100644 --- a/crates/sol-macro/src/expand/ty.rs +++ b/crates/sol-macro/src/expand/ty.rs @@ -112,8 +112,13 @@ fn rec_expand_type(ty: &Type, tokens: &mut TokenStream) { } } } - Type::Function(ref function) => todo!("function types: {function:?}"), - Type::Mapping(ref mapping) => todo!("mapping types: {mapping:?}"), + Type::Function(ref function) => { + let span = function.span(); + quote_spanned! {span=> + ::alloy_sol_types::sol_data::Function + } + } + Type::Mapping(ref mapping) => panic!("mapping types are unsupported: {mapping:?}"), Type::Custom(ref custom) => return custom.to_tokens(tokens), }; tokens.extend(tts); diff --git a/crates/sol-type-parser/src/root.rs b/crates/sol-type-parser/src/root.rs index 3bbd3af68..2336f0e76 100644 --- a/crates/sol-type-parser/src/root.rs +++ b/crates/sol-type-parser/src/root.rs @@ -76,7 +76,7 @@ impl<'a> RootType<'a> { #[inline] pub fn try_basic_solidity(self) -> Result<()> { match self.0 { - "address" | "bool" | "string" | "bytes" | "uint" | "int" => Ok(()), + "address" | "bool" | "string" | "bytes" | "uint" | "int" | "function" => Ok(()), name => { if let Some(sz) = name.strip_prefix("bytes") { if let Ok(sz) = sz.parse::() { @@ -96,10 +96,10 @@ impl<'a> RootType<'a> { return Ok(()) } } - Err(Error::invalid_size(name)) - } else { - Err(Error::invalid_type_string(name)) + return Err(Error::invalid_size(name)) } + + Err(Error::invalid_type_string(name)) } } } diff --git a/crates/sol-type-parser/src/type_spec.rs b/crates/sol-type-parser/src/type_spec.rs index c9475e28b..2735fd469 100644 --- a/crates/sol-type-parser/src/type_spec.rs +++ b/crates/sol-type-parser/src/type_spec.rs @@ -40,7 +40,7 @@ use core::num::NonZeroUsize; /// ``` /// # use alloy_sol_type_parser::TypeSpecifier; /// # use core::num::NonZeroUsize; -/// let spec = TypeSpecifier::try_from("uint256[2][]")?; +/// let spec = TypeSpecifier::parse("uint256[2][]")?; /// assert_eq!(spec.span(), "uint256[2][]"); /// assert_eq!(spec.stem.span(), "uint256"); /// // The sizes are in innermost-to-outermost order. @@ -79,43 +79,62 @@ impl<'a> TypeSpecifier<'a> { /// Parse a type specifier from a string. pub fn parse(span: &'a str) -> Result { let span = span.trim(); + let err = || Error::invalid_type_string(span); + + // i is the start of the array sizes + let (i, is_tuple) = if let Some(i) = span.rfind(')') { + // ')' is 1 byte + (i + 1, true) + } else { + (span.find('[').unwrap_or(span.len()), false) + }; + // spit_at_unchecked(i) + let (l, r) = unsafe { (span.get_unchecked(..i), span.get_unchecked(i..)) }; + // avoids double check in `TypeStem::parse` + let stem = if is_tuple { + l.try_into().map(TypeStem::Tuple) + } else { + l.try_into().map(TypeStem::Root) + }?; - let mut root = span; let mut sizes = vec![]; - - // an iterator of string slices split by `[` - for s in root.rsplit('[') { - // we've reached a root tuple so we need to include the closing - // paren - if s.contains(')') { - let idx = span.rfind(')').unwrap(); - root = &span[..=idx]; - break - } - // we've reached a root type that is not a tuple or array - if !s.contains(']') { - root = s; - break + let mut chars = r.char_indices(); + while let Some((i, next)) = chars.next() { + match next { + '[' => { + let mut j = 0; + let mut closed = false; + for (idx, c) in chars.by_ref() { + match c { + ']' => { + closed = true; + break + } + c if c.is_ascii_digit() => j = idx, + c if c.is_whitespace() => continue, + _ => return Err(err()), + } + } + if !closed { + return Err(err()) + } + let size = if j == 0 { + None + } else { + // i and j are the index of '[' and the last digit respectively, + // '[' and ASCII digits are 1 byte + let s = unsafe { r.get_unchecked(i + 1..j + 1) }; + // end is trimmed in the loop above + Some(s.trim_start().parse().map_err(|_| err())?) + }; + sizes.push(size); + } + c if c.is_whitespace() => continue, + _ => return Err(err()), } - - let s = s - .trim() - .strip_suffix(']') - .ok_or_else(|| Error::invalid_type_string(span))?; - let size = if s.is_empty() { - None - } else { - Some(s.parse().map_err(|_| Error::invalid_type_string(span))?) - }; - sizes.push(size); } - sizes.reverse(); - Ok(Self { - span, - stem: root.try_into()?, - sizes, - }) + Ok(Self { span, stem, sizes }) } /// Returns the type stem as a string. @@ -145,64 +164,64 @@ mod test { #[test] fn parse_test() { assert_eq!( - super::TypeSpecifier::try_from("uint256").unwrap(), - super::TypeSpecifier { + TypeSpecifier::parse("uint256").unwrap(), + TypeSpecifier { span: "uint256", - stem: TypeStem::try_from("uint256").unwrap(), + stem: TypeStem::parse("uint256").unwrap(), sizes: vec![], } ); assert_eq!( - super::TypeSpecifier::try_from("uint256[2]").unwrap(), - super::TypeSpecifier { + TypeSpecifier::parse("uint256[2]").unwrap(), + TypeSpecifier { span: "uint256[2]", - stem: TypeStem::try_from("uint256").unwrap(), + stem: TypeStem::parse("uint256").unwrap(), sizes: vec![NonZeroUsize::new(2)], } ); assert_eq!( - super::TypeSpecifier::try_from("uint256[2][]").unwrap(), - super::TypeSpecifier { + TypeSpecifier::parse("uint256[2][]").unwrap(), + TypeSpecifier { span: "uint256[2][]", - stem: TypeStem::try_from("uint256").unwrap(), + stem: TypeStem::parse("uint256").unwrap(), sizes: vec![NonZeroUsize::new(2), None], } ); assert_eq!( - super::TypeSpecifier::try_from("(uint256,uint256)").unwrap(), - super::TypeSpecifier { + TypeSpecifier::parse("(uint256,uint256)").unwrap(), + TypeSpecifier { span: "(uint256,uint256)", - stem: TypeStem::Tuple(TupleSpecifier::try_from("(uint256,uint256)").unwrap()), + stem: TypeStem::Tuple(TupleSpecifier::parse("(uint256,uint256)").unwrap()), sizes: vec![], } ); assert_eq!( - super::TypeSpecifier::try_from("(uint256,uint256)[2]").unwrap(), - super::TypeSpecifier { + TypeSpecifier::parse("(uint256,uint256)[2]").unwrap(), + TypeSpecifier { span: "(uint256,uint256)[2]", - stem: TypeStem::Tuple(TupleSpecifier::try_from("(uint256,uint256)").unwrap()), + stem: TypeStem::Tuple(TupleSpecifier::parse("(uint256,uint256)").unwrap()), sizes: vec![NonZeroUsize::new(2)], } ); assert_eq!( - super::TypeSpecifier::try_from("MyStruct").unwrap(), - super::TypeSpecifier { + TypeSpecifier::parse("MyStruct").unwrap(), + TypeSpecifier { span: "MyStruct", - stem: TypeStem::try_from("MyStruct").unwrap(), + stem: TypeStem::parse("MyStruct").unwrap(), sizes: vec![], } ); assert_eq!( - super::TypeSpecifier::try_from("MyStruct[2]").unwrap(), - super::TypeSpecifier { + TypeSpecifier::parse("MyStruct[2]").unwrap(), + TypeSpecifier { span: "MyStruct[2]", - stem: TypeStem::try_from("MyStruct").unwrap(), + stem: TypeStem::parse("MyStruct").unwrap(), sizes: vec![NonZeroUsize::new(2)], } ); @@ -210,6 +229,6 @@ mod test { #[test] fn a_type_named_tuple() { - TypeSpecifier::try_from("tuple").unwrap(); + TypeSpecifier::parse("tuple").unwrap(); } } diff --git a/crates/sol-types/src/coder/encoder.rs b/crates/sol-types/src/coder/encoder.rs index e2ebefbec..6c5e0e26e 100644 --- a/crates/sol-types/src/coder/encoder.rs +++ b/crates/sol-types/src/coder/encoder.rs @@ -7,13 +7,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::{ - token::TokenSeq, - utils::{pad_u32, words_for}, - TokenType, Word, -}; +use crate::{token::TokenSeq, utils, TokenType, Word}; use alloc::vec::Vec; -use core::mem; +use core::{mem, ptr}; /// An ABI encoder. /// @@ -111,34 +107,13 @@ impl Encoder { #[inline] #[cfg_attr(debug_assertions, track_caller)] pub fn append_indirection(&mut self) { - self.append_word(pad_u32(self.suffix_offset())); + self.append_word(utils::pad_u32(self.suffix_offset())); } /// Append a sequence length. #[inline] pub fn append_seq_len(&mut self, len: usize) { - self.append_word(pad_u32(len as u32)); - } - - /// Append a sequence of bytes, padding to the next word. - #[inline] - fn append_bytes(&mut self, bytes: &[u8]) { - let len = words_for(bytes); - for i in 0..len { - let mut padded = Word::ZERO; - - let to_copy = match i == len - 1 { - false => 32, - true => match bytes.len() % 32 { - 0 => 32, - x => x, - }, - }; - - let offset = 32 * i; - padded[..to_copy].copy_from_slice(&bytes[offset..offset + to_copy]); - self.append_word(padded); - } + self.append_word(utils::pad_u32(len as u32)); } /// Append a sequence of bytes as a packed sequence with a length prefix. @@ -153,6 +128,31 @@ impl Encoder { pub fn append_head_tail<'a, T: TokenSeq<'a>>(&mut self, token: &T) { token.encode_sequence(self); } + + /// Append a sequence of bytes, padding to the next word. + #[inline(always)] + fn append_bytes(&mut self, bytes: &[u8]) { + let n_words = utils::words_for(bytes); + self.buf.reserve(n_words); + unsafe { + // set length before copying + // this is fine because we reserved above and we don't panic below + let len = self.buf.len(); + self.buf.set_len(len + n_words); + + // copy + let cnt = bytes.len(); + let dst = self.buf.as_mut_ptr().add(len).cast::(); + ptr::copy_nonoverlapping(bytes.as_ptr(), dst, cnt); + + // set remaining bytes to zero if necessary + let rem = cnt % 32; + if rem != 0 { + let pad = 32 - rem; + ptr::write_bytes(dst.add(cnt), 0, pad); + } + } + } } /// ABI-encode a token sequence. diff --git a/crates/sol-types/src/types/data_type.rs b/crates/sol-types/src/types/data_type.rs index 8648d971c..86aa2e6c8 100644 --- a/crates/sol-types/src/types/data_type.rs +++ b/crates/sol-types/src/types/data_type.rs @@ -8,7 +8,7 @@ use crate::{token::*, utils, Encodable, Result, SolType, Word}; use alloc::{borrow::Cow, string::String as RustString, vec::Vec}; -use alloy_primitives::{keccak256, Address as RustAddress, I256, U256}; +use alloy_primitives::{keccak256, Address as RustAddress, Function as RustFunction, I256, U256}; use core::{borrow::Borrow, fmt::*, hash::Hash, marker::PhantomData, ops::*}; /// Bool - `bool` @@ -209,14 +209,58 @@ impl SolType for Address { #[inline] fn encode_packed_to(rust: &Self::RustType, out: &mut Vec) { - out.extend_from_slice(rust.as_ref()); + out.extend_from_slice(rust.as_slice()); + } +} + +/// Function - `function` +pub struct Function; + +impl> Encodable for T { + #[inline] + fn to_tokens(&self) -> WordToken { + WordToken(RustFunction::new(*self.borrow()).into_word()) + } +} + +impl SolType for Function { + type RustType = RustFunction; + type TokenType<'a> = WordToken; + + #[inline] + fn sol_type_name() -> Cow<'static, str> { + "function".into() + } + + #[inline] + fn detokenize(token: Self::TokenType<'_>) -> Self::RustType { + RustFunction::from_word(token.0) + } + + #[inline] + fn type_check(token: &Self::TokenType<'_>) -> Result<()> { + if utils::check_zeroes(&token.0[24..]) { + Ok(()) + } else { + Err(Self::type_check_fail(token.as_slice())) + } + } + + #[inline] + fn eip712_data_word(rust: &Self::RustType) -> Word { + rust.into_word() + } + + #[inline] + fn encode_packed_to(rust: &Self::RustType, out: &mut Vec) { + out.extend_from_slice(rust.as_slice()); } } /// Bytes - `bytes` pub struct Bytes; -impl> Encodable for T { +impl> Encodable for T { #[inline] fn to_tokens(&self) -> PackedSeqToken<'_> { PackedSeqToken(self.as_ref()) @@ -333,10 +377,10 @@ impl SolType for Array { /// String - `string` pub struct String; -impl> Encodable for T { +impl> Encodable for T { #[inline] - fn to_tokens(&self) -> ::TokenType<'_> { - self.as_ref().as_bytes().into() + fn to_tokens(&self) -> PackedSeqToken<'_> { + PackedSeqToken(self.as_ref().as_bytes()) } } diff --git a/crates/sol-types/src/types/event/topic.rs b/crates/sol-types/src/types/event/topic.rs index 38abf5b0d..af2333a97 100644 --- a/crates/sol-types/src/types/event/topic.rs +++ b/crates/sol-types/src/types/event/topic.rs @@ -40,7 +40,7 @@ pub trait EventTopic: SolType { // Single word types: encoded as just the single word macro_rules! word_impl { - ($t:ty) => { + () => { #[inline] fn topic_preimage_length(_: &Self::RustType) -> usize { 32 @@ -48,48 +48,52 @@ macro_rules! word_impl { #[inline] fn encode_topic_preimage(rust: &Self::RustType, out: &mut Vec) { - out.extend($crate::Encodable::<$t>::to_tokens(rust).0 .0); + out.extend($crate::Encodable::::to_tokens(rust).0 .0); } #[inline] fn encode_topic(rust: &Self::RustType) -> WordToken { - $crate::Encodable::<$t>::to_tokens(rust) + $crate::Encodable::::to_tokens(rust) } }; } impl EventTopic for Address { - word_impl!(Address); + word_impl!(); +} + +impl EventTopic for Function { + word_impl!(); } impl EventTopic for Bool { - word_impl!(Bool); + word_impl!(); } impl EventTopic for Int where IntBitCount: SupportedInt, { - word_impl!(Int); + word_impl!(); } impl EventTopic for Uint where IntBitCount: SupportedInt, { - word_impl!(Uint); + word_impl!(); } impl EventTopic for FixedBytes where ByteCount: SupportedFixedBytes, { - word_impl!(FixedBytes); + word_impl!(); } // Bytes-like types - preimage encoding: bytes padded to 32; hash: the bytes macro_rules! bytes_impl { - ($t:ty) => { + () => { #[inline] fn topic_preimage_length(rust: &Self::RustType) -> usize { crate::utils::next_multiple_of_32(rust.len()) @@ -108,11 +112,11 @@ macro_rules! bytes_impl { } impl EventTopic for String { - bytes_impl!(String); + bytes_impl!(); } impl EventTopic for Bytes { - bytes_impl!(Bytes); + bytes_impl!(); } // Complex types - preimage encoding and hash: iter each element diff --git a/crates/sol-types/src/utils.rs b/crates/sol-types/src/utils.rs index 3727ef143..5528be3a4 100644 --- a/crates/sol-types/src/utils.rs +++ b/crates/sol-types/src/utils.rs @@ -13,26 +13,26 @@ use crate::{Error, Result, Word}; /// Calculates the padded length of a slice by rounding its length to the next /// word. -#[inline] +#[inline(always)] pub const fn words_for(data: &[u8]) -> usize { words_for_len(data.len()) } /// Calculates the padded length of a slice of a specific length by rounding its /// length to the next word. -#[inline] +#[inline(always)] pub const fn words_for_len(len: usize) -> usize { (len + 31) / 32 } /// `padded_len` rounds a slice length up to the next multiple of 32 -#[inline] +#[inline(always)] pub(crate) const fn padded_len(data: &[u8]) -> usize { next_multiple_of_32(data.len()) } /// See [`usize::next_multiple_of`]. -#[inline] +#[inline(always)] pub const fn next_multiple_of_32(n: usize) -> usize { match n % 32 { 0 => n, diff --git a/crates/sol-types/tests/ui/type.rs b/crates/sol-types/tests/ui/type.rs index 9a1a8ee1c..046c60014 100644 --- a/crates/sol-types/tests/ui/type.rs +++ b/crates/sol-types/tests/ui/type.rs @@ -734,4 +734,22 @@ sol! { } } +sol! { + struct Mappings { + mapping(mapping(a b => c d) e => mapping(f g => h i) j) map; + } +} + +sol! { + function mappings(mapping(uint256 a => bool b), mapping(bool => bool) x); +} + +sol! { + struct FunctionTypes { + function(function(bool) external pure returns (function(function())) f) external returns (function()) c; + } + + function functionTypes(FunctionTypes f) returns (function(function(function(), function())), function(function(), function())); +} + fn main() {} diff --git a/crates/sol-types/tests/ui/type.stderr b/crates/sol-types/tests/ui/type.stderr index bb40b476b..eb68fd074 100644 --- a/crates/sol-types/tests/ui/type.stderr +++ b/crates/sol-types/tests/ui/type.stderr @@ -64,6 +64,28 @@ error: enum has too many variants 476 | enum TooBigEnum { | ^^^^^^^^^^ +error: proc macro panicked + --> tests/ui/type.rs:737:1 + | +737 | / sol! { +738 | | struct Mappings { +739 | | mapping(mapping(a b => c d) e => mapping(f g => h i) j) map; +740 | | } +741 | | } + | |_^ + | + = help: message: mapping types are unsupported: TypeMapping { key: TypeMapping { key: Custom([SolIdent("a")]), key_name: Some(SolIdent("b")), value: Custom([SolIdent("c")]), value_name: Some(SolIdent("d")) }, key_name: Some(SolIdent("e")), value: TypeMapping { key: Custom([SolIdent("f")]), key_name: Some(SolIdent("g")), value: Custom([SolIdent("h")]), value_name: Some(SolIdent("i")) }, value_name: Some(SolIdent("j")) } + +error: proc macro panicked + --> tests/ui/type.rs:743:1 + | +743 | / sol! { +744 | | function mappings(mapping(uint256 a => bool b), mapping(bool => bool) x); +745 | | } + | |_^ + | + = help: message: mapping types are unsupported: TypeMapping { key: Uint(Some(256)), key_name: Some(SolIdent("a")), value: Bool, value_name: Some(SolIdent("b")) } + error[E0412]: cannot find type `bytes_` in this scope --> tests/ui/type.rs:205:9 | @@ -117,6 +139,7 @@ error[E0277]: the trait bound `(Address, Address, alloy_sol_types::sol_data::Str ArrayTypes TupleTypes CustomTypes + FunctionTypes = note: required for `(Address, ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ...)` to implement `SolType` note: required by a bound in `Encodable` --> src/types/ty.rs @@ -142,5 +165,6 @@ error[E0277]: the trait bound `(Address, Address, alloy_sol_types::sol_data::Str ArrayTypes TupleTypes CustomTypes + FunctionTypes = note: required for `(Address, ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ..., ...)` to implement `SolType` = note: this error originates in the macro `sol` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/syn-solidity/src/attribute/function.rs b/crates/syn-solidity/src/attribute/function.rs index 347f50c70..cb56c6f95 100644 --- a/crates/syn-solidity/src/attribute/function.rs +++ b/crates/syn-solidity/src/attribute/function.rs @@ -41,11 +41,7 @@ impl DerefMut for FunctionAttributes { impl Parse for FunctionAttributes { fn parse(input: ParseStream<'_>) -> Result { let mut attributes = Vec::::new(); - while !(input.is_empty() - || input.peek(kw::returns) - || input.peek(Token![;]) - || input.peek(Brace)) - { + while !(input.is_empty() || input.peek(kw::returns) || input.peek(Ident::peek_any)) { let attr: FunctionAttribute = input.parse()?; if let Some(prev) = attributes.iter().find(|a| **a == attr) { let mut e = Error::new(attr.span(), "duplicate attribute"); diff --git a/crates/syn-solidity/src/type/mapping.rs b/crates/syn-solidity/src/type/mapping.rs index 69daa85c3..fb5986646 100644 --- a/crates/syn-solidity/src/type/mapping.rs +++ b/crates/syn-solidity/src/type/mapping.rs @@ -51,7 +51,15 @@ impl fmt::Debug for TypeMapping { impl fmt::Display for TypeMapping { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "mapping({} => {})", self.key, self.value) + write!(f, "mapping({} ", self.key)?; + if let Some(key_name) = &self.key_name { + write!(f, "{key_name} ")?; + } + write!(f, "=> {} ", self.value)?; + if let Some(value_name) = &self.value_name { + write!(f, "{value_name}")?; + } + f.write_str(")") } }