From 417396d9bdfb1044002de0a591087990c8f654a3 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Jul 2023 15:27:20 -0700 Subject: [PATCH 01/17] fix: decode relative to first word of dyn array body --- crates/dyn-abi/src/token.rs | 32 ++++---- crates/dyn-abi/src/type.rs | 107 ++++++++++++++++++++++++++ crates/dyn-abi/src/value.rs | 11 ++- crates/sol-types/src/coder/decoder.rs | 50 +++++++++++- crates/sol-types/src/coder/token.rs | 18 ++--- 5 files changed, 190 insertions(+), 28 deletions(-) diff --git a/crates/dyn-abi/src/token.rs b/crates/dyn-abi/src/token.rs index e8a7ff00f..79b82731e 100644 --- a/crates/dyn-abi/src/token.rs +++ b/crates/dyn-abi/src/token.rs @@ -134,29 +134,31 @@ impl<'a> DynToken<'a> { let dynamic = self.is_dynamic(); match self { Self::Word(w) => *w = WordToken::decode_from(dec)?.0, - Self::FixedSeq(tokens, size) => { + Self::FixedSeq(_, _) => { let mut child = if dynamic { dec.take_indirection()? } else { dec.raw_child() }; - for token in tokens.to_mut().iter_mut().take(*size) { - token.decode_populate(&mut child)?; + + self.decode_sequence_populate(&mut child)?; + + if !dynamic { + dec.take_offset(child); } } Self::DynSeq { contents, template } => { let mut child = dec.take_indirection()?; let size = child.take_u32()? as usize; - let mut new_tokens: Vec> = Vec::with_capacity(size); - for _ in 0..size { - let mut t = if let Some(item) = new_tokens.first() { - item.clone() - } else { - *(template.take().unwrap()) - }; - t.decode_populate(&mut child)?; - new_tokens.push(t); - } + let mut child = child.raw_child(); + + let mut new_tokens: Vec<_> = Vec::with_capacity(size); + new_tokens.resize(size, *(template.take().unwrap())); + + new_tokens + .iter_mut() + .for_each(|t| t.decode_populate(&mut child).unwrap()); + *contents = new_tokens.into(); } Self::PackedSeq(buf) => *buf = PackedSeqToken::decode_from(dec)?.0, @@ -169,8 +171,8 @@ impl<'a> DynToken<'a> { #[inline] pub(crate) fn decode_sequence_populate(&mut self, dec: &mut Decoder<'a>) -> Result<()> { match self { - Self::FixedSeq(buf, _) => { - for item in buf.to_mut().iter_mut() { + Self::FixedSeq(buf, size) => { + for item in buf.to_mut().iter_mut().take(*size) { item.decode_populate(dec)?; } Ok(()) diff --git a/crates/dyn-abi/src/type.rs b/crates/dyn-abi/src/type.rs index 78e439d81..f8b24d931 100644 --- a/crates/dyn-abi/src/type.rs +++ b/crates/dyn-abi/src/type.rs @@ -328,6 +328,22 @@ mod tests { use alloy_primitives::Address; use serde_json::json; + use hex_literal::hex; + + macro_rules! encoder_test { + ($ty:literal, $encoded:ident) => { + let t: DynSolType = $ty.parse().expect("parsing failed"); + let dec = t.decode_params(&$encoded).expect("decoding failed"); + + let re_encoded = dec.encode_params(); + if (re_encoded != $encoded) { + println!("correct: {}", hex::encode(&$encoded)); + println!("actual: {}", hex::encode(&re_encoded)); + } + assert_eq!(re_encoded, $encoded); + }; + } + #[test] fn dynamically_encodes() { let word1 = "0000000000000000000000000101010101010101010101010101010101010101" @@ -462,4 +478,95 @@ mod tests { } ) } + + #[test] + fn address() { + let enc = hex! {"0000000000000000000000001111111111111111111111111111111111111111"}; + encoder_test!("address", enc); + } + + #[test] + fn dynamic_array_of_addresses() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000002222222222222222222222222222222222222222 + " + ); + encoder_test!("address[]", encoded); + } + + #[test] + fn fixed_array_of_addresses() { + let encoded = hex!( + " + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000002222222222222222222222222222222222222222 + " + ); + encoder_test!("address[2]", encoded); + } + + #[test] + fn two_addresses() { + let encoded = hex!( + " + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000002222222222222222222222222222222222222222 + " + ); + encoder_test!("(address,address)", encoded); + } + + #[test] + fn fixed_array_of_dyanmic_arrays_of_addresses() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000040 + 00000000000000000000000000000000000000000000000000000000000000a0 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000002222222222222222222222222222222222222222 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000003333333333333333333333333333333333333333 + 0000000000000000000000004444444444444444444444444444444444444444 + " + ); + encoder_test!("address[][2]", encoded); + } + + #[test] + fn dynamic_array_of_fixed_arrays_of_addresses() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000002222222222222222222222222222222222222222 + 0000000000000000000000003333333333333333333333333333333333333333 + 0000000000000000000000004444444444444444444444444444444444444444 + " + ); + encoder_test!("address[2][]", encoded); + } + + #[test] + fn dynamic_array_of_dynamic_arrays() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000080 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000002222222222222222222222222222222222222222 + " + ); + encoder_test!("address[][]", encoded); + } } diff --git a/crates/dyn-abi/src/value.rs b/crates/dyn-abi/src/value.rs index 997c21c2b..01cd7555e 100644 --- a/crates/dyn-abi/src/value.rs +++ b/crates/dyn-abi/src/value.rs @@ -470,16 +470,23 @@ impl DynSolValue { } if let Some(vals) = self.as_fixed_seq() { - return self.is_dynamic() as usize * vals.len() + return self.is_dynamic() as usize * vals.iter().map(Self::total_words).sum::() } if let Some(vals) = self.as_array() { - return 1 + vals.iter().map(Self::tail_words).sum::() + // 1 for the length. Then all words for all elements. + return 1 + vals.iter().map(Self::total_words).sum::() } unreachable!() } + /// Returns the total number of words this type uses in the ABI blob. + #[inline] + pub fn total_words(&self) -> usize { + self.head_words() + self.tail_words() + } + /// Append this data to the head of an in-progress blob via the encoder. #[inline] pub fn head_append(&self, enc: &mut Encoder) { diff --git a/crates/sol-types/src/coder/decoder.rs b/crates/sol-types/src/coder/decoder.rs index e03a4821d..909c21490 100644 --- a/crates/sol-types/src/coder/decoder.rs +++ b/crates/sol-types/src/coder/decoder.rs @@ -31,14 +31,35 @@ pub struct Decoder<'de> { impl fmt::Debug for Decoder<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut body = self + .buf + .chunks(32) + .map(hex::encode_prefixed) + .collect::>(); + body[self.offset / 32].push_str(" <-- Next Word"); + f.debug_struct("Decoder") - .field("buf", &hex::encode_prefixed(self.buf)) + .field("buf", &body) .field("offset", &self.offset) .field("validate", &self.validate) .finish() } } +impl fmt::Display for Decoder<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut body = self + .buf + .chunks(32) + .enumerate() + .map(|(i, chunk)| format!("0x{:0>4x}: {}", i * 32, hex::encode_prefixed(chunk))) + .collect::>(); + body[self.offset / 32].push_str(" <-- Next Word"); + writeln!(f, "\nAbi Decode Buffer")?; + writeln!(f, "{}", body.join("\n")) + } +} + impl<'de> Decoder<'de> { /// Instantiate a new decoder from a byte slice and a validation flag. /// @@ -243,6 +264,33 @@ mod tests { use alloy_primitives::{Address, B256, U256}; use hex_literal::hex; + #[test] + fn dynamic_array_of_dynamic_arrays() { + type MyTy = sol_data::Array>; + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000080 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000002222222222222222222222222222222222222222 + " + ); + + assert_eq!( + MyTy::encode_params(&vec![ + vec![Address::repeat_byte(0x11)], + vec![Address::repeat_byte(0x22)], + ]), + encoded + ); + + MyTy::decode_params(&encoded, false).unwrap(); + } + #[test] fn decode_static_tuple_of_addresses_and_uints() { type MyTy = (sol_data::Address, sol_data::Address, sol_data::Uint<256>); diff --git a/crates/sol-types/src/coder/token.rs b/crates/sol-types/src/coder/token.rs index 2581a2902..74fcf5e87 100644 --- a/crates/sol-types/src/coder/token.rs +++ b/crates/sol-types/src/coder/token.rs @@ -268,13 +268,12 @@ impl<'de, T: TokenType<'de>, const N: usize> TokenSeq<'de> for FixedSeqToken(); enc.push_offset(head_words as u32); - for t in self.0.iter() { + self.0.iter().for_each(|t| { t.head_append(enc); enc.bump_offset(t.tail_words() as u32); - } - for t in self.0.iter() { - t.tail_append(enc); - } + }); + self.0.iter().for_each(|t| t.tail_append(enc)); + enc.pop_offset(); } @@ -329,6 +328,7 @@ impl<'de, T: TokenType<'de>> TokenType<'de> for DynSeqToken { fn decode_from(dec: &mut Decoder<'de>) -> Result { let mut child = dec.take_indirection()?; let len = child.take_u32()? as usize; + let mut child = child.raw_child(); (0..len) .map(|_| T::decode_from(&mut child)) .collect::>>() @@ -361,13 +361,11 @@ impl<'de, T: TokenType<'de>> TokenSeq<'de> for DynSeqToken { fn encode_sequence(&self, enc: &mut Encoder) { let head_words = self.0.iter().map(TokenType::head_words).sum::(); enc.push_offset(head_words as u32); - for t in self.0.iter() { + self.0.iter().for_each(|t| { t.head_append(enc); enc.bump_offset(t.tail_words() as u32); - } - for t in self.0.iter() { - t.tail_append(enc); - } + }); + self.0.iter().for_each(|t| t.tail_append(enc)); enc.pop_offset(); } From 355caae51262cf61466a55a2341cf1b59168a5e5 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Jul 2023 15:33:29 -0700 Subject: [PATCH 02/17] test: add a calculated encoded size check to dyn tests --- crates/dyn-abi/src/type.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/dyn-abi/src/type.rs b/crates/dyn-abi/src/type.rs index f8b24d931..38bfa796a 100644 --- a/crates/dyn-abi/src/type.rs +++ b/crates/dyn-abi/src/type.rs @@ -335,6 +335,7 @@ mod tests { let t: DynSolType = $ty.parse().expect("parsing failed"); let dec = t.decode_params(&$encoded).expect("decoding failed"); + assert_eq!(dec.total_words() * 32, $encoded.len()); let re_encoded = dec.encode_params(); if (re_encoded != $encoded) { println!("correct: {}", hex::encode(&$encoded)); From 4e0400edd880508cd9915464a8faa109062f892f Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Jul 2023 15:37:43 -0700 Subject: [PATCH 03/17] fix: import vec in decoder --- crates/dyn-abi/src/token.rs | 4 ++++ crates/sol-types/src/coder/decoder.rs | 2 +- crates/sol-types/src/coder/token.rs | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/dyn-abi/src/token.rs b/crates/dyn-abi/src/token.rs index 79b82731e..20e3d1529 100644 --- a/crates/dyn-abi/src/token.rs +++ b/crates/dyn-abi/src/token.rs @@ -150,6 +150,10 @@ impl<'a> DynToken<'a> { Self::DynSeq { contents, template } => { let mut child = dec.take_indirection()?; let size = child.take_u32()? as usize; + // This appears to be a bug in the solidity spec. The spec + // specifies that offsets are relative to the first word of + // `enc(X)`. But known-good test vectors do relative to the + // word AFTER the array size let mut child = child.raw_child(); let mut new_tokens: Vec<_> = Vec::with_capacity(size); diff --git a/crates/sol-types/src/coder/decoder.rs b/crates/sol-types/src/coder/decoder.rs index 909c21490..213ff4ff6 100644 --- a/crates/sol-types/src/coder/decoder.rs +++ b/crates/sol-types/src/coder/decoder.rs @@ -8,7 +8,7 @@ // except according to those terms. // -use crate::{encode, token::TokenSeq, util, Error, Result, TokenType, Word}; +use crate::{encode, private::Vec, token::TokenSeq, util, Error, Result, TokenType, Word}; use alloc::borrow::Cow; use core::{fmt, slice::SliceIndex}; diff --git a/crates/sol-types/src/coder/token.rs b/crates/sol-types/src/coder/token.rs index 74fcf5e87..03f05884b 100644 --- a/crates/sol-types/src/coder/token.rs +++ b/crates/sol-types/src/coder/token.rs @@ -328,6 +328,10 @@ impl<'de, T: TokenType<'de>> TokenType<'de> for DynSeqToken { fn decode_from(dec: &mut Decoder<'de>) -> Result { let mut child = dec.take_indirection()?; let len = child.take_u32()? as usize; + // This appears to be a bug in the solidity spec. The spec + // specifies that offsets are relative to the first word of + // `enc(X)`. But known-good test vectors do relative to the + // word AFTER the array size let mut child = child.raw_child(); (0..len) .map(|_| T::decode_from(&mut child)) From 9d714a3c10e48ef5f1b719d7a97e2f383b19cfc5 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Jul 2023 15:46:47 -0700 Subject: [PATCH 04/17] doc: annotate tail words impl --- crates/dyn-abi/src/value.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/dyn-abi/src/value.rs b/crates/dyn-abi/src/value.rs index 01cd7555e..3860ba23f 100644 --- a/crates/dyn-abi/src/value.rs +++ b/crates/dyn-abi/src/value.rs @@ -3,7 +3,7 @@ use crate::{ DynSolType, DynToken, Word, }; use alloy_primitives::{Address, I256, U256}; -use alloy_sol_types::Encoder; +use alloy_sol_types::{private::next_multiple_of_32, Encoder}; /// This type represents a Solidity value that has been decoded into rust. It /// is broadly similar to `serde_json::Value` in that it is an enum of possible @@ -466,10 +466,13 @@ impl DynSolValue { } if let Some(buf) = self.as_packed_seq() { - return 1 + (buf.len() + 31) / 32 + // 1 for the length, then the body padded to the next word. + return 1 + next_multiple_of_32(buf.len()) } if let Some(vals) = self.as_fixed_seq() { + // if static, 0. + // If dynamic, all words for all elements. return self.is_dynamic() as usize * vals.iter().map(Self::total_words).sum::() } From 4d6197a95ca5f1d3f21c0edde0f04694c97e8e98 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Jul 2023 15:50:46 -0700 Subject: [PATCH 05/17] fix: remove prints in test macro --- crates/dyn-abi/src/type.rs | 54 +++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/crates/dyn-abi/src/type.rs b/crates/dyn-abi/src/type.rs index 38bfa796a..3a2168abd 100644 --- a/crates/dyn-abi/src/type.rs +++ b/crates/dyn-abi/src/type.rs @@ -337,10 +337,6 @@ mod tests { assert_eq!(dec.total_words() * 32, $encoded.len()); let re_encoded = dec.encode_params(); - if (re_encoded != $encoded) { - println!("correct: {}", hex::encode(&$encoded)); - println!("actual: {}", hex::encode(&re_encoded)); - } assert_eq!(re_encoded, $encoded); }; } @@ -570,4 +566,54 @@ mod tests { ); encoder_test!("address[][]", encoded); } + + #[test] + fn dynamic_array_of_dynamic_arrays2() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000000000000000000000000000000000000000000040 + 00000000000000000000000000000000000000000000000000000000000000a0 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000002222222222222222222222222222222222222222 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000003333333333333333333333333333333333333333 + 0000000000000000000000004444444444444444444444444444444444444444 + " + ); + encoder_test!("address[][]", encoded); + } + + #[test] + fn fixed_array_of_fixed_arrays() { + let encoded = hex!( + " + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000002222222222222222222222222222222222222222 + 0000000000000000000000003333333333333333333333333333333333333333 + 0000000000000000000000004444444444444444444444444444444444444444 + " + ); + encoder_test!("address[2][2]", encoded); + } + + // #[test] + // fn fixed_array_of_fixed_arrays2() { + // let encoded = hex!( + // " + // 0000000000000000000000000000000000000000000000000000000005930cc5 + // 0000000000000000000000000000000000000000000000000000000015002967 + // 0000000000000000000000004444444444444444444444444444444444444444 + // 000000000000000000000000000000000000000000000000000000000000307b + // 00000000000000000000000000000000000000000000000000000000000001c3 + // 0000000000000000000000002222222222222222222222222222222222222222 + // 00000000000000000000000000000000000000000000000000000000000000e0 + // 0000000000000000000000000000000000000000000000000000000000000009 + // 6761766f66796f726b0000000000000000000000000000000000000000000000 + // " + // ); + // encoder_test!("((uint256, uint256, address)[2], string)", encoded); + // } } From fadd0145ab1f9162a704ae1d31ea3da8d2c747b9 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Jul 2023 16:20:13 -0700 Subject: [PATCH 06/17] fix: test account for dynamic tuples in test vectors --- crates/dyn-abi/src/token.rs | 6 +-- crates/dyn-abi/src/type.rs | 56 ++++++++++++++++++--------- crates/dyn-abi/src/value.rs | 19 +++++---- crates/sol-types/src/coder/encoder.rs | 2 + crates/sol-types/src/coder/token.rs | 4 +- crates/sol-types/src/lib.rs | 2 +- crates/sol-types/src/util.rs | 13 +++++-- 7 files changed, 68 insertions(+), 34 deletions(-) diff --git a/crates/dyn-abi/src/token.rs b/crates/dyn-abi/src/token.rs index 20e3d1529..56c0ca0b3 100644 --- a/crates/dyn-abi/src/token.rs +++ b/crates/dyn-abi/src/token.rs @@ -150,9 +150,9 @@ impl<'a> DynToken<'a> { Self::DynSeq { contents, template } => { let mut child = dec.take_indirection()?; let size = child.take_u32()? as usize; - // This appears to be a bug in the solidity spec. The spec - // specifies that offsets are relative to the first word of - // `enc(X)`. But known-good test vectors do relative to the + // 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(); diff --git a/crates/dyn-abi/src/type.rs b/crates/dyn-abi/src/type.rs index 3a2168abd..17ca4c2ae 100644 --- a/crates/dyn-abi/src/type.rs +++ b/crates/dyn-abi/src/type.rs @@ -335,7 +335,17 @@ mod tests { let t: DynSolType = $ty.parse().expect("parsing failed"); let dec = t.decode_params(&$encoded).expect("decoding failed"); - assert_eq!(dec.total_words() * 32, $encoded.len()); + // Tuples are treated as top-level lists. So if we encounter a + // dynamic tuple, the total length of the encoded data will include + // the offset, but the encoding/decoding process will not. To + // account for this, we add 32 bytes to the expected length when + // the type is a dynamic tuple. + if dec.as_tuple().is_some() && dec.is_dynamic() { + assert_eq!(dec.total_words() * 32, $encoded.len() + 32); + } else { + assert_eq!(dec.total_words() * 32, $encoded.len()); + } + let re_encoded = dec.encode_params(); assert_eq!(re_encoded, $encoded); }; @@ -599,21 +609,31 @@ mod tests { encoder_test!("address[2][2]", encoded); } - // #[test] - // fn fixed_array_of_fixed_arrays2() { - // let encoded = hex!( - // " - // 0000000000000000000000000000000000000000000000000000000005930cc5 - // 0000000000000000000000000000000000000000000000000000000015002967 - // 0000000000000000000000004444444444444444444444444444444444444444 - // 000000000000000000000000000000000000000000000000000000000000307b - // 00000000000000000000000000000000000000000000000000000000000001c3 - // 0000000000000000000000002222222222222222222222222222222222222222 - // 00000000000000000000000000000000000000000000000000000000000000e0 - // 0000000000000000000000000000000000000000000000000000000000000009 - // 6761766f66796f726b0000000000000000000000000000000000000000000000 - // " - // ); - // encoder_test!("((uint256, uint256, address)[2], string)", encoded); - // } + #[test] + fn fixed_array_of_fixed_arrays2() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000005930cc5 + 0000000000000000000000000000000000000000000000000000000015002967 + 0000000000000000000000004444444444444444444444444444444444444444 + 000000000000000000000000000000000000000000000000000000000000307b + 00000000000000000000000000000000000000000000000000000000000001c3 + 0000000000000000000000002222222222222222222222222222222222222222 + 00000000000000000000000000000000000000000000000000000000000000e0 + 0000000000000000000000000000000000000000000000000000000000000009 + 6761766f66796f726b0000000000000000000000000000000000000000000000 + " + ); + let val = "((uint256,uint256,address)[2],string)" + .parse::() + .unwrap() + .decode_params(&encoded) + .unwrap(); + + dbg!(&val); + dbg!(&val.total_words()); + dbg!(val.encode().unwrap().len()); + + encoder_test!("((uint256,uint256,address)[2],string)", encoded); + } } diff --git a/crates/dyn-abi/src/value.rs b/crates/dyn-abi/src/value.rs index 3860ba23f..9ca7a2a75 100644 --- a/crates/dyn-abi/src/value.rs +++ b/crates/dyn-abi/src/value.rs @@ -3,7 +3,7 @@ use crate::{ DynSolType, DynToken, Word, }; use alloy_primitives::{Address, I256, U256}; -use alloy_sol_types::{private::next_multiple_of_32, Encoder}; +use alloy_sol_types::{private::words_for, Encoder}; /// This type represents a Solidity value that has been decoded into rust. It /// is broadly similar to `serde_json::Value` in that it is an enum of possible @@ -450,7 +450,7 @@ impl DynSolValue { /// Returns the number of words this type uses in the head of the ABI blob. #[inline] - pub fn head_words(&self) -> usize { + pub(crate) fn head_words(&self) -> usize { match self.as_fixed_seq() { Some(_) if self.is_dynamic() => 1, Some(inner) => inner.iter().map(Self::head_words).sum(), @@ -460,14 +460,14 @@ impl DynSolValue { /// Returns the number of words this type uses in the tail of the ABI blob. #[inline] - pub fn tail_words(&self) -> usize { + pub(crate) fn tail_words(&self) -> usize { if self.is_word() { return 0 } if let Some(buf) = self.as_packed_seq() { // 1 for the length, then the body padded to the next word. - return 1 + next_multiple_of_32(buf.len()) + return 1 + words_for(buf) } if let Some(vals) = self.as_fixed_seq() { @@ -484,10 +484,15 @@ impl DynSolValue { unreachable!() } - /// Returns the total number of words this type uses in the ABI blob. + /// Returns the total number of words this type uses in the ABI blob, + /// assuming it is not the top-level #[inline] - pub fn total_words(&self) -> usize { - self.head_words() + self.tail_words() + pub(crate) fn total_words(&self) -> usize { + let h = self.head_words(); + let t = self.tail_words(); + println!("{}, {} {}", self.sol_type_name().unwrap(), h, t); + h + t + // self.head_words() + self.tail_words() } /// Append this data to the head of an in-progress blob via the encoder. diff --git a/crates/sol-types/src/coder/encoder.rs b/crates/sol-types/src/coder/encoder.rs index 82c6d8130..cb2b054ab 100644 --- a/crates/sol-types/src/coder/encoder.rs +++ b/crates/sol-types/src/coder/encoder.rs @@ -425,6 +425,8 @@ mod tests { ) .to_vec(); + dbg!(MyTy::sol_type_name()); + let encoded_params = MyTy::encode_params(&data); assert_eq!(encoded_params, expected); assert_eq!(encoded_params.len(), MyTy::encoded_size(&data)); diff --git a/crates/sol-types/src/coder/token.rs b/crates/sol-types/src/coder/token.rs index 03f05884b..28ba9bb1c 100644 --- a/crates/sol-types/src/coder/token.rs +++ b/crates/sol-types/src/coder/token.rs @@ -328,9 +328,9 @@ impl<'de, T: TokenType<'de>> TokenType<'de> for DynSeqToken { fn decode_from(dec: &mut Decoder<'de>) -> Result { let mut child = dec.take_indirection()?; let len = child.take_u32()? as usize; - // This appears to be a bug in the solidity spec. The spec + // This appears to be an unclarity in the solidity spec. The spec // specifies that offsets are relative to the first word of - // `enc(X)`. But known-good test vectors do relative to the + // `enc(X)`. But known-good test vectors ha vrelative to the // word AFTER the array size let mut child = child.raw_child(); (0..len) diff --git a/crates/sol-types/src/lib.rs b/crates/sol-types/src/lib.rs index 21da5cc74..bc0eeb5f5 100644 --- a/crates/sol-types/src/lib.rs +++ b/crates/sol-types/src/lib.rs @@ -200,7 +200,7 @@ pub use alloy_sol_macro::sol; // Not public API. #[doc(hidden)] pub mod private { - pub use super::util::{just_ok, next_multiple_of_32}; + pub use super::util::{just_ok, next_multiple_of_32, words_for, words_for_len}; pub use alloc::{ borrow::{Borrow, Cow, ToOwned}, string::{String, ToString}, diff --git a/crates/sol-types/src/util.rs b/crates/sol-types/src/util.rs index f35c8f792..750064172 100644 --- a/crates/sol-types/src/util.rs +++ b/crates/sol-types/src/util.rs @@ -12,10 +12,17 @@ use crate::{Error, Result, Word}; /// Calculates the padded length of a slice by rounding its length to the next -/// word +/// word. #[inline] -pub(crate) const fn words_for(data: &[u8]) -> usize { - (data.len() + 31) / 32 +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] +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 From 8e972832243f669f45d6859aebca1d8707ce5c3f Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Jul 2023 16:23:43 -0700 Subject: [PATCH 07/17] fix: remove dbgs and prints again lol --- crates/dyn-abi/src/type.rs | 30 +++++++++++++++++++++++++----- crates/dyn-abi/src/value.rs | 6 +----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/crates/dyn-abi/src/type.rs b/crates/dyn-abi/src/type.rs index 17ca4c2ae..987ea313d 100644 --- a/crates/dyn-abi/src/type.rs +++ b/crates/dyn-abi/src/type.rs @@ -610,7 +610,7 @@ mod tests { } #[test] - fn fixed_array_of_fixed_arrays2() { + fn fixed_array_of_static_tuples_followed_by_dynamic_type() { let encoded = hex!( " 0000000000000000000000000000000000000000000000000000000005930cc5 @@ -630,10 +630,30 @@ mod tests { .decode_params(&encoded) .unwrap(); - dbg!(&val); - dbg!(&val.total_words()); - dbg!(val.encode().unwrap().len()); - encoder_test!("((uint256,uint256,address)[2],string)", encoded); } + + #[test] + fn empty_array() { + let expected = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000000 + " + ); + encoder_test!("address[]", expected); + } + + #[test] + fn empty_array_2() { + let expected = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000060 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + " + ); + encoder_test!("address[][]", expected); + } } diff --git a/crates/dyn-abi/src/value.rs b/crates/dyn-abi/src/value.rs index 9ca7a2a75..431629aba 100644 --- a/crates/dyn-abi/src/value.rs +++ b/crates/dyn-abi/src/value.rs @@ -488,11 +488,7 @@ impl DynSolValue { /// assuming it is not the top-level #[inline] pub(crate) fn total_words(&self) -> usize { - let h = self.head_words(); - let t = self.tail_words(); - println!("{}, {} {}", self.sol_type_name().unwrap(), h, t); - h + t - // self.head_words() + self.tail_words() + self.head_words() + self.tail_words() } /// Append this data to the head of an in-progress blob via the encoder. From 840b918c67494a17426b5442672e3e7770c493a8 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Jul 2023 16:33:10 -0700 Subject: [PATCH 08/17] tests: more of em --- crates/dyn-abi/src/type.rs | 107 +++++++++++++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/crates/dyn-abi/src/type.rs b/crates/dyn-abi/src/type.rs index 987ea313d..3c2f1d61c 100644 --- a/crates/dyn-abi/src/type.rs +++ b/crates/dyn-abi/src/type.rs @@ -624,29 +624,24 @@ mod tests { 6761766f66796f726b0000000000000000000000000000000000000000000000 " ); - let val = "((uint256,uint256,address)[2],string)" - .parse::() - .unwrap() - .decode_params(&encoded) - .unwrap(); encoder_test!("((uint256,uint256,address)[2],string)", encoded); } #[test] fn empty_array() { - let expected = hex!( + let encoded = hex!( " 0000000000000000000000000000000000000000000000000000000000000020 0000000000000000000000000000000000000000000000000000000000000000 " ); - encoder_test!("address[]", expected); + encoder_test!("address[]", encoded); } #[test] fn empty_array_2() { - let expected = hex!( + let encoded = hex!( " 0000000000000000000000000000000000000000000000000000000000000040 0000000000000000000000000000000000000000000000000000000000000060 @@ -654,6 +649,100 @@ mod tests { 0000000000000000000000000000000000000000000000000000000000000000 " ); - encoder_test!("address[][]", expected); + encoder_test!("(address[],address[])", encoded); + } + + #[test] + fn empty_array_3() { + // Nested empty arrays + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000040 + 00000000000000000000000000000000000000000000000000000000000000a0 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000000 + " + ); + encoder_test!("(address[][], address[][])", encoded); + } + + #[test] + fn fixed_bytes() { + let encoded = hex!("1234000000000000000000000000000000000000000000000000000000000000"); + encoder_test!("bytes2", encoded); + } + + #[test] + fn string() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000009 + 6761766f66796f726b0000000000000000000000000000000000000000000000 + " + ); + encoder_test!("string", encoded); + } + + #[test] + fn bytes() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000002 + 1234000000000000000000000000000000000000000000000000000000000000 + " + ); + encoder_test!("bytes", encoded); + } + + #[test] + fn bytes_2() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 000000000000000000000000000000000000000000000000000000000000001f + 1000000000000000000000000000000000000000000000000000000000000200 + " + ); + encoder_test!("bytes", encoded); + } + + #[test] + fn bytes_3() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000040 + 1000000000000000000000000000000000000000000000000000000000000000 + 1000000000000000000000000000000000000000000000000000000000000000 + " + ); + encoder_test!("bytes", encoded); + } + + #[test] + fn two_bytes() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000080 + 000000000000000000000000000000000000000000000000000000000000001f + 1000000000000000000000000000000000000000000000000000000000000200 + 0000000000000000000000000000000000000000000000000000000000000020 + 0010000000000000000000000000000000000000000000000000000000000002 + " + ); + encoder_test!("(bytes,bytes)", encoded); + } + + #[test] + fn uint() { + let encoded = hex!("0000000000000000000000000000000000000000000000000000000000000004"); + encoder_test!("uint", encoded); } } From ebae401c3201164ee8021c733cf930293702aecc Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Jul 2023 16:34:14 -0700 Subject: [PATCH 09/17] fix: remove a dbg --- crates/sol-types/src/coder/encoder.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/sol-types/src/coder/encoder.rs b/crates/sol-types/src/coder/encoder.rs index cb2b054ab..82c6d8130 100644 --- a/crates/sol-types/src/coder/encoder.rs +++ b/crates/sol-types/src/coder/encoder.rs @@ -425,8 +425,6 @@ mod tests { ) .to_vec(); - dbg!(MyTy::sol_type_name()); - let encoded_params = MyTy::encode_params(&data); assert_eq!(encoded_params, expected); assert_eq!(encoded_params.len(), MyTy::encoded_size(&data)); From 6cf53e8739ed698b543d8c6cf5d9e94b6b2c054b Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Jul 2023 16:57:18 -0700 Subject: [PATCH 10/17] tests: remainder of encoder test --- crates/dyn-abi/src/type.rs | 242 +++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) diff --git a/crates/dyn-abi/src/type.rs b/crates/dyn-abi/src/type.rs index 3c2f1d61c..0fc5fe904 100644 --- a/crates/dyn-abi/src/type.rs +++ b/crates/dyn-abi/src/type.rs @@ -745,4 +745,246 @@ mod tests { let encoded = hex!("0000000000000000000000000000000000000000000000000000000000000004"); encoder_test!("uint", encoded); } + + #[test] + fn int() { + let encoded = hex!("0000000000000000000000000000000000000000000000000000000000000004"); + encoder_test!("int", encoded); + } + + #[test] + fn bool() { + let encoded = hex!("0000000000000000000000000000000000000000000000000000000000000001"); + encoder_test!("bool", encoded); + } + + #[test] + fn bool2() { + let encoded = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + encoder_test!("bool", encoded); + } + + #[test] + fn comprehensive_test() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000005 + 0000000000000000000000000000000000000000000000000000000000000080 + 0000000000000000000000000000000000000000000000000000000000000003 + 00000000000000000000000000000000000000000000000000000000000000e0 + 0000000000000000000000000000000000000000000000000000000000000040 + 131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b + 131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b + 0000000000000000000000000000000000000000000000000000000000000040 + 131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b + 131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b + " + ); + encoder_test!("(uint8,bytes,uint8,bytes)", encoded); + } + + #[test] + fn comprehensive_test2() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000001 + 00000000000000000000000000000000000000000000000000000000000000c0 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000000000000000000000000000000000000000000003 + 0000000000000000000000000000000000000000000000000000000000000004 + 0000000000000000000000000000000000000000000000000000000000000100 + 0000000000000000000000000000000000000000000000000000000000000009 + 6761766f66796f726b0000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000003 + 0000000000000000000000000000000000000000000000000000000000000005 + 0000000000000000000000000000000000000000000000000000000000000006 + 0000000000000000000000000000000000000000000000000000000000000007 + " + ); + encoder_test!("(bool,string,uint8,uint8,uint8,uint8[])", encoded); + } + + #[test] + fn dynamic_array_of_bytes() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000026 + 019c80031b20d5e69c8093a571162299032018d913930d93ab320ae5ea44a421 + 8a274f00d6070000000000000000000000000000000000000000000000000000 + " + ); + encoder_test!("bytes[]", encoded); + } + + #[test] + fn dynamic_array_of_bytes2() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000000000000000000000000000000000000000000040 + 00000000000000000000000000000000000000000000000000000000000000a0 + 0000000000000000000000000000000000000000000000000000000000000026 + 4444444444444444444444444444444444444444444444444444444444444444 + 4444444444440000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000026 + 6666666666666666666666666666666666666666666666666666666666666666 + 6666666666660000000000000000000000000000000000000000000000000000 + " + ); + encoder_test!("bytes[]", encoded); + } + + #[test] + fn static_tuple_of_addresses() { + let encoded = hex!( + " + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000002222222222222222222222222222222222222222 + " + ); + encoder_test!("(address,address)", encoded); + } + + #[test] + fn dynamic_tuple() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000080 + 0000000000000000000000000000000000000000000000000000000000000009 + 6761766f66796f726b0000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000009 + 6761766f66796f726b0000000000000000000000000000000000000000000000 + " + ); + encoder_test!("(string,string)", encoded); + } + + #[test] + fn dynamic_tuple_of_bytes() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000040 + 00000000000000000000000000000000000000000000000000000000000000a0 + 0000000000000000000000000000000000000000000000000000000000000026 + 4444444444444444444444444444444444444444444444444444444444444444 + 4444444444440000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000026 + 6666666666666666666666666666666666666666666666666666666666666666 + 6666666666660000000000000000000000000000000000000000000000000000 + " + ); + encoder_test!("(bytes,bytes)", encoded); + } + + #[test] + fn complex_tuple() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 1111111111111111111111111111111111111111111111111111111111111111 + 0000000000000000000000000000000000000000000000000000000000000080 + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000002222222222222222222222222222222222222222 + 0000000000000000000000000000000000000000000000000000000000000009 + 6761766f66796f726b0000000000000000000000000000000000000000000000 + " + ); + encoder_test!("(uint256,string,address,address)", encoded); + } + + #[test] + fn nested_tuple() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000080 + 0000000000000000000000000000000000000000000000000000000000000001 + 00000000000000000000000000000000000000000000000000000000000000c0 + 0000000000000000000000000000000000000000000000000000000000000100 + 0000000000000000000000000000000000000000000000000000000000000004 + 7465737400000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000006 + 6379626f72670000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000060 + 00000000000000000000000000000000000000000000000000000000000000a0 + 00000000000000000000000000000000000000000000000000000000000000e0 + 0000000000000000000000000000000000000000000000000000000000000005 + 6e69676874000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000003 + 6461790000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000080 + 0000000000000000000000000000000000000000000000000000000000000004 + 7765656500000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000008 + 66756e7465737473000000000000000000000000000000000000000000000000 + " + ); + encoder_test!( + "(string,bool,string,(string,string,(string,string)))", + encoded + ); + } + + #[test] + fn params_containing_dynamic_tuple() { + let encoded = hex!( + " + 0000000000000000000000002222222222222222222222222222222222222222 + 00000000000000000000000000000000000000000000000000000000000000a0 + 0000000000000000000000003333333333333333333333333333333333333333 + 0000000000000000000000004444444444444444444444444444444444444444 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000060 + 00000000000000000000000000000000000000000000000000000000000000a0 + 0000000000000000000000000000000000000000000000000000000000000009 + 7370616365736869700000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000006 + 6379626f72670000000000000000000000000000000000000000000000000000 + " + ); + encoder_test!( + "(address,(bool,string,string),address,address,bool)", + encoded + ); + } + + #[test] + fn params_containing_static_tuple() { + let encoded = hex!( + " + 0000000000000000000000001111111111111111111111111111111111111111 + 0000000000000000000000002222222222222222222222222222222222222222 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000003333333333333333333333333333333333333333 + 0000000000000000000000004444444444444444444444444444444444444444 + " + ); + encoder_test!("(address,(address,bool,bool),address,address", encoded); + } + + #[test] + fn dynamic_tuple_with_nested_static_tuples() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000777 + 0000000000000000000000000000000000000000000000000000000000000060 + 0000000000000000000000000000000000000000000000000000000000000002 + 0000000000000000000000000000000000000000000000000000000000000042 + 0000000000000000000000000000000000000000000000000000000000001337 + " + ); + encoder_test!("(((bool,uint16),), uint16[])", encoded); + } } From 611283d1bc0e4b09a4ed94b2c42287b5a9dfab0d Mon Sep 17 00:00:00 2001 From: James Date: Mon, 3 Jul 2023 08:42:10 -0700 Subject: [PATCH 11/17] fix: dyn type parsing for dynamic tuples --- crates/dyn-abi/src/parser.rs | 51 +++++++++++++++++++++++++----------- crates/dyn-abi/src/type.rs | 12 ++++----- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/crates/dyn-abi/src/parser.rs b/crates/dyn-abi/src/parser.rs index dc247c5ea..c5dfae678 100644 --- a/crates/dyn-abi/src/parser.rs +++ b/crates/dyn-abi/src/parser.rs @@ -188,6 +188,7 @@ impl<'a> TryFrom<&'a str> for TupleSpecifier<'a> { let value = value.strip_prefix("tuple").unwrap_or(value); let value = value.strip_prefix('(').unwrap_or(value); + // passes over nested tuples let mut types = vec![]; let mut start = 0; let mut depth = 0; @@ -202,7 +203,11 @@ impl<'a> TryFrom<&'a str> for TupleSpecifier<'a> { _ => {} } } - types.push(value[start..].try_into()?); + // handle termina commas in tuples + let candidate = value[start..].trim(); + if !candidate.is_empty() { + types.push(candidate.try_into()?); + } Ok(Self { span: value, types }) } } @@ -348,8 +353,31 @@ impl core::str::FromStr for DynSolType { #[cfg(test)] mod tests { use super::*; + #[test] - fn it_parses_solidity_types() { + fn it_parses_tuples() { + assert_eq!( + parse("(bool,)").unwrap(), + DynSolType::Tuple(vec![DynSolType::Bool]) + ); + assert_eq!( + parse("(uint256,uint256)").unwrap(), + DynSolType::Tuple(vec![DynSolType::Uint(256), DynSolType::Uint(256)]) + ); + assert_eq!( + parse("(uint256,uint256)[2]").unwrap(), + DynSolType::FixedArray( + Box::new(DynSolType::Tuple(vec![ + DynSolType::Uint(256), + DynSolType::Uint(256) + ])), + 2 + ) + ); + } + + #[test] + fn it_parses_simple_types() { assert_eq!(parse("uint256").unwrap(), DynSolType::Uint(256)); assert_eq!(parse("uint8").unwrap(), DynSolType::Uint(8)); assert_eq!(parse("uint").unwrap(), DynSolType::Uint(256)); @@ -358,6 +386,10 @@ mod tests { assert_eq!(parse("string").unwrap(), DynSolType::String); assert_eq!(parse("bytes").unwrap(), DynSolType::Bytes); assert_eq!(parse("bytes32").unwrap(), DynSolType::FixedBytes(32)); + } + + #[test] + fn it_parses_complex_solidity_types() { assert_eq!( parse("uint256[]").unwrap(), DynSolType::Array(Box::new(DynSolType::Uint(256))) @@ -379,20 +411,7 @@ mod tests { Box::new(DynSolType::Uint(256)) ))))) ); - assert_eq!( - parse("(uint256,uint256)").unwrap(), - DynSolType::Tuple(vec![DynSolType::Uint(256), DynSolType::Uint(256)]) - ); - assert_eq!( - parse("(uint256,uint256)[2]").unwrap(), - DynSolType::FixedArray( - Box::new(DynSolType::Tuple(vec![ - DynSolType::Uint(256), - DynSolType::Uint(256) - ])), - 2 - ) - ); + assert_eq!( parse(r#"tuple(address,bytes, (bool, (string, uint256)[][3]))[2]"#), Ok(DynSolType::FixedArray( diff --git a/crates/dyn-abi/src/type.rs b/crates/dyn-abi/src/type.rs index 0fc5fe904..5f4b4e350 100644 --- a/crates/dyn-abi/src/type.rs +++ b/crates/dyn-abi/src/type.rs @@ -862,7 +862,7 @@ mod tests { 6761766f66796f726b0000000000000000000000000000000000000000000000 " ); - encoder_test!("(string,string)", encoded); + encoder_test!("((string,string),)", encoded); } #[test] @@ -880,7 +880,7 @@ mod tests { 6666666666660000000000000000000000000000000000000000000000000000 " ); - encoder_test!("(bytes,bytes)", encoded); + encoder_test!("((bytes,bytes),)", encoded); } #[test] @@ -896,7 +896,7 @@ mod tests { 6761766f66796f726b0000000000000000000000000000000000000000000000 " ); - encoder_test!("(uint256,string,address,address)", encoded); + encoder_test!("((uint256,string,address,address),)", encoded); } #[test] @@ -928,7 +928,7 @@ mod tests { " ); encoder_test!( - "(string,bool,string,(string,string,(string,string)))", + "((string,bool,string,(string,string,(string,string))),)", encoded ); } @@ -969,7 +969,7 @@ mod tests { 0000000000000000000000004444444444444444444444444444444444444444 " ); - encoder_test!("(address,(address,bool,bool),address,address", encoded); + encoder_test!("(address,(address,bool,bool),address,address)", encoded); } #[test] @@ -985,6 +985,6 @@ mod tests { 0000000000000000000000000000000000000000000000000000000000001337 " ); - encoder_test!("(((bool,uint16),), uint16[])", encoded); + encoder_test!("((((bool,uint16),), uint16[]),)", encoded); } } From e8db253047a95473e6d14014f88ba6cf4736989b Mon Sep 17 00:00:00 2001 From: James Date: Mon, 3 Jul 2023 08:47:26 -0700 Subject: [PATCH 12/17] opt: faster display impl --- crates/sol-types/src/coder/decoder.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/sol-types/src/coder/decoder.rs b/crates/sol-types/src/coder/decoder.rs index 213ff4ff6..236fd7f0c 100644 --- a/crates/sol-types/src/coder/decoder.rs +++ b/crates/sol-types/src/coder/decoder.rs @@ -48,15 +48,22 @@ impl fmt::Debug for Decoder<'_> { impl fmt::Display for Decoder<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut body = self - .buf - .chunks(32) - .enumerate() - .map(|(i, chunk)| format!("0x{:0>4x}: {}", i * 32, hex::encode_prefixed(chunk))) - .collect::>(); - body[self.offset / 32].push_str(" <-- Next Word"); writeln!(f, "\nAbi Decode Buffer")?; - writeln!(f, "{}", body.join("\n")) + + for (i, chunk) in self.buf.chunks(32).enumerate() { + writeln!( + f, + "0x{:04x}: {} {}", + i * 32, + hex::encode_prefixed(chunk), + if i * 32 == self.offset { + " <-- Next Word" + } else { + "" + } + )?; + } + Ok(()) } } From f8a9c0e30bb7dbf3e2c8554a698c6c0f6413ddeb Mon Sep 17 00:00:00 2001 From: James Date: Mon, 3 Jul 2023 08:47:37 -0700 Subject: [PATCH 13/17] nit: newline in display --- crates/sol-types/src/coder/decoder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sol-types/src/coder/decoder.rs b/crates/sol-types/src/coder/decoder.rs index 236fd7f0c..f2742f8ae 100644 --- a/crates/sol-types/src/coder/decoder.rs +++ b/crates/sol-types/src/coder/decoder.rs @@ -48,7 +48,7 @@ impl fmt::Debug for Decoder<'_> { impl fmt::Display for Decoder<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "\nAbi Decode Buffer")?; + writeln!(f, "Abi Decode Buffer")?; for (i, chunk) in self.buf.chunks(32).enumerate() { writeln!( From 8f9e9e00740c6dd8322b56e1a6313f59d100b4a8 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 3 Jul 2023 11:47:02 -0700 Subject: [PATCH 14/17] fix: handle mismatches paren stripping during tuple parsing --- crates/dyn-abi/src/parser.rs | 54 ++++++++++++++++++++++++++++++++---- crates/dyn-abi/src/token.rs | 9 ++++-- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/crates/dyn-abi/src/parser.rs b/crates/dyn-abi/src/parser.rs index c5dfae678..e5a9e6c77 100644 --- a/crates/dyn-abi/src/parser.rs +++ b/crates/dyn-abi/src/parser.rs @@ -184,18 +184,36 @@ impl<'a> TryFrom<&'a str> for TupleSpecifier<'a> { // flexible for `a, b` or `(a, b)`, or `tuple(a, b)` // or any missing parenthesis let value = value.trim(); - let value = value.strip_suffix(')').unwrap_or(value); - let value = value.strip_prefix("tuple").unwrap_or(value); - let value = value.strip_prefix('(').unwrap_or(value); + + // if we strip a trailing paren we MUST strip a leading paren + let value = if let Some(val) = value.strip_suffix(')') { + val.strip_prefix("tuple") + .unwrap_or(val) + .strip_prefix('(') + .ok_or_else(|| DynAbiError::invalid_type_string(value))? + } else { + value + }; + + let value = value + .strip_suffix(')') + .and_then(|val| val.strip_prefix('(')) + .and_then(|val| val.strip_prefix("tuple")) + .unwrap_or(value); // passes over nested tuples - let mut types = vec![]; + let mut types: Vec> = vec![]; let mut start = 0; - let mut depth = 0; + let mut depth: usize = 0; for (i, c) in value.char_indices() { match c { '(' => depth += 1, - ')' => depth -= 1, + ')' => { + // handle extra closing paren + depth = depth + .checked_sub(1) + .ok_or_else(|| DynAbiError::invalid_type_string(value))?; + } ',' if depth == 0 => { types.push(value[start..i].try_into()?); start = i + 1; @@ -203,6 +221,12 @@ impl<'a> TryFrom<&'a str> for TupleSpecifier<'a> { _ => {} } } + + // handle extra open paren + if depth != 0 { + return Err(DynAbiError::invalid_type_string(value)) + } + // handle termina commas in tuples let candidate = value[start..].trim(); if !candidate.is_empty() { @@ -354,6 +378,24 @@ impl core::str::FromStr for DynSolType { mod tests { use super::*; + #[test] + fn extra_close_parens() { + let test_str = "bool,uint256))"; + assert_eq!( + parse(test_str), + Err(DynAbiError::invalid_type_string(test_str)) + ); + } + + #[test] + fn extra_open_parents() { + let test_str = "(bool,uint256"; + assert_eq!( + parse(test_str), + Err(DynAbiError::invalid_type_string(test_str)) + ); + } + #[test] fn it_parses_tuples() { assert_eq!( diff --git a/crates/dyn-abi/src/token.rs b/crates/dyn-abi/src/token.rs index 56c0ca0b3..af00ab26e 100644 --- a/crates/dyn-abi/src/token.rs +++ b/crates/dyn-abi/src/token.rs @@ -130,7 +130,7 @@ impl<'a> DynToken<'a> { /// Decodes from a decoder, populating the structure with the decoded data. #[inline] - pub fn decode_populate(&mut self, dec: &mut Decoder<'a>) -> Result<()> { + 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, @@ -157,7 +157,12 @@ impl<'a> DynToken<'a> { let mut child = child.raw_child(); let mut new_tokens: Vec<_> = Vec::with_capacity(size); - new_tokens.resize(size, *(template.take().unwrap())); + // 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 an alloy bug. Please report it."); + new_tokens.resize(size, *t); new_tokens .iter_mut() From f53964dea2496304bec21126ec0e37e91a8ba62a Mon Sep 17 00:00:00 2001 From: James Date: Mon, 3 Jul 2023 11:48:20 -0700 Subject: [PATCH 15/17] nit: comment correction --- crates/dyn-abi/src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/dyn-abi/src/parser.rs b/crates/dyn-abi/src/parser.rs index e5a9e6c77..ffa7fb737 100644 --- a/crates/dyn-abi/src/parser.rs +++ b/crates/dyn-abi/src/parser.rs @@ -227,7 +227,7 @@ impl<'a> TryFrom<&'a str> for TupleSpecifier<'a> { return Err(DynAbiError::invalid_type_string(value)) } - // handle termina commas in tuples + // handle trailing commas in tuples let candidate = value[start..].trim(); if !candidate.is_empty() { types.push(candidate.try_into()?); From b5a0b4a55d951dde12718a05164ccee1503535da Mon Sep 17 00:00:00 2001 From: James Date: Mon, 3 Jul 2023 12:27:05 -0700 Subject: [PATCH 16/17] fix: remove leftover code --- crates/dyn-abi/src/parser.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/dyn-abi/src/parser.rs b/crates/dyn-abi/src/parser.rs index ffa7fb737..721d3ac01 100644 --- a/crates/dyn-abi/src/parser.rs +++ b/crates/dyn-abi/src/parser.rs @@ -195,12 +195,6 @@ impl<'a> TryFrom<&'a str> for TupleSpecifier<'a> { value }; - let value = value - .strip_suffix(')') - .and_then(|val| val.strip_prefix('(')) - .and_then(|val| val.strip_prefix("tuple")) - .unwrap_or(value); - // passes over nested tuples let mut types: Vec> = vec![]; let mut start = 0; From 5ebbba930d31299969d2d8f2a345be14432b17ed Mon Sep 17 00:00:00 2001 From: James Date: Mon, 3 Jul 2023 13:17:36 -0700 Subject: [PATCH 17/17] tests: more of 'em for tuples and stuff --- crates/dyn-abi/src/parser.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/dyn-abi/src/parser.rs b/crates/dyn-abi/src/parser.rs index 721d3ac01..ecd7fb70e 100644 --- a/crates/dyn-abi/src/parser.rs +++ b/crates/dyn-abi/src/parser.rs @@ -412,6 +412,38 @@ mod tests { ); } + #[test] + fn nested_tuples() { + assert_eq!( + parse("(bool,(uint256,uint256))").unwrap(), + DynSolType::Tuple(vec![ + DynSolType::Bool, + DynSolType::Tuple(vec![DynSolType::Uint(256), DynSolType::Uint(256)]) + ]) + ); + assert_eq!( + parse("(((bool),),)").unwrap(), + DynSolType::Tuple(vec![DynSolType::Tuple(vec![DynSolType::Tuple(vec![ + DynSolType::Bool + ])])]) + ); + } + + #[test] + fn empty_tuples() { + assert_eq!(parse("()").unwrap(), DynSolType::Tuple(vec![])); + assert_eq!( + parse("((),())").unwrap(), + DynSolType::Tuple(vec![DynSolType::Tuple(vec![]), DynSolType::Tuple(vec![])]) + ); + assert_eq!( + parse("((()))"), + Ok(DynSolType::Tuple(vec![DynSolType::Tuple(vec![ + DynSolType::Tuple(vec![]) + ])])) + ); + } + #[test] fn it_parses_simple_types() { assert_eq!(parse("uint256").unwrap(), DynSolType::Uint(256));