diff --git a/crates/dyn-abi/src/parser.rs b/crates/dyn-abi/src/parser.rs index dc247c5ea9..ecd7fb70e0 100644 --- a/crates/dyn-abi/src/parser.rs +++ b/crates/dyn-abi/src/parser.rs @@ -184,17 +184,30 @@ 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); - let mut types = vec![]; + // 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 + }; + + // passes over nested tuples + 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; @@ -202,7 +215,17 @@ impl<'a> TryFrom<&'a str> for TupleSpecifier<'a> { _ => {} } } - types.push(value[start..].try_into()?); + + // handle extra open paren + if depth != 0 { + return Err(DynAbiError::invalid_type_string(value)) + } + + // handle trailing commas in tuples + let candidate = value[start..].trim(); + if !candidate.is_empty() { + types.push(candidate.try_into()?); + } Ok(Self { span: value, types }) } } @@ -348,8 +371,81 @@ impl core::str::FromStr for DynSolType { #[cfg(test)] 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_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 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)); assert_eq!(parse("uint8").unwrap(), DynSolType::Uint(8)); assert_eq!(parse("uint").unwrap(), DynSolType::Uint(256)); @@ -358,6 +454,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 +479,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/token.rs b/crates/dyn-abi/src/token.rs index e8a7ff00f7..af00ab26e5 100644 --- a/crates/dyn-abi/src/token.rs +++ b/crates/dyn-abi/src/token.rs @@ -130,33 +130,44 @@ 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, - 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); - } + // 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 an alloy bug. Please report it."); + new_tokens.resize(size, *t); + + 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 +180,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 78e439d81c..5f4b4e350f 100644 --- a/crates/dyn-abi/src/type.rs +++ b/crates/dyn-abi/src/type.rs @@ -328,6 +328,29 @@ 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"); + + // 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); + }; + } + #[test] fn dynamically_encodes() { let word1 = "0000000000000000000000000101010101010101010101010101010101010101" @@ -462,4 +485,506 @@ 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); + } + + #[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_static_tuples_followed_by_dynamic_type() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000005930cc5 + 0000000000000000000000000000000000000000000000000000000015002967 + 0000000000000000000000004444444444444444444444444444444444444444 + 000000000000000000000000000000000000000000000000000000000000307b + 00000000000000000000000000000000000000000000000000000000000001c3 + 0000000000000000000000002222222222222222222222222222222222222222 + 00000000000000000000000000000000000000000000000000000000000000e0 + 0000000000000000000000000000000000000000000000000000000000000009 + 6761766f66796f726b0000000000000000000000000000000000000000000000 + " + ); + + encoder_test!("((uint256,uint256,address)[2],string)", encoded); + } + + #[test] + fn empty_array() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000000 + " + ); + encoder_test!("address[]", encoded); + } + + #[test] + fn empty_array_2() { + let encoded = hex!( + " + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000060 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + " + ); + 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); + } + + #[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); + } } diff --git a/crates/dyn-abi/src/value.rs b/crates/dyn-abi/src/value.rs index 997c21c2b8..431629aba1 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::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,26 +460,37 @@ 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() { - return 1 + (buf.len() + 31) / 32 + // 1 for the length, then the body padded to the next word. + return 1 + words_for(buf) } if let Some(vals) = self.as_fixed_seq() { - return self.is_dynamic() as usize * vals.len() + // if static, 0. + // If dynamic, all words for all elements. + 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, + /// assuming it is not the top-level + #[inline] + pub(crate) 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 e03a4821d0..f2742f8aea 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}; @@ -31,14 +31,42 @@ 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 { + writeln!(f, "Abi Decode Buffer")?; + + 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(()) + } +} + impl<'de> Decoder<'de> { /// Instantiate a new decoder from a byte slice and a validation flag. /// @@ -243,6 +271,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 2581a29022..28ba9bb1c8 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,11 @@ 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 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 ha vrelative to the + // word AFTER the array size + let mut child = child.raw_child(); (0..len) .map(|_| T::decode_from(&mut child)) .collect::>>() @@ -361,13 +365,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(); } diff --git a/crates/sol-types/src/lib.rs b/crates/sol-types/src/lib.rs index 21da5cc740..bc0eeb5f5a 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 f35c8f792b..7500641722 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