From 0ad05a7831391deffedcbb401342eb6a1d9a5d17 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 22 Mar 2023 10:24:01 -0700 Subject: [PATCH 1/9] feature: dynamic abi encoding --- abi/src/dyn_sol_type.rs | 538 ++++++++++++++++++++++++++++++++++++++++ abi/src/errors.rs | 12 + abi/src/lib.rs | 3 + abi/src/token.rs | 10 +- 4 files changed, 558 insertions(+), 5 deletions(-) create mode 100644 abi/src/dyn_sol_type.rs diff --git a/abi/src/dyn_sol_type.rs b/abi/src/dyn_sol_type.rs new file mode 100644 index 000000000..3342c94e7 --- /dev/null +++ b/abi/src/dyn_sol_type.rs @@ -0,0 +1,538 @@ +use core::{ops::Deref, str::FromStr}; + +use ethers_primitives::{B160, U256}; + +use crate::{ + decoder::Decoder, encoder::Encoder, sol_type, AbiResult, Error, FixedSeqToken, PackedSeqToken, + SolType, TokenType, Word, WordToken, +}; + +pub struct Parenthesized<'a> { + inner: &'a str, +} + +impl<'a> FromStr for Parenthesized<'a> { + type Err = (); + + fn from_str(s: &'a str) -> Result, Self::Err> { + s.split_once('(') + .and_then(|(_, inner)| inner.rsplit_once(')')) + .map(|(inner, _)| Self { inner }) + .ok_or(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +// Wraps all implementers of sol_type::SolType +pub enum DynSolType { + Address, + Bool, + Bytes, + FixedBytes(usize), + Int(usize), + Uint(usize), + Function, + String, + Tuple(Vec), + Array(Box), + FixedArray(Box, usize), + CustomStruct { + name: String, + tuple: Vec, + }, + CustomValue { + name: String, + inner: Box, + }, +} + +impl FromStr for DynSolType { + type Err = crate::Error; + + fn from_str(s: &str) -> AbiResult { + match s { + "address" => Ok(Self::Address), + "bool" => Ok(Self::Bool), + "bytes" => Ok(Self::Bytes), + "function" => Ok(Self::Function), + "string" => Ok(Self::String), + _ => todo!(), + } + } +} + +impl DynSolType { + /// Dynamic tokenization + pub fn tokenize(&self, value: SolValue) -> AbiResult { + match (self, value) { + (DynSolType::Address, SolValue::Address(val)) => { + Ok(DynToken::Word(sol_type::Address::tokenize(val).inner())) + } + (DynSolType::Bool, SolValue::Bool(val)) => { + Ok(DynToken::Word(sol_type::Bool::tokenize(val).inner())) + } + (DynSolType::Bytes, SolValue::Bytes(val)) => Ok(DynToken::PackedSeq(val)), + (DynSolType::FixedBytes(len), SolValue::FixedBytes(word, size)) => { + if size != *len { + return Err(crate::Error::custom_owned(format!( + "Size mismatch for FixedBytes. Got {}, expected {}", + size, len + ))); + } + Ok(word.into()) + } + (DynSolType::Int(_), SolValue::Int(word, _)) => Ok(word.into()), + (DynSolType::Uint(_), SolValue::Uint(num, _)) => Ok(DynToken::Word(num.into())), + (DynSolType::Function, SolValue::Function(word)) => { + Ok(DynToken::Word(sol_type::Function::tokenize(word).inner())) + } + (DynSolType::String, SolValue::String(buf)) => Ok(DynToken::PackedSeq(buf.into())), + (DynSolType::Tuple(types), SolValue::Tuple(tokens)) => { + let tokens = types + .iter() + .zip(tokens.into_iter()) + .map(|(ty, token)| ty.tokenize(token)) + .collect::>()?; + + Ok(DynToken::FixedSeq(tokens, types.len())) + } + (DynSolType::Array(t), SolValue::Array(values)) => { + let contents: Vec = values + .into_iter() + .map(|val| t.tokenize(val)) + .collect::>()?; + let template = Box::new(contents.first().unwrap().clone()); + Ok(DynToken::DynSeq { contents, template }) + } + (DynSolType::FixedArray(t, size), SolValue::FixedArray(tokens)) => { + if *size != tokens.len() { + return Err(crate::Error::custom_owned(format!( + "Size mismatch for FixedArray. Got {}, expected {}", + tokens.len(), + size, + ))); + } + Ok(DynToken::FixedSeq( + tokens + .into_iter() + .map(|token| t.tokenize(token)) + .collect::>()?, + *size, + )) + } + (DynSolType::CustomStruct { name, tuple }, SolValue::Tuple(tokens)) => { + if tuple.len() != tokens.len() { + return Err(crate::Error::custom_owned(format!( + "Tuple length mismatch for {} . Got {}, expected {}", + name, + tokens.len(), + tuple.len(), + ))); + } + let len = tuple.len(); + let tuple = tuple + .iter() + .zip(tokens.into_iter()) + .map(|(ty, token)| ty.tokenize(token)) + .collect::>()?; + Ok(DynToken::FixedSeq(tuple, len)) + } + ( + DynSolType::CustomStruct { name, tuple }, + SolValue::CustomStruct { + name: name_val, + tuple: tuple_val, + }, + ) => { + if name != &name_val { + return Err(crate::Error::custom_owned(std::format!( + "Name mismatch for {} . Got {}, expected {}", + name, + name_val, + name, + ))); + } + if tuple.len() != tuple_val.len() { + return Err(crate::Error::custom_owned(std::format!( + "Tuple length mismatch for {} . Got {}, expected {}", + name, + tuple_val.len(), + tuple.len(), + ))); + } + let len = tuple.len(); + let tuple = tuple + .iter() + .zip(tuple_val.into_iter()) + .map(|(ty, token)| ty.tokenize(token)) + .collect::>()?; + Ok(DynToken::FixedSeq(tuple, len)) + } + ( + DynSolType::CustomValue { name, inner }, + SolValue::CustomValue { + name: name_val, + inner: inner_val, + }, + ) => { + if name != &name_val { + return Err(crate::Error::custom_owned(std::format!( + "Name mismatch for {} . Got {}, expected {}", + name, + name_val, + name, + ))); + } + Ok(inner.tokenize(*inner_val)?) + } + (DynSolType::CustomValue { inner, .. }, value) => inner.tokenize(value), + + _ => Err(crate::Error::Other( + "Invalid type on dynamic tokenization".into(), + )), + } + } + + /// Dynamic detokenization + pub fn detokenize(&self, token: DynToken) -> AbiResult { + match (self, token) { + (DynSolType::Address, DynToken::Word(word)) => Ok(SolValue::Address( + sol_type::Address::detokenize(word.into())?, + )), + (DynSolType::Bool, DynToken::Word(word)) => { + Ok(SolValue::Bool(sol_type::Bool::detokenize(word.into())?)) + } + (DynSolType::Bytes, DynToken::PackedSeq(buf)) => Ok(SolValue::Bytes(buf)), + (DynSolType::FixedBytes(size), DynToken::Word(word)) => Ok(SolValue::FixedBytes( + sol_type::FixedBytes::<32>::detokenize(word.into())?.into(), + *size, + )), + // cheating here, but it's ok + (DynSolType::Int(size), DynToken::Word(word)) => Ok(SolValue::Int( + sol_type::FixedBytes::<32>::detokenize(word.into())?.into(), + *size, + )), + (DynSolType::Uint(size), DynToken::Word(word)) => Ok(SolValue::Uint( + sol_type::Uint::<256>::detokenize(word.into())?, + *size, + )), + (DynSolType::Function, DynToken::Word(word)) => Ok(SolValue::Function( + sol_type::Function::detokenize(word.into())?, + )), + (DynSolType::String, DynToken::PackedSeq(buf)) => { + Ok(SolValue::String(sol_type::String::detokenize(buf.into())?)) + } + (DynSolType::Tuple(types), DynToken::FixedSeq(tokens, _)) => { + if types.len() != tokens.len() { + return Err(crate::Error::custom( + "tuple length mismatch on dynamic detokenization", + )); + } + Ok(SolValue::Tuple( + types + .iter() + .zip(tokens.into_iter()) + .map(|(t, w)| t.detokenize(w)) + .collect::>()?, + )) + } + (DynSolType::Array(t), DynToken::DynSeq { contents, .. }) => Ok(SolValue::Array( + contents + .into_iter() + .map(|tok| t.detokenize(tok)) + .collect::>()?, + )), + (DynSolType::FixedArray(t, size), DynToken::FixedSeq(tokens, count)) => { + if *size != tokens.len() { + return Err(crate::Error::custom( + "array length mismatch on dynamic detokenization", + )); + } + Ok(SolValue::FixedArray( + tokens + .into_iter() + .map(|tok| t.detokenize(tok)) + .collect::>()?, + )) + } + (DynSolType::CustomStruct { name, tuple }, DynToken::FixedSeq(tokens, len)) => { + if len != tokens.len() || len != tuple.len() { + return Err(crate::Error::custom( + "custom length mismatch on dynamic detokenization", + )); + } + let tuple = tuple + .iter() + .zip(tokens.into_iter()) + .map(|(t, w)| t.detokenize(w)) + .collect::>()?; + + Ok(SolValue::CustomStruct { + name: name.clone(), + tuple, + }) + } + (DynSolType::CustomValue { name, inner }, token) => inner.detokenize(token), + _ => Err(crate::Error::custom( + "mismatched types on dynamic detokenization", + )), + } + } + + /// Instantiate an empty dyn token, to be decoded into + pub fn empty_dyn_token(&self) -> DynToken { + match self { + DynSolType::Address => DynToken::Word(Word::default()), + DynSolType::Bool => DynToken::Word(Word::default()), + DynSolType::Bytes => DynToken::PackedSeq(Vec::new()), + DynSolType::FixedBytes(_) => DynToken::Word(Word::default()), + DynSolType::Int(_) => DynToken::Word(Word::default()), + DynSolType::Uint(_) => DynToken::Word(Word::default()), + DynSolType::Function => DynToken::Word(Word::default()), + DynSolType::String => DynToken::PackedSeq(Vec::new()), + DynSolType::Tuple(types) => DynToken::FixedSeq( + types.iter().map(|t| t.empty_dyn_token()).collect(), + types.len(), + ), + DynSolType::Array(t) => DynToken::DynSeq { + contents: vec![], + template: Box::new(t.empty_dyn_token()), + }, + DynSolType::FixedArray(t, size) => { + DynToken::FixedSeq(vec![t.empty_dyn_token(); *size], *size) + } + DynSolType::CustomStruct { tuple, .. } => DynToken::FixedSeq( + tuple.iter().map(|t| t.empty_dyn_token()).collect(), + tuple.len(), + ), + DynSolType::CustomValue { inner, .. } => inner.empty_dyn_token(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SolValue { + Address(B160), + Bool(bool), + Bytes(Vec), + FixedBytes(Word, usize), + Int(Word, usize), + Uint(U256, usize), + Function((B160, [u8; 4])), + String(String), + Tuple(Vec), + Array(Vec), + FixedArray(Vec), + CustomStruct { name: String, tuple: Vec }, + CustomValue { name: String, inner: Box }, +} + +impl From for SolValue { + fn from(value: B160) -> Self { + Self::Address(value) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum DynToken { + Word(Word), + FixedSeq(Vec, usize), + DynSeq { + contents: Vec, + template: Box, + }, + PackedSeq(Vec), +} + +impl From for DynToken { + fn from(value: Word) -> Self { + Self::Word(value.into()) + } +} + +impl DynToken { + fn is_dynamic(&self) -> bool { + match self { + Self::Word(_) => false, + Self::FixedSeq(inner, _) => inner.iter().any(|i| i.is_dynamic()), + Self::DynSeq { .. } => true, + Self::PackedSeq(_) => true, + } + } + + fn decode_populate(&mut self, dec: &mut Decoder) -> AbiResult<()> { + let dynamic = self.is_dynamic(); + match self { + DynToken::Word(w) => *w = WordToken::decode_from(dec)?.inner(), + DynToken::FixedSeq(toks, size) => { + // todo try to remove this duplication? + let mut child = if dynamic { + dec.take_indirection()? + } else { + dec.raw_child() + }; + for tok in toks.iter_mut().take(*size) { + tok.decode_populate(&mut child)?; + } + } + DynToken::DynSeq { contents, template } => { + let mut child = dec.take_indirection()?; + let size = dec.take_u32()? as usize; + + let mut new_toks = Vec::with_capacity(size); + for tok in 0..size { + let mut t = (**template).clone(); + t.decode_populate(&mut child)?; + new_toks.push(t); + } + *contents = new_toks; + } + DynToken::PackedSeq(buf) => *buf = PackedSeqToken::decode_from(dec)?.take_vec(), + } + Ok(()) + } + + fn head_words(&self) -> usize { + match self { + DynToken::Word(_) => 1, + DynToken::FixedSeq(tokens, _) => { + if self.is_dynamic() { + 1 + } else { + tokens.iter().map(DynToken::head_words).sum() + } + } + DynToken::DynSeq { .. } => 1, + DynToken::PackedSeq(_) => 1, + } + } + + fn tail_words(&self) -> usize { + match self { + DynToken::Word(_) => 0, + DynToken::FixedSeq(_, size) => { + if self.is_dynamic() { + *size + } else { + 0 + } + } + DynToken::DynSeq { contents, .. } => { + 1 + contents.iter().map(DynToken::tail_words).sum::() + } + DynToken::PackedSeq(buf) => 1 + (buf.len() + 31) / 32, + } + } + + fn head_append(&self, enc: &mut Encoder) { + match self { + DynToken::Word(word) => enc.append_word(*word), + DynToken::FixedSeq(tokens, _) => { + if self.is_dynamic() { + enc.append_indirection(); + } else { + tokens.iter().for_each(|inner| inner.head_append(enc)) + } + } + DynToken::DynSeq { .. } => enc.append_indirection(), + DynToken::PackedSeq(buf) => enc.append_indirection(), + } + } + + fn tail_append(&self, enc: &mut Encoder) { + match self { + DynToken::Word(_) => {} + DynToken::FixedSeq(_, _) => { + if self.is_dynamic() { + self.encode_sequence(enc); + } + } + DynToken::DynSeq { contents, .. } => { + enc.append_seq_len(contents); + self.encode_sequence(enc); + } + DynToken::PackedSeq(buf) => enc.append_packed_seq(buf), + } + } + + fn encode_sequence(&self, enc: &mut Encoder) { + match self { + DynToken::FixedSeq(tokens, _) => { + let head_words = tokens.iter().map(DynToken::head_words).sum::(); + enc.push_offset(head_words as u32); + for t in tokens.iter() { + t.head_append(enc); + enc.bump_offset(t.tail_words() as u32); + } + for t in tokens.iter() { + t.tail_append(enc); + } + enc.pop_offset(); + } + DynToken::DynSeq { contents, .. } => { + let head_words = contents.iter().map(DynToken::head_words).sum::(); + enc.push_offset(head_words as u32); + for t in contents.iter() { + t.head_append(enc); + enc.bump_offset(t.tail_words() as u32); + } + for t in contents.iter() { + t.tail_append(enc); + } + enc.pop_offset(); + } + _ => {} + } + } + + fn decode_sequence_populate(&mut self, dec: &mut Decoder) -> AbiResult<()> { + match self { + DynToken::FixedSeq(buf, size) => { + for item in buf.iter_mut() { + item.decode_populate(dec)?; + } + Ok(()) + } + DynToken::DynSeq { .. } => self.decode_populate(dec), + _ => Err(Error::custom( + "Called decode_sequence_populate on non-sequence", + )), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn it_encodes() { + let word1 = "0000000000000000000000000101010101010101010101010101010101010101" + .parse() + .unwrap(); + let word2 = "0000000000000000000000000202020202020202020202020202020202020202" + .parse() + .unwrap(); + + let sol_type = DynSolType::Address; + let token = sol_type + .tokenize(SolValue::Address(B160::repeat_byte(0x01))) + .unwrap(); + assert_eq!(token, DynToken::from(word1)); + + let sol_type = DynSolType::FixedArray(Box::new(DynSolType::Address), 2); + let token = sol_type + .tokenize(SolValue::FixedArray(vec![ + B160::repeat_byte(0x01).into(), + B160::repeat_byte(0x02).into(), + ])) + .unwrap(); + assert_eq!( + token, + DynToken::FixedSeq(vec![DynToken::Word(word1), DynToken::Word(word2)], 2) + ); + let mut enc = Encoder::default(); + token.encode_sequence(&mut enc); + assert_eq!(enc.finish(), vec![word1, word2]); + } +} diff --git a/abi/src/errors.rs b/abi/src/errors.rs index 71c09df15..de9cdd9ac 100644 --- a/abi/src/errors.rs +++ b/abi/src/errors.rs @@ -43,6 +43,18 @@ pub enum Error { Other(Cow<'static, str>), } +impl Error { + /// Instantiates a new error with a static str + pub fn custom(s: &'static str) -> Self { + Self::Other(s.into()) + } + + /// Instantiates a new error with a string + pub fn custom_owned(s: String) -> Self { + Self::Other(s.into()) + } +} + impl From for Error { fn from(value: hex::FromHexError) -> Self { Self::FromHexError(value) diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 66554c774..d4c466c39 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -156,6 +156,9 @@ use no_std_prelude::*; mod decoder; pub use decoder::{decode, decode_params, decode_single}; +mod dyn_sol_type; +pub use dyn_sol_type::{DynSolType, DynToken}; + mod encoder; pub use encoder::{encode, encode_params, encode_single}; diff --git a/abi/src/token.rs b/abi/src/token.rs index 6e15a2b64..c5fd52d73 100644 --- a/abi/src/token.rs +++ b/abi/src/token.rs @@ -63,7 +63,7 @@ pub trait TokenSeq: TokenType { } /// A single EVM word - T for any value type -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct WordToken(Word); impl From for WordToken { @@ -230,7 +230,7 @@ where enc.bump_offset(t.tail_words() as u32); } for t in self.0.iter() { - t.tail_append(enc) + t.tail_append(enc); } enc.pop_offset(); } @@ -350,7 +350,7 @@ where enc.bump_offset(t.tail_words() as u32); } for t in self.0.iter() { - t.tail_append(enc) + t.tail_append(enc); } enc.pop_offset(); } @@ -405,8 +405,8 @@ impl TokenType for PackedSeqToken { } fn tail_words(&self) -> usize { - // "+ 1" because len is also appended - ((self.0.len() + 31) / 32) + 1 + // "1 +" because len is also appended + 1 + ((self.0.len() + 31) / 32) } fn head_append(&self, enc: &mut Encoder) { From 7bfb06bd86a2dc73ae975e5b5a7acbdc57b1e903 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 27 Mar 2023 09:48:48 -0700 Subject: [PATCH 2/9] wip: dynencode --- abi/src/dyn_sol_type.rs | 41 +++++++++++++++++++++++++++-------------- abi/src/lib.rs | 4 ++-- abi/tests/proc.rs | 6 ++++++ 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/abi/src/dyn_sol_type.rs b/abi/src/dyn_sol_type.rs index 3342c94e7..9c26f431e 100644 --- a/abi/src/dyn_sol_type.rs +++ b/abi/src/dyn_sol_type.rs @@ -7,18 +7,31 @@ use crate::{ SolType, TokenType, Word, WordToken, }; -pub struct Parenthesized<'a> { - inner: &'a str, +pub struct CommaSeparatedList<'a> { + elements: Vec<&'a str>, } -impl<'a> FromStr for Parenthesized<'a> { - type Err = (); +impl<'a> TryFrom<&'a str> for CommaSeparatedList<'a> { + type Error = (); - fn from_str(s: &'a str) -> Result, Self::Err> { - s.split_once('(') - .and_then(|(_, inner)| inner.rsplit_once(')')) - .map(|(inner, _)| Self { inner }) - .ok_or(()) + fn try_from(s: &'a str) -> Result { + let s = s.trim_matches("()"); + + let mut elements = vec![]; + let mut depth = 0; + let mut start = 0; + + for (i, c) in s.char_indices() { + match c { + '(' => depth += 1, + ')' => depth -= 1, + ',' if depth == 0 => { + elements.push(&s[start..i]); + start = i + 1; + } + _ => {} + } + } } } @@ -26,16 +39,16 @@ impl<'a> FromStr for Parenthesized<'a> { // Wraps all implementers of sol_type::SolType pub enum DynSolType { Address, - Bool, Bytes, - FixedBytes(usize), Int(usize), Uint(usize), - Function, - String, - Tuple(Vec), + Bool, Array(Box), + String, + FixedBytes(usize), FixedArray(Box, usize), + Tuple(Vec), + Function, CustomStruct { name: String, tuple: Vec, diff --git a/abi/src/lib.rs b/abi/src/lib.rs index d4c466c39..5c496279d 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -156,8 +156,8 @@ use no_std_prelude::*; mod decoder; pub use decoder::{decode, decode_params, decode_single}; -mod dyn_sol_type; -pub use dyn_sol_type::{DynSolType, DynToken}; +// mod dyn_sol_type; +// pub use dyn_sol_type::{DynSolType, DynToken}; mod encoder; pub use encoder::{encode, encode_params, encode_single}; diff --git a/abi/tests/proc.rs b/abi/tests/proc.rs index 66f6d98bf..3da177f46 100644 --- a/abi/tests/proc.rs +++ b/abi/tests/proc.rs @@ -10,6 +10,12 @@ sol! { } } +sol! { + tuple( + address[][3], bool, address + ) +} + // Works only outside of function scope due to rust import rules sol! { struct MyStruct2 { From bb6ba0f99015987fa24cfdb7972ad85669a96d1b Mon Sep 17 00:00:00 2001 From: James Date: Sat, 1 Apr 2023 19:02:04 -0500 Subject: [PATCH 3/9] refactor: multi-file module for dyn encoding --- abi/Cargo.toml | 3 +- abi/src/dyn_abi/mod.rs | 17 + abi/src/dyn_abi/parser.rs | 206 +++++++++++ .../{dyn_sol_type.rs => dyn_abi/sol_type.rs} | 338 ++++-------------- abi/src/dyn_abi/sol_value.rs | 124 +++++++ abi/src/dyn_abi/token.rs | 199 +++++++++++ abi/src/lib.rs | 6 +- abi/tests/proc.rs | 10 +- 8 files changed, 623 insertions(+), 280 deletions(-) create mode 100644 abi/src/dyn_abi/mod.rs create mode 100644 abi/src/dyn_abi/parser.rs rename abi/src/{dyn_sol_type.rs => dyn_abi/sol_type.rs} (52%) create mode 100644 abi/src/dyn_abi/sol_value.rs create mode 100644 abi/src/dyn_abi/token.rs diff --git a/abi/Cargo.toml b/abi/Cargo.toml index 82c4ef49f..85e2f323f 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -20,5 +20,6 @@ thiserror = { version = "1.0.39", optional = true } hex-literal = "0.3.4" [features] -default = ["std"] +default = ["std", "dynamic"] std = ["hex/std", "thiserror"] +dynamic = [] \ No newline at end of file diff --git a/abi/src/dyn_abi/mod.rs b/abi/src/dyn_abi/mod.rs new file mode 100644 index 000000000..3e437b5f5 --- /dev/null +++ b/abi/src/dyn_abi/mod.rs @@ -0,0 +1,17 @@ +//! Dynamic Solidity Type Encoder +//! +//! This module provides a runtime encoder/decoder for solidity types. It is +//! intended to be used when the solidity type is not known at compile time. +//! This is particularly useful for EIP-712 encoding and signing. +//! +mod sol_type; +pub use sol_type::DynSolType; + +mod sol_value; +pub use sol_value::DynSolValue; + +mod token; +pub use token::DynToken; + +mod parser; +pub use parser::parse; diff --git a/abi/src/dyn_abi/parser.rs b/abi/src/dyn_abi/parser.rs new file mode 100644 index 000000000..45c2c68f2 --- /dev/null +++ b/abi/src/dyn_abi/parser.rs @@ -0,0 +1,206 @@ +use core::num::ParseIntError; + +use super::DynSolType; + +#[derive(Debug, Clone, PartialEq)] +pub enum ParserError { + NotAList, + UnmatchedParen, + UnmatchedBracket, + ParseInt(ParseIntError), + Unknown(String), +} + +impl From for ParserError { + fn from(value: ParseIntError) -> Self { + ParserError::ParseInt(value) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CommaSeparatedList<'a> { + elements: Vec<&'a str>, +} + +impl<'a> TryFrom<&'a str> for CommaSeparatedList<'a> { + type Error = ParserError; + + fn try_from(s: &'a str) -> Result { + let s = s.strip_prefix("tuple").unwrap_or(s); + let s = s.strip_prefix('(').ok_or(ParserError::NotAList)?; + let s = s.strip_suffix(')').ok_or(ParserError::UnmatchedParen)?; + + let mut elements = vec![]; + let mut depth = 0; + let mut start = 0; + + for (i, c) in s.char_indices() { + match c { + '(' => depth += 1, + ')' => depth -= 1, + ',' if depth == 0 => { + elements.push(s[start..i].trim()); + start = i + 1; + } + _ => {} + } + } + // Everything left is also a list item. Check prevents empty list items + if !s[start..].is_empty() { + elements.push(s[start..].trim()); + } + Ok(CommaSeparatedList { elements }) + } +} + +/// Parse a solidity type from a string +pub fn parse(s: &str) -> Result { + let s = s.trim(); + + if s.ends_with(')') { + let csl: CommaSeparatedList = s.try_into()?; + let v: Vec<_> = csl + .elements + .into_iter() + .map(parse) + .collect::>()?; + return Ok(DynSolType::Tuple(v)); + } + if s.ends_with(']') { + let (prefix, suffix) = s.rsplit_once('[').ok_or(ParserError::UnmatchedBracket)?; + let inner = Box::new(parse(prefix)?); + + let suffix = suffix.strip_suffix(']').unwrap(); + if !suffix.is_empty() { + return Ok(DynSolType::FixedArray(inner, suffix.parse()?)); + } else { + return Ok(DynSolType::Array(inner)); + } + } + + if let Some(s) = s.strip_prefix("int") { + return Ok(DynSolType::Int(s.parse()?)); + } + + if let Some(s) = s.strip_prefix("uint") { + return Ok(DynSolType::Uint(s.parse()?)); + } + + match s { + "address" => return Ok(DynSolType::Address), + "bytes" => return Ok(DynSolType::Bytes), + "bool" => return Ok(DynSolType::Bool), + "string" => return Ok(DynSolType::String), + "function" => return Ok(DynSolType::Function), + _ => {} + } + + if let Some(s) = s.strip_prefix("bytes") { + return Ok(DynSolType::FixedBytes(s.parse()?)); + } + + Err(ParserError::Unknown(s.to_string())) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::DynSolType; + + #[test] + fn csl_parse() { + assert_eq!( + CommaSeparatedList::try_from("(a,b,c)"), + Ok(CommaSeparatedList { + elements: vec!["a", "b", "c"] + }) + ); + assert_eq!( + CommaSeparatedList::try_from("(a,(b,c),d)"), + Ok(CommaSeparatedList { + elements: vec!["a", "(b,c)", "d"] + }) + ); + assert_eq!( + CommaSeparatedList::try_from("(a, (b,c), (d, (e, f)))"), + Ok(CommaSeparatedList { + elements: vec!["a", "(b,c)", "(d, (e, f))"] + }) + ); + assert_eq!( + CommaSeparatedList::try_from("(a, (b,c), (d, (e, f))[3])"), + Ok(CommaSeparatedList { + elements: vec!["a", "(b,c)", "(d, (e, f))[3]"] + }) + ); + assert_eq!( + CommaSeparatedList::try_from("tuple(a, b)"), + Ok(CommaSeparatedList { + elements: vec!["a", "b"] + }) + ); + assert_eq!( + CommaSeparatedList::try_from("( a )"), + Ok(CommaSeparatedList { + elements: vec!["a"] + }) + ); + assert_eq!( + CommaSeparatedList::try_from("()"), + Ok(CommaSeparatedList { elements: vec![] }) + ); + assert_eq!( + CommaSeparatedList::try_from("(a,)"), + Ok(CommaSeparatedList { + elements: vec!["a"] + }) + ); + assert_eq!( + CommaSeparatedList::try_from("(a,,b)"), + Ok(CommaSeparatedList { + elements: vec!["a", "", "b"] + }) + ); + + assert_eq!( + CommaSeparatedList::try_from("("), + Err(ParserError::UnmatchedParen) + ); + assert_eq!( + CommaSeparatedList::try_from("a"), + Err(ParserError::NotAList) + ); + } + + #[test] + fn parse_test() { + assert_eq!( + parse("bytes[][3]"), + Ok(DynSolType::FixedArray( + Box::new(DynSolType::Array(Box::new(DynSolType::Bytes))), + 3 + )) + ); + + assert_eq!( + parse(r#"tuple(address,function, (bool, (string, uint256)[][3]))[2]"#), + Ok(DynSolType::FixedArray( + Box::new(DynSolType::Tuple(vec![ + DynSolType::Address, + DynSolType::Function, + DynSolType::Tuple(vec![ + DynSolType::Bool, + DynSolType::FixedArray( + Box::new(DynSolType::Array(Box::new(DynSolType::Tuple(vec![ + DynSolType::String, + DynSolType::Uint(256) + ])))), + 3 + ), + ]), + ])), + 2 + )) + ); + } +} diff --git a/abi/src/dyn_sol_type.rs b/abi/src/dyn_abi/sol_type.rs similarity index 52% rename from abi/src/dyn_sol_type.rs rename to abi/src/dyn_abi/sol_type.rs index 9c26f431e..12dfb3e01 100644 --- a/abi/src/dyn_sol_type.rs +++ b/abi/src/dyn_abi/sol_type.rs @@ -1,61 +1,48 @@ -use core::{ops::Deref, str::FromStr}; - -use ethers_primitives::{B160, U256}; +use core::str::FromStr; use crate::{ - decoder::Decoder, encoder::Encoder, sol_type, AbiResult, Error, FixedSeqToken, PackedSeqToken, - SolType, TokenType, Word, WordToken, + dyn_abi::{DynSolValue, DynToken}, + sol_type, AbiResult, SolType, Word, }; -pub struct CommaSeparatedList<'a> { - elements: Vec<&'a str>, -} - -impl<'a> TryFrom<&'a str> for CommaSeparatedList<'a> { - type Error = (); - - fn try_from(s: &'a str) -> Result { - let s = s.trim_matches("()"); - - let mut elements = vec![]; - let mut depth = 0; - let mut start = 0; - - for (i, c) in s.char_indices() { - match c { - '(' => depth += 1, - ')' => depth -= 1, - ',' if depth == 0 => { - elements.push(&s[start..i]); - start = i + 1; - } - _ => {} - } - } - } -} - #[derive(Debug, Clone, PartialEq, Eq)] -// Wraps all implementers of sol_type::SolType +/// A Dynamic SolType. Equivalent to an enum wrapper around all implementers of +/// [`crate::SolType`] pub enum DynSolType { + /// Address Address, + /// Dynamic bytes Bytes, + /// Signed Integer Int(usize), + /// Unsigned Integer Uint(usize), + /// Boolean Bool, + /// Dynamically sized array Array(Box), + /// String String, + /// Fixed-size bytes, up to 32 FixedBytes(usize), + /// Fixed-sized array FixedArray(Box, usize), + /// Tuple Tuple(Vec), + /// Function Function, + /// User-defined struct CustomStruct { + /// Name of the struct name: String, + // TODO: names? + /// Inner types tuple: Vec, }, + /// User-defined value CustomValue { + /// Name of the value type name: String, - inner: Box, }, } @@ -76,16 +63,16 @@ impl FromStr for DynSolType { impl DynSolType { /// Dynamic tokenization - pub fn tokenize(&self, value: SolValue) -> AbiResult { + pub fn tokenize(&self, value: DynSolValue) -> AbiResult { match (self, value) { - (DynSolType::Address, SolValue::Address(val)) => { + (DynSolType::Address, DynSolValue::Address(val)) => { Ok(DynToken::Word(sol_type::Address::tokenize(val).inner())) } - (DynSolType::Bool, SolValue::Bool(val)) => { + (DynSolType::Bool, DynSolValue::Bool(val)) => { Ok(DynToken::Word(sol_type::Bool::tokenize(val).inner())) } - (DynSolType::Bytes, SolValue::Bytes(val)) => Ok(DynToken::PackedSeq(val)), - (DynSolType::FixedBytes(len), SolValue::FixedBytes(word, size)) => { + (DynSolType::Bytes, DynSolValue::Bytes(val)) => Ok(DynToken::PackedSeq(val)), + (DynSolType::FixedBytes(len), DynSolValue::FixedBytes(word, size)) => { if size != *len { return Err(crate::Error::custom_owned(format!( "Size mismatch for FixedBytes. Got {}, expected {}", @@ -94,13 +81,13 @@ impl DynSolType { } Ok(word.into()) } - (DynSolType::Int(_), SolValue::Int(word, _)) => Ok(word.into()), - (DynSolType::Uint(_), SolValue::Uint(num, _)) => Ok(DynToken::Word(num.into())), - (DynSolType::Function, SolValue::Function(word)) => { - Ok(DynToken::Word(sol_type::Function::tokenize(word).inner())) - } - (DynSolType::String, SolValue::String(buf)) => Ok(DynToken::PackedSeq(buf.into())), - (DynSolType::Tuple(types), SolValue::Tuple(tokens)) => { + (DynSolType::Int(_), DynSolValue::Int(word, _)) => Ok(word.into()), + (DynSolType::Uint(_), DynSolValue::Uint(num, _)) => Ok(DynToken::Word(num.into())), + (DynSolType::Function, DynSolValue::Function(addr, selector)) => Ok(DynToken::Word( + sol_type::Function::tokenize((addr, selector)).inner(), + )), + (DynSolType::String, DynSolValue::String(buf)) => Ok(DynToken::PackedSeq(buf.into())), + (DynSolType::Tuple(types), DynSolValue::Tuple(tokens)) => { let tokens = types .iter() .zip(tokens.into_iter()) @@ -109,7 +96,7 @@ impl DynSolType { Ok(DynToken::FixedSeq(tokens, types.len())) } - (DynSolType::Array(t), SolValue::Array(values)) => { + (DynSolType::Array(t), DynSolValue::Array(values)) => { let contents: Vec = values .into_iter() .map(|val| t.tokenize(val)) @@ -117,7 +104,7 @@ impl DynSolType { let template = Box::new(contents.first().unwrap().clone()); Ok(DynToken::DynSeq { contents, template }) } - (DynSolType::FixedArray(t, size), SolValue::FixedArray(tokens)) => { + (DynSolType::FixedArray(t, size), DynSolValue::FixedArray(tokens)) => { if *size != tokens.len() { return Err(crate::Error::custom_owned(format!( "Size mismatch for FixedArray. Got {}, expected {}", @@ -133,7 +120,7 @@ impl DynSolType { *size, )) } - (DynSolType::CustomStruct { name, tuple }, SolValue::Tuple(tokens)) => { + (DynSolType::CustomStruct { name, tuple }, DynSolValue::Tuple(tokens)) => { if tuple.len() != tokens.len() { return Err(crate::Error::custom_owned(format!( "Tuple length mismatch for {} . Got {}, expected {}", @@ -152,7 +139,7 @@ impl DynSolType { } ( DynSolType::CustomStruct { name, tuple }, - SolValue::CustomStruct { + DynSolValue::CustomStruct { name: name_val, tuple: tuple_val, }, @@ -182,8 +169,8 @@ impl DynSolType { Ok(DynToken::FixedSeq(tuple, len)) } ( - DynSolType::CustomValue { name, inner }, - SolValue::CustomValue { + DynSolType::CustomValue { name }, + DynSolValue::CustomValue { name: name_val, inner: inner_val, }, @@ -196,10 +183,9 @@ impl DynSolType { name, ))); } - Ok(inner.tokenize(*inner_val)?) + // A little hacky. A Custom value type is always encoded as a full 32-byte worc + Ok(DynSolType::FixedBytes(32).tokenize(DynSolValue::FixedBytes(inner_val, 32))?) } - (DynSolType::CustomValue { inner, .. }, value) => inner.tokenize(value), - _ => Err(crate::Error::Other( "Invalid type on dynamic tokenization".into(), )), @@ -207,41 +193,42 @@ impl DynSolType { } /// Dynamic detokenization - pub fn detokenize(&self, token: DynToken) -> AbiResult { + pub fn detokenize(&self, token: DynToken) -> AbiResult { match (self, token) { - (DynSolType::Address, DynToken::Word(word)) => Ok(SolValue::Address( + (DynSolType::Address, DynToken::Word(word)) => Ok(DynSolValue::Address( sol_type::Address::detokenize(word.into())?, )), (DynSolType::Bool, DynToken::Word(word)) => { - Ok(SolValue::Bool(sol_type::Bool::detokenize(word.into())?)) + Ok(DynSolValue::Bool(sol_type::Bool::detokenize(word.into())?)) } - (DynSolType::Bytes, DynToken::PackedSeq(buf)) => Ok(SolValue::Bytes(buf)), - (DynSolType::FixedBytes(size), DynToken::Word(word)) => Ok(SolValue::FixedBytes( + (DynSolType::Bytes, DynToken::PackedSeq(buf)) => Ok(DynSolValue::Bytes(buf)), + (DynSolType::FixedBytes(size), DynToken::Word(word)) => Ok(DynSolValue::FixedBytes( sol_type::FixedBytes::<32>::detokenize(word.into())?.into(), *size, )), // cheating here, but it's ok - (DynSolType::Int(size), DynToken::Word(word)) => Ok(SolValue::Int( + (DynSolType::Int(size), DynToken::Word(word)) => Ok(DynSolValue::Int( sol_type::FixedBytes::<32>::detokenize(word.into())?.into(), *size, )), - (DynSolType::Uint(size), DynToken::Word(word)) => Ok(SolValue::Uint( + (DynSolType::Uint(size), DynToken::Word(word)) => Ok(DynSolValue::Uint( sol_type::Uint::<256>::detokenize(word.into())?, *size, )), - (DynSolType::Function, DynToken::Word(word)) => Ok(SolValue::Function( - sol_type::Function::detokenize(word.into())?, - )), - (DynSolType::String, DynToken::PackedSeq(buf)) => { - Ok(SolValue::String(sol_type::String::detokenize(buf.into())?)) + (DynSolType::Function, DynToken::Word(word)) => { + let detok = sol_type::Function::detokenize(word.into())?; + Ok(DynSolValue::Function(detok.0, detok.1)) } + (DynSolType::String, DynToken::PackedSeq(buf)) => Ok(DynSolValue::String( + sol_type::String::detokenize(buf.into())?, + )), (DynSolType::Tuple(types), DynToken::FixedSeq(tokens, _)) => { if types.len() != tokens.len() { return Err(crate::Error::custom( "tuple length mismatch on dynamic detokenization", )); } - Ok(SolValue::Tuple( + Ok(DynSolValue::Tuple( types .iter() .zip(tokens.into_iter()) @@ -249,19 +236,19 @@ impl DynSolType { .collect::>()?, )) } - (DynSolType::Array(t), DynToken::DynSeq { contents, .. }) => Ok(SolValue::Array( + (DynSolType::Array(t), DynToken::DynSeq { contents, .. }) => Ok(DynSolValue::Array( contents .into_iter() .map(|tok| t.detokenize(tok)) .collect::>()?, )), - (DynSolType::FixedArray(t, size), DynToken::FixedSeq(tokens, count)) => { + (DynSolType::FixedArray(t, size), DynToken::FixedSeq(tokens, _)) => { if *size != tokens.len() { return Err(crate::Error::custom( "array length mismatch on dynamic detokenization", )); } - Ok(SolValue::FixedArray( + Ok(DynSolValue::FixedArray( tokens .into_iter() .map(|tok| t.detokenize(tok)) @@ -280,12 +267,12 @@ impl DynSolType { .map(|(t, w)| t.detokenize(w)) .collect::>()?; - Ok(SolValue::CustomStruct { + Ok(DynSolValue::CustomStruct { name: name.clone(), tuple, }) } - (DynSolType::CustomValue { name, inner }, token) => inner.detokenize(token), + (DynSolType::CustomValue { .. }, token) => DynSolType::FixedBytes(32).detokenize(token), _ => Err(crate::Error::custom( "mismatched types on dynamic detokenization", )), @@ -318,198 +305,7 @@ impl DynSolType { tuple.iter().map(|t| t.empty_dyn_token()).collect(), tuple.len(), ), - DynSolType::CustomValue { inner, .. } => inner.empty_dyn_token(), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum SolValue { - Address(B160), - Bool(bool), - Bytes(Vec), - FixedBytes(Word, usize), - Int(Word, usize), - Uint(U256, usize), - Function((B160, [u8; 4])), - String(String), - Tuple(Vec), - Array(Vec), - FixedArray(Vec), - CustomStruct { name: String, tuple: Vec }, - CustomValue { name: String, inner: Box }, -} - -impl From for SolValue { - fn from(value: B160) -> Self { - Self::Address(value) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum DynToken { - Word(Word), - FixedSeq(Vec, usize), - DynSeq { - contents: Vec, - template: Box, - }, - PackedSeq(Vec), -} - -impl From for DynToken { - fn from(value: Word) -> Self { - Self::Word(value.into()) - } -} - -impl DynToken { - fn is_dynamic(&self) -> bool { - match self { - Self::Word(_) => false, - Self::FixedSeq(inner, _) => inner.iter().any(|i| i.is_dynamic()), - Self::DynSeq { .. } => true, - Self::PackedSeq(_) => true, - } - } - - fn decode_populate(&mut self, dec: &mut Decoder) -> AbiResult<()> { - let dynamic = self.is_dynamic(); - match self { - DynToken::Word(w) => *w = WordToken::decode_from(dec)?.inner(), - DynToken::FixedSeq(toks, size) => { - // todo try to remove this duplication? - let mut child = if dynamic { - dec.take_indirection()? - } else { - dec.raw_child() - }; - for tok in toks.iter_mut().take(*size) { - tok.decode_populate(&mut child)?; - } - } - DynToken::DynSeq { contents, template } => { - let mut child = dec.take_indirection()?; - let size = dec.take_u32()? as usize; - - let mut new_toks = Vec::with_capacity(size); - for tok in 0..size { - let mut t = (**template).clone(); - t.decode_populate(&mut child)?; - new_toks.push(t); - } - *contents = new_toks; - } - DynToken::PackedSeq(buf) => *buf = PackedSeqToken::decode_from(dec)?.take_vec(), - } - Ok(()) - } - - fn head_words(&self) -> usize { - match self { - DynToken::Word(_) => 1, - DynToken::FixedSeq(tokens, _) => { - if self.is_dynamic() { - 1 - } else { - tokens.iter().map(DynToken::head_words).sum() - } - } - DynToken::DynSeq { .. } => 1, - DynToken::PackedSeq(_) => 1, - } - } - - fn tail_words(&self) -> usize { - match self { - DynToken::Word(_) => 0, - DynToken::FixedSeq(_, size) => { - if self.is_dynamic() { - *size - } else { - 0 - } - } - DynToken::DynSeq { contents, .. } => { - 1 + contents.iter().map(DynToken::tail_words).sum::() - } - DynToken::PackedSeq(buf) => 1 + (buf.len() + 31) / 32, - } - } - - fn head_append(&self, enc: &mut Encoder) { - match self { - DynToken::Word(word) => enc.append_word(*word), - DynToken::FixedSeq(tokens, _) => { - if self.is_dynamic() { - enc.append_indirection(); - } else { - tokens.iter().for_each(|inner| inner.head_append(enc)) - } - } - DynToken::DynSeq { .. } => enc.append_indirection(), - DynToken::PackedSeq(buf) => enc.append_indirection(), - } - } - - fn tail_append(&self, enc: &mut Encoder) { - match self { - DynToken::Word(_) => {} - DynToken::FixedSeq(_, _) => { - if self.is_dynamic() { - self.encode_sequence(enc); - } - } - DynToken::DynSeq { contents, .. } => { - enc.append_seq_len(contents); - self.encode_sequence(enc); - } - DynToken::PackedSeq(buf) => enc.append_packed_seq(buf), - } - } - - fn encode_sequence(&self, enc: &mut Encoder) { - match self { - DynToken::FixedSeq(tokens, _) => { - let head_words = tokens.iter().map(DynToken::head_words).sum::(); - enc.push_offset(head_words as u32); - for t in tokens.iter() { - t.head_append(enc); - enc.bump_offset(t.tail_words() as u32); - } - for t in tokens.iter() { - t.tail_append(enc); - } - enc.pop_offset(); - } - DynToken::DynSeq { contents, .. } => { - let head_words = contents.iter().map(DynToken::head_words).sum::(); - enc.push_offset(head_words as u32); - for t in contents.iter() { - t.head_append(enc); - enc.bump_offset(t.tail_words() as u32); - } - for t in contents.iter() { - t.tail_append(enc); - } - enc.pop_offset(); - } - _ => {} - } - } - - fn decode_sequence_populate(&mut self, dec: &mut Decoder) -> AbiResult<()> { - match self { - DynToken::FixedSeq(buf, size) => { - for item in buf.iter_mut() { - item.decode_populate(dec)?; - } - Ok(()) - } - DynToken::DynSeq { .. } => self.decode_populate(dec), - _ => Err(Error::custom( - "Called decode_sequence_populate on non-sequence", - )), + DynSolType::CustomValue { .. } => DynToken::Word(Word::default()), } } } @@ -517,9 +313,11 @@ impl DynToken { #[cfg(test)] mod test { use super::*; + use crate::*; + use ethers_primitives::B160; #[test] - fn it_encodes() { + fn dynamically_encodes() { let word1 = "0000000000000000000000000101010101010101010101010101010101010101" .parse() .unwrap(); @@ -529,13 +327,13 @@ mod test { let sol_type = DynSolType::Address; let token = sol_type - .tokenize(SolValue::Address(B160::repeat_byte(0x01))) + .tokenize(DynSolValue::Address(B160::repeat_byte(0x01))) .unwrap(); assert_eq!(token, DynToken::from(word1)); let sol_type = DynSolType::FixedArray(Box::new(DynSolType::Address), 2); let token = sol_type - .tokenize(SolValue::FixedArray(vec![ + .tokenize(DynSolValue::FixedArray(vec![ B160::repeat_byte(0x01).into(), B160::repeat_byte(0x02).into(), ])) @@ -544,7 +342,7 @@ mod test { token, DynToken::FixedSeq(vec![DynToken::Word(word1), DynToken::Word(word2)], 2) ); - let mut enc = Encoder::default(); + let mut enc = crate::encoder::Encoder::default(); token.encode_sequence(&mut enc); assert_eq!(enc.finish(), vec![word1, word2]); } diff --git a/abi/src/dyn_abi/sol_value.rs b/abi/src/dyn_abi/sol_value.rs new file mode 100644 index 000000000..dde3bba36 --- /dev/null +++ b/abi/src/dyn_abi/sol_value.rs @@ -0,0 +1,124 @@ +use ethers_primitives::{B160, U256}; + +use crate::Word; + +/// 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 +/// types, and the user must inspect and disambiguate +#[derive(Debug, Clone, PartialEq)] +pub enum DynSolValue { + /// An address + Address(B160), + /// A boolean + Bool(bool), + /// A dynamic-length byte array + Bytes(Vec), + /// A fixed-length byte string + FixedBytes(Word, usize), + /// A signed integer + Int(Word, usize), + /// An unsigned integer + Uint(U256, usize), + /// A function + Function(B160, [u8; 4]), + /// A string + String(String), + /// A tuple of values + Tuple(Vec), + /// A dynamically-sized array of values + Array(Vec), + /// A fixed-size array of values + FixedArray(Vec), + /// A named struct, treated as a tuple with a name parameter + CustomStruct { + /// The name of the struct + name: String, + // TODO: names + /// A inner types + tuple: Vec, + }, + /// A user-defined value type. + CustomValue { + /// The name of the custom value type + name: String, + /// The value itself + inner: Word, + }, +} + +impl From for DynSolValue { + fn from(value: B160) -> Self { + Self::Address(value) + } +} + +impl From for DynSolValue { + fn from(value: bool) -> Self { + Self::Bool(value) + } +} + +impl From> for DynSolValue { + fn from(value: Vec) -> Self { + Self::Bytes(value) + } +} + +macro_rules! impl_from_int { + ($size:ty) => { + impl From<$size> for DynSolValue { + fn from(value: $size) -> Self { + let bits = <$size>::BITS as usize; + let bytes = bits / 8; + let mut word = if value < 0 { + ethers_primitives::B256::repeat_byte(0xff) + } else { + ethers_primitives::B256::default() + }; + word[32 - bytes..].copy_from_slice(&value.to_be_bytes()); + + Self::Int(word.into(), bits) + } + } + }; +} + +impl_from_int!(i8); +impl_from_int!(i16); +impl_from_int!(i32); +impl_from_int!(i64); +impl_from_int!(isize); +// TODO: more? + +macro_rules! impl_from_uint { + ($size:ty) => { + impl From<$size> for DynSolValue { + fn from(value: $size) -> Self { + Self::Uint(U256::from(value), <$size>::BITS as usize) + } + } + }; +} + +impl_from_uint!(u8); +impl_from_uint!(u16); +impl_from_uint!(u32); +impl_from_uint!(u64); +impl_from_uint!(usize); +// TODO: more? +impl_from_uint!(U256); + +impl From<(B160, [u8; 4])> for DynSolValue { + fn from(value: (B160, [u8; 4])) -> Self { + Self::Function(value.0, value.1) + } +} + +impl From for DynSolValue { + fn from(value: String) -> Self { + Self::String(value) + } +} + + + diff --git a/abi/src/dyn_abi/token.rs b/abi/src/dyn_abi/token.rs new file mode 100644 index 000000000..1eb9173a3 --- /dev/null +++ b/abi/src/dyn_abi/token.rs @@ -0,0 +1,199 @@ +use crate::{ + decoder::Decoder, encoder::Encoder, AbiResult, Error, PackedSeqToken, TokenType, Word, + WordToken, +}; + +/// A dynamic token. Equivalent to an enum over all types implementing +/// [`crate::TokenType`] +#[derive(Debug, Clone, PartialEq)] +pub enum DynToken { + /// A single word + Word(Word), + /// A Fixed Sequence + FixedSeq(Vec, usize), + /// A dynamic-length sequence + DynSeq { + /// The contents of the dynamic sequence + contents: Vec, + /// The template type of the dynamic sequence + template: Box, + }, + /// A packed sequence (string or bytes) + PackedSeq(Vec), +} + +impl From for DynToken { + fn from(value: Word) -> Self { + Self::Word(value) + } +} + +impl DynToken { + /// Attempt to cast to a word + pub fn as_word(&self) -> Option { + match self { + Self::Word(word) => Some(*word), + _ => None, + } + } + + /// True if the type is dynamic, else false + pub fn is_dynamic(&self) -> bool { + match self { + Self::Word(_) => false, + Self::FixedSeq(inner, _) => inner.iter().any(|i| i.is_dynamic()), + Self::DynSeq { .. } => true, + Self::PackedSeq(_) => true, + } + } + + /// Decodes from a decoder, populating the structure with the decoded data + pub fn decode_populate(&mut self, dec: &mut Decoder) -> AbiResult<()> { + let dynamic = self.is_dynamic(); + match self { + DynToken::Word(w) => *w = WordToken::decode_from(dec)?.inner(), + DynToken::FixedSeq(toks, size) => { + // todo try to remove this duplication? + let mut child = if dynamic { + dec.take_indirection()? + } else { + dec.raw_child() + }; + for tok in toks.iter_mut().take(*size) { + tok.decode_populate(&mut child)?; + } + } + DynToken::DynSeq { contents, template } => { + let mut child = dec.take_indirection()?; + let size = dec.take_u32()? as usize; + + let mut new_toks = Vec::with_capacity(size); + for _ in 0..size { + let mut t = (**template).clone(); + t.decode_populate(&mut child)?; + new_toks.push(t); + } + *contents = new_toks; + } + DynToken::PackedSeq(buf) => *buf = PackedSeqToken::decode_from(dec)?.take_vec(), + } + Ok(()) + } + + /// Returns the number of words this type uses in the head of the ABI blob + pub fn head_words(&self) -> usize { + match self { + DynToken::Word(_) => 1, + DynToken::FixedSeq(tokens, _) => { + if self.is_dynamic() { + 1 + } else { + tokens.iter().map(DynToken::head_words).sum() + } + } + DynToken::DynSeq { .. } => 1, + DynToken::PackedSeq(_) => 1, + } + } + + /// Returns the number of words this type uses in the tail of the ABI blob + pub fn tail_words(&self) -> usize { + match self { + DynToken::Word(_) => 0, + DynToken::FixedSeq(_, size) => { + if self.is_dynamic() { + *size + } else { + 0 + } + } + DynToken::DynSeq { contents, .. } => { + 1 + contents.iter().map(DynToken::tail_words).sum::() + } + DynToken::PackedSeq(buf) => 1 + (buf.len() + 31) / 32, + } + } + + /// Append this data to the head of an in-progress blob via the encoder + pub fn head_append(&self, enc: &mut Encoder) { + match self { + DynToken::Word(word) => enc.append_word(*word), + DynToken::FixedSeq(tokens, _) => { + if self.is_dynamic() { + enc.append_indirection(); + } else { + tokens.iter().for_each(|inner| inner.head_append(enc)) + } + } + DynToken::DynSeq { .. } => enc.append_indirection(), + DynToken::PackedSeq(_) => enc.append_indirection(), + } + } + + /// Append this data to the tail of an in-progress blob via the encoder + pub fn tail_append(&self, enc: &mut Encoder) { + match self { + DynToken::Word(_) => {} + DynToken::FixedSeq(_, _) => { + if self.is_dynamic() { + self.encode_sequence(enc); + } + } + DynToken::DynSeq { contents, .. } => { + enc.append_seq_len(contents); + self.encode_sequence(enc); + } + DynToken::PackedSeq(buf) => enc.append_packed_seq(buf), + } + } + + /// Encode this data, if it is a sequence. No-op otherwise + /// + /// TODO: should this panic otherwise? + pub fn encode_sequence(&self, enc: &mut Encoder) { + match self { + DynToken::FixedSeq(tokens, _) => { + let head_words = tokens.iter().map(DynToken::head_words).sum::(); + enc.push_offset(head_words as u32); + for t in tokens.iter() { + t.head_append(enc); + enc.bump_offset(t.tail_words() as u32); + } + for t in tokens.iter() { + t.tail_append(enc); + } + enc.pop_offset(); + } + DynToken::DynSeq { contents, .. } => { + let head_words = contents.iter().map(DynToken::head_words).sum::(); + enc.push_offset(head_words as u32); + for t in contents.iter() { + t.head_append(enc); + enc.bump_offset(t.tail_words() as u32); + } + for t in contents.iter() { + t.tail_append(enc); + } + enc.pop_offset(); + } + _ => {} + } + } + + /// Decode a sequence from the decoder, populating the data by consuming + /// decoder words + pub fn decode_sequence_populate(&mut self, dec: &mut Decoder) -> AbiResult<()> { + match self { + DynToken::FixedSeq(buf, _) => { + for item in buf.iter_mut() { + item.decode_populate(dec)?; + } + Ok(()) + } + DynToken::DynSeq { .. } => self.decode_populate(dec), + _ => Err(Error::custom( + "Called decode_sequence_populate on non-sequence", + )), + } + } +} diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 5c496279d..967987a3c 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -156,8 +156,10 @@ use no_std_prelude::*; mod decoder; pub use decoder::{decode, decode_params, decode_single}; -// mod dyn_sol_type; -// pub use dyn_sol_type::{DynSolType, DynToken}; +#[cfg(feature = "dynamic")] +mod dyn_abi; +#[cfg(feature = "dynamic")] +pub use dyn_abi::{parse, DynSolType, DynSolValue, DynToken}; mod encoder; pub use encoder::{encode, encode_params, encode_single}; diff --git a/abi/tests/proc.rs b/abi/tests/proc.rs index 3da177f46..b6ac693b3 100644 --- a/abi/tests/proc.rs +++ b/abi/tests/proc.rs @@ -10,12 +10,6 @@ sol! { } } -sol! { - tuple( - address[][3], bool, address - ) -} - // Works only outside of function scope due to rust import rules sol! { struct MyStruct2 { @@ -35,12 +29,14 @@ type LateBinding = sol! { (A[], address) }; +// testcase for something i messed up earlier :) type NestedArray = sol! { bool[2][] }; #[test] -fn hello() { +fn hello_world() { + // this is possible but not recomended :) ::hex_encode_single(true); From aa19ce7217303c28b039e4a3219b90d1f6f2d238 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Apr 2023 09:55:13 -0500 Subject: [PATCH 4/9] docs: continue documenting dynamic encoding --- abi/Cargo.toml | 2 +- abi/src/decoder.rs | 32 ++++++++++ abi/src/dyn_abi/mod.rs | 87 +++++++++++++++++++++++++++- abi/src/dyn_abi/sol_type.rs | 35 ++++++++++- abi/src/dyn_abi/sol_value.rs | 109 ++++++++++++++++++++++++++++++++++- abi/src/dyn_abi/token.rs | 93 ++++++++++++++++++++++++++---- abi/src/encoder.rs | 45 ++++++++++----- abi/src/lib.rs | 10 +++- 8 files changed, 376 insertions(+), 37 deletions(-) diff --git a/abi/Cargo.toml b/abi/Cargo.toml index 85e2f323f..7bb9acb01 100644 --- a/abi/Cargo.toml +++ b/abi/Cargo.toml @@ -22,4 +22,4 @@ hex-literal = "0.3.4" [features] default = ["std", "dynamic"] std = ["hex/std", "thiserror"] -dynamic = [] \ No newline at end of file +dynamic = ["std"] \ No newline at end of file diff --git a/abi/src/decoder.rs b/abi/src/decoder.rs index 725834060..8f1aa3024 100644 --- a/abi/src/decoder.rs +++ b/abi/src/decoder.rs @@ -125,6 +125,10 @@ impl core::fmt::Debug for Decoder<'_> { } impl<'a> Decoder<'a> { + /// Instantiate a new decoder from a byte slice and a validation flag. If + /// Validation is set to true, the decoder will check that the bytes + /// conform to expected type limitations, and that the decoded values can be + /// re-encoded to an identical bytestring. pub fn new(buf: &'a [u8], validate: bool) -> Self { Self { buf, @@ -133,6 +137,9 @@ impl<'a> Decoder<'a> { } } + /// Create a child decoder, starting at `offset` bytes from the current + /// decoder's offset. The child decoder shares the buffer and validation + /// flag fn child(&self, offset: usize) -> Result, Error> { if offset > self.buf.len() { return Err(Error::Overrun); @@ -144,62 +151,80 @@ impl<'a> Decoder<'a> { }) } + /// Get a child decoder at the current offset pub fn raw_child(&self) -> Decoder<'a> { self.child(self.offset).unwrap() } + /// Advance the offset by `len` bytes fn increase_offset(&mut self, len: usize) { self.offset += len; } + /// Peek a range from the buffer pub fn peek(&self, range: Range) -> Result<&'a [u8], Error> { (self.buf.len() >= range.end) .then(|| &self.buf[range]) .ok_or(Error::Overrun) } + /// Peek a slice of size `len` from the buffer at a specific offset, without + /// advancing the offset pub fn peek_len_at(&self, offset: usize, len: usize) -> Result<&'a [u8], Error> { self.peek(offset..offset + len) } + /// Peek a slice of size `len` from the buffer without advancing the offset. pub fn peek_len(&self, len: usize) -> Result<&'a [u8], Error> { self.peek_len_at(self.offset, len) } + /// Peek a word from the buffer at a specific offset, without advancing the + /// offset pub fn peek_word_at(&self, offset: usize) -> Result { Ok(Word::from_slice( self.peek_len_at(offset, Word::len_bytes())?, )) } + /// Peek the next word from the buffer without advancing the offset. pub fn peek_word(&self) -> Result { self.peek_word_at(self.offset) } + /// Peek a u32 from the buffer at a specific offset, without advancing the + /// offset. pub fn peek_u32_at(&self, offset: usize) -> AbiResult { as_u32(self.peek_word_at(offset)?, true) } + /// Peek the next word as a u32 pub fn peek_u32(&self) -> AbiResult { as_u32(self.peek_word()?, true) } + /// Take a word from the buffer, advancing the offset. pub fn take_word(&mut self) -> Result { let contents = self.peek_word()?; self.increase_offset(Word::len_bytes()); Ok(contents) } + /// Return a child decoder by consuming a word, interpreting it as a + /// pointer, and following it. pub fn take_indirection(&mut self) -> Result, Error> { let ptr = self.take_u32()? as usize; self.child(ptr) } + /// Take a u32 from the buffer by consuming a word. pub fn take_u32(&mut self) -> AbiResult { let word = self.take_word()?; as_u32(word, true) } + /// Takes a slice of bytes of the given length by consuming up to the next + /// word boundary pub fn take_slice(&mut self, len: usize) -> Result<&[u8], Error> { if self.validate { let padded_len = round_up_nearest_multiple(len, 32); @@ -217,22 +242,28 @@ impl<'a> Decoder<'a> { Ok(res) } + /// True if this decoder is validating type correctness pub fn validate(&self) -> bool { self.validate } + /// Takes the offset from the child decoder and sets it as the current + /// offset. pub fn take_offset(&mut self, child: Decoder<'a>) { self.set_offset(child.offset + (self.buf.len() - child.buf.len())) } + /// Sets the current offset in the buffer. pub fn set_offset(&mut self, offset: usize) { self.offset = offset; } + /// Returns the current offset in the buffer. pub fn offset(&self) -> usize { self.offset } + /// Decodes a single token from the underlying buffer. pub fn decode(&mut self, data: &[u8]) -> AbiResult where T: TokenType, @@ -246,6 +277,7 @@ impl<'a> Decoder<'a> { Ok(token) } + /// Decodes a sequence of tokens from the underlying buffer. pub fn decode_sequence(&mut self, data: &[u8]) -> AbiResult where T: TokenType + TokenSeq, diff --git a/abi/src/dyn_abi/mod.rs b/abi/src/dyn_abi/mod.rs index 3e437b5f5..a22ba9c2f 100644 --- a/abi/src/dyn_abi/mod.rs +++ b/abi/src/dyn_abi/mod.rs @@ -2,8 +2,61 @@ //! //! This module provides a runtime encoder/decoder for solidity types. It is //! intended to be used when the solidity type is not known at compile time. -//! This is particularly useful for EIP-712 encoding and signing. +//! This is particularly useful for EIP-712 signing interfaces. //! +//! The dynamic encodr/decoder is implemented as a set of enums that represent +//! solidity types, solidity values (in rust representation form), and ABI +//! tokens. Unlike the static encoder, each of these must be instantiated at +//! runtime. The [`DynSolType`] enum represents a solidity type, and is +//! equivalent to an enum over types implementing the [`crate::SolType`] trait. +//! The [`DynSolValue`] enum represents a solidity value, and describes the +//! rust shapes of possible solidity values. It is similar to, but not +//! equivalent to an enum over types used as [`crate::SolType::RustType`]. The +//! [`DynToken`] enum represents an ABI token, and is equivalent to an enum over +//! the types implementing the [`crate::TokenType`] trait. +//! +//! Where the static encoding system encodes the expected type information into +//! the rust type system, the dynamic encoder/decoder encodes it as a concrete +//! instance of [`DynSolType`]. This type is used to tokenize and detokenize +//! [`DynSolValue`] instances. The [`parse`] function is used to parse a +//! solidity type string into a [`DynSolType`] object. +//! +//! Tokenizing - `DynSolType + `DynSolValue` = `DynToken` +//! Detokenizing - `DynSolType` + `DynToken` = `DynSolValue` +//! +//! Users must manually handle the conversions between [`DynSolValue`] and their +//! own rust types. We provide several From implementations, but they fall +//! short when dealing with arrays and tuples. We also provide fallible casts +//! into the contents of each variant. +//! +//! ## `DynToken::decode_populate` +//! +//! Because the shape of the data is known only at runtime, we cannot +//! compile-time allocate the memory needed to hold decoded data. Instead, we +//! pre-allocate a [`DynToken`] with the same shape as the expected type, and +//! empty values. We then populate the empty values with the decoded data. +//! +//! This is a significant behavior departure from the static decoder. +//! +//! ## Example +//! +//! ``` +//! # use ethers_abi_enc::{Decoder, Encoder, DynSolType, DynSolValue, parse}; +//! // parse a type from a string +//! let my_type: DynSolType = parse("uint8[2][]").unwrap(); +//! +//! // set values +//! let uints = DynSolValue::FixedArray(vec![0u8.into(), 1u8.into()]); +//! let my_values = DynSolValue::Array(vec![uints]); +//! +//! // encode +//! let encoded = my_type.encode_single(my_values.clone()).unwrap(); +//! +//! // decode +//! let decoded = my_type.decode_single(&encoded).unwrap(); +//! +//! assert_eq!(decoded, my_values); +//! ``` mod sol_type; pub use sol_type::DynSolType; @@ -15,3 +68,35 @@ pub use token::DynToken; mod parser; pub use parser::parse; + +#[cfg(test)] +mod test { + use crate::{decoder::Decoder, encoder::Encoder, parse, DynSolValue}; + + #[test] + fn simple_e2e() { + // parse a type from a string + let my_type = parse("uint8[2][]").unwrap(); + + // set values + let uints = DynSolValue::FixedArray(vec![64u8.into(), 128u8.into()]); + let my_values = DynSolValue::Array(vec![uints]); + + // tokenize and detokenize + let tokens = my_type.tokenize(my_values.clone()).unwrap(); + let detokenized = my_type.detokenize(tokens.clone()).unwrap(); + assert_eq!(detokenized, my_values); + + // encode + let mut encoder = Encoder::default(); + tokens.encode_single(&mut encoder).unwrap(); + let encoded = encoder.into_bytes(); + + // decode + let mut decoder = Decoder::new(&encoded, true); + let mut decoded = my_type.empty_dyn_token(); + decoded.decode_single_populate(&mut decoder).unwrap(); + + assert_eq!(decoded, tokens); + } +} diff --git a/abi/src/dyn_abi/sol_type.rs b/abi/src/dyn_abi/sol_type.rs index 12dfb3e01..c00b0fbe3 100644 --- a/abi/src/dyn_abi/sol_type.rs +++ b/abi/src/dyn_abi/sol_type.rs @@ -280,7 +280,7 @@ impl DynSolType { } /// Instantiate an empty dyn token, to be decoded into - pub fn empty_dyn_token(&self) -> DynToken { + pub(crate) fn empty_dyn_token(&self) -> DynToken { match self { DynSolType::Address => DynToken::Word(Word::default()), DynSolType::Bool => DynToken::Word(Word::default()), @@ -308,6 +308,37 @@ impl DynSolType { DynSolType::CustomValue { .. } => DynToken::Word(Word::default()), } } + + /// Encode a single value. Fails if the value does not match this type + pub fn encode_single(&self, value: DynSolValue) -> AbiResult> { + let mut encoder = crate::encoder::Encoder::default(); + self.tokenize(value)?.encode_single(&mut encoder)?; + Ok(encoder.into_bytes()) + } + + /// Decode a single value. Fails if the value does not match this type + pub fn decode_single(&self, data: &[u8]) -> AbiResult { + let mut decoder = crate::decoder::Decoder::new(data, false); + let mut toks = self.empty_dyn_token(); + toks.decode_single_populate(&mut decoder)?; + self.detokenize(toks) + } + + /// Encode a sequence of values. Fails if the values do not match this + /// type. Is a no-op if this type or the values are not a sequence. + pub fn encode_sequence(&self, values: DynSolValue) -> AbiResult> { + let mut encoder = crate::encoder::Encoder::default(); + self.tokenize(values)?.encode_sequence(&mut encoder)?; + Ok(encoder.into_bytes()) + } + + /// Decode a sequence of values. Fails if the values do not match this type + pub fn decode_sequence(&self, data: &[u8]) -> AbiResult { + let mut decoder = crate::decoder::Decoder::new(data, false); + let mut toks = self.empty_dyn_token(); + toks.decode_sequence_populate(&mut decoder)?; + self.detokenize(toks) + } } #[cfg(test)] @@ -343,7 +374,7 @@ mod test { DynToken::FixedSeq(vec![DynToken::Word(word1), DynToken::Word(word2)], 2) ); let mut enc = crate::encoder::Encoder::default(); - token.encode_sequence(&mut enc); + token.encode_sequence(&mut enc).unwrap(); assert_eq!(enc.finish(), vec![word1, word2]); } } diff --git a/abi/src/dyn_abi/sol_value.rs b/abi/src/dyn_abi/sol_value.rs index dde3bba36..cf931373b 100644 --- a/abi/src/dyn_abi/sol_value.rs +++ b/abi/src/dyn_abi/sol_value.rs @@ -46,6 +46,112 @@ pub enum DynSolValue { }, } +impl DynSolValue { + /// Fallible cast to the contents of a variant DynSolValue { + pub fn as_address(&self) -> Option { + match self { + Self::Address(a) => Some(*a), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_bool(&self) -> Option { + match self { + Self::Bool(b) => Some(*b), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_bytes(&self) -> Option<&[u8]> { + match self { + Self::Bytes(b) => Some(b), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_fixed_bytes(&self) -> Option<(&[u8], usize)> { + match self { + Self::FixedBytes(w, size) => Some((w.as_bytes(), *size)), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_int(&self) -> Option<(Word, usize)> { + match self { + Self::Int(w, size) => Some((*w, *size)), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_uint(&self) -> Option<(U256, usize)> { + match self { + Self::Uint(u, size) => Some((*u, *size)), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_function(&self) -> Option<(B160, [u8; 4])> { + match self { + Self::Function(a, f) => Some((*a, *f)), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_str(&self) -> Option<&str> { + match self { + Self::String(s) => Some(s.as_str()), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_tuple(&self) -> Option<&[DynSolValue]> { + match self { + Self::Tuple(t) => Some(t.as_slice()), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_array(&self) -> Option<&[DynSolValue]> { + match self { + Self::Array(a) => Some(a.as_slice()), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_fixed_array(&self) -> Option<&[DynSolValue]> { + match self { + Self::FixedArray(a) => Some(a.as_slice()), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_custom_struct(&self) -> Option<(&str, &[DynSolValue])> { + match self { + Self::CustomStruct { name, tuple } => Some((name.as_str(), tuple.as_slice())), + _ => None, + } + } + + /// Fallible cast to the contents of a variant + pub fn as_custom_value(&self) -> Option<(&str, Word)> { + match self { + Self::CustomValue { name, inner } => Some((name.as_str(), *inner)), + _ => None, + } + } +} + impl From for DynSolValue { fn from(value: B160) -> Self { Self::Address(value) @@ -119,6 +225,3 @@ impl From for DynSolValue { Self::String(value) } } - - - diff --git a/abi/src/dyn_abi/token.rs b/abi/src/dyn_abi/token.rs index 1eb9173a3..bd0a97d8f 100644 --- a/abi/src/dyn_abi/token.rs +++ b/abi/src/dyn_abi/token.rs @@ -5,7 +5,10 @@ use crate::{ /// A dynamic token. Equivalent to an enum over all types implementing /// [`crate::TokenType`] -#[derive(Debug, Clone, PartialEq)] +// 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` +#[derive(Debug, Clone)] pub enum DynToken { /// A single word Word(Word), @@ -15,13 +18,36 @@ pub enum DynToken { DynSeq { /// The contents of the dynamic sequence contents: Vec, - /// The template type of the dynamic sequence + /// The type of the dynamic sequence template: Box, }, /// A packed sequence (string or bytes) PackedSeq(Vec), } +impl PartialEq for DynToken { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Word(l0), Self::Word(r0)) => l0 == r0, + (Self::FixedSeq(l0, l1), Self::FixedSeq(r0, r1)) => l0 == r0 && l1 == r1, + ( + Self::DynSeq { + contents: l_contents, + .. + }, + Self::DynSeq { + contents: r_contents, + .. + }, + ) => l_contents == r_contents, + (Self::PackedSeq(l0), Self::PackedSeq(r0)) => l0 == r0, + _ => false, + } + } +} + +impl Eq for DynToken {} + impl From for DynToken { fn from(value: Word) -> Self { Self::Word(value) @@ -29,7 +55,7 @@ impl From for DynToken { } impl DynToken { - /// Attempt to cast to a word + /// Attempt to cast to a word. pub fn as_word(&self) -> Option { match self { Self::Word(word) => Some(*word), @@ -37,6 +63,30 @@ impl DynToken { } } + /// Fallible cast into a fixed sequence. + pub fn as_fixed_seq(&self) -> Option<(&[DynToken], usize)> { + match self { + Self::FixedSeq(toks, size) => Some((toks.as_slice(), *size)), + _ => None, + } + } + + /// Fallible cast into a dynamic sequence. + pub fn as_dynamic_seq(&self) -> Option<&[DynToken]> { + match self { + Self::DynSeq { contents, .. } => Some(contents.as_slice()), + _ => None, + } + } + + /// Fallible cast into a packed sequence. + pub fn as_packed_seq(&self) -> Option<&[u8]> { + match self { + Self::PackedSeq(bytes) => Some(bytes.as_slice()), + _ => None, + } + } + /// True if the type is dynamic, else false pub fn is_dynamic(&self) -> bool { match self { @@ -65,8 +115,7 @@ impl DynToken { } DynToken::DynSeq { contents, template } => { let mut child = dec.take_indirection()?; - let size = dec.take_u32()? as usize; - + let size = child.take_u32()? as usize; let mut new_toks = Vec::with_capacity(size); for _ in 0..size { let mut t = (**template).clone(); @@ -136,12 +185,12 @@ impl DynToken { DynToken::Word(_) => {} DynToken::FixedSeq(_, _) => { if self.is_dynamic() { - self.encode_sequence(enc); + self.encode_sequence(enc).expect("known to be sequence"); } } DynToken::DynSeq { contents, .. } => { enc.append_seq_len(contents); - self.encode_sequence(enc); + self.encode_sequence(enc).expect("known to be sequence"); } DynToken::PackedSeq(buf) => enc.append_packed_seq(buf), } @@ -150,7 +199,7 @@ impl DynToken { /// Encode this data, if it is a sequence. No-op otherwise /// /// TODO: should this panic otherwise? - pub fn encode_sequence(&self, enc: &mut Encoder) { + pub(crate) fn encode_sequence(&self, enc: &mut Encoder) -> AbiResult<()> { match self { DynToken::FixedSeq(tokens, _) => { let head_words = tokens.iter().map(DynToken::head_words).sum::(); @@ -176,13 +225,18 @@ impl DynToken { } enc.pop_offset(); } - _ => {} + _ => { + return Err(Error::custom( + "Called encode_sequence on non-sequence token", + )) + } } + Ok(()) } /// Decode a sequence from the decoder, populating the data by consuming /// decoder words - pub fn decode_sequence_populate(&mut self, dec: &mut Decoder) -> AbiResult<()> { + pub(crate) fn decode_sequence_populate(&mut self, dec: &mut Decoder) -> AbiResult<()> { match self { DynToken::FixedSeq(buf, _) => { for item in buf.iter_mut() { @@ -192,8 +246,25 @@ impl DynToken { } DynToken::DynSeq { .. } => self.decode_populate(dec), _ => Err(Error::custom( - "Called decode_sequence_populate on non-sequence", + "Called decode_sequence_populate on non-sequence token", )), } } + + /// Encode a single item of this type, as a sequence of length 1 + pub(crate) fn encode_single(&self, enc: &mut Encoder) -> AbiResult<()> { + DynToken::FixedSeq(vec![self.clone()], 1).encode_sequence(enc) + } + + /// Decode a single item of this type, as a sequence of length 1 + pub(crate) fn decode_single_populate(&mut self, dec: &mut Decoder) -> AbiResult<()> { + let mut tok = DynToken::FixedSeq(vec![self.clone()], 1); + tok.decode_sequence_populate(dec)?; + if let DynToken::FixedSeq(mut toks, _) = tok { + *self = toks.drain(..).next().unwrap(); + } else { + unreachable!() + } + Ok(()) + } } diff --git a/abi/src/encoder.rs b/abi/src/encoder.rs index 598db8c75..a150c453b 100644 --- a/abi/src/encoder.rs +++ b/abi/src/encoder.rs @@ -40,6 +40,10 @@ use crate::no_std_prelude::*; use crate::{token::TokenSeq, util::pad_u32, TokenType, Word}; +/// An ABI encoder. This is not intended for public consumption. It should be +/// used only by the token types. If you have found yourself here, you probably +/// want to use the high-level [`SolType`] interface (or its dynamic +/// equivalent) instead. #[derive(Default, Clone, Debug)] pub struct Encoder { buf: Vec, @@ -47,6 +51,7 @@ pub struct Encoder { } impl Encoder { + /// Instantiate a new encoder with a given capacity in words. pub fn with_capacity(size: usize) -> Self { Self { buf: Vec::with_capacity(size + 1), @@ -54,50 +59,55 @@ impl Encoder { } } + /// Finish the encoding process, returning the encoded words pub fn finish(self) -> Vec { self.buf } + /// Finish the encoding process, returning the encoded bytes + pub fn into_bytes(self) -> Vec { + self.buf + .into_iter() + .flat_map(Word::to_fixed_bytes) + .collect() + } + + /// Determine the current suffix offset pub fn suffix_offset(&self) -> u32 { *self.suffix_offset.last().unwrap() } + /// Push a new suffix offset pub fn push_offset(&mut self, words: u32) { self.suffix_offset.push(words * 32); } + /// Pop the last suffix offset pub fn pop_offset(&mut self) -> u32 { self.suffix_offset.pop().unwrap() } - pub fn into_bytes(self) -> Vec { - self.buf - .into_iter() - .flat_map(Word::to_fixed_bytes) - .collect() - } - + /// Bump the suffix offset by a given number of words pub fn bump_offset(&mut self, words: u32) { (*self.suffix_offset.last_mut().unwrap()) += words * 32; } + /// Append a word to the encoder pub fn append_word(&mut self, word: Word) { self.buf.push(word); } + /// Append a pointer to the current suffix offset pub fn append_indirection(&mut self) { self.append_word(pad_u32(self.suffix_offset())); } + /// Append a sequence length pub fn append_seq_len(&mut self, seq: &[T]) { self.append_word(pad_u32(seq.len() as u32)); } - pub fn append_packed_seq(&mut self, bytes: &[u8]) { - self.append_seq_len(bytes); - self.append_bytes(bytes); - } - + /// Append a seqeunce of bytes, padding to the next word fn append_bytes(&mut self, bytes: &[u8]) { let len = (bytes.len() + 31) / 32; for i in 0..len { @@ -117,16 +127,19 @@ impl Encoder { } } + /// Append a sequence of bytes as a packed sequence with a length prefix + pub fn append_packed_seq(&mut self, bytes: &[u8]) { + self.append_seq_len(bytes); + self.append_bytes(bytes); + } + + /// Shortcut for appending a token sequence pub fn append_head_tail(&mut self, token: &T) where T: TokenSeq, { token.encode_sequence(self); } - - pub fn is_params(&self) -> bool { - self.suffix_offset.is_empty() - } } /// Encodes vector of tokens into ABI-compliant vector of bytes. diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 967987a3c..829c496b6 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -114,7 +114,7 @@ //! //! ## Tokenization/Detokenization //! -//! The process of converting from a Rust type to a to a token is called +//! The process of converting from a Rust type to a to an abi token is called //! "Tokenization". Typical users will not access tokenizaiton directly. //! Power users should use the [`SolType::tokenize()`] and //! [`SolType::detokenize()`] methods. @@ -153,7 +153,9 @@ use ethers_primitives::B256; #[cfg(not(feature = "std"))] use no_std_prelude::*; -mod decoder; +pub(crate) mod decoder; +#[doc(hidden)] +pub use decoder::Decoder; pub use decoder::{decode, decode_params, decode_single}; #[cfg(feature = "dynamic")] @@ -161,7 +163,9 @@ mod dyn_abi; #[cfg(feature = "dynamic")] pub use dyn_abi::{parse, DynSolType, DynSolValue, DynToken}; -mod encoder; +pub(crate) mod encoder; +#[doc(hidden)] +pub use encoder::Encoder; pub use encoder::{encode, encode_params, encode_single}; mod token; From b637cc9fb784860f208058fe291dce35bad9a626 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Apr 2023 10:27:41 -0500 Subject: [PATCH 5/9] docs: example in soltype --- abi/src/dyn_abi/mod.rs | 53 ++++++++++++++++++++++--------------- abi/src/dyn_abi/sol_type.rs | 32 +++++++++++++++++++++- abi/src/dyn_abi/token.rs | 4 +-- 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/abi/src/dyn_abi/mod.rs b/abi/src/dyn_abi/mod.rs index a22ba9c2f..9ed8235a6 100644 --- a/abi/src/dyn_abi/mod.rs +++ b/abi/src/dyn_abi/mod.rs @@ -4,6 +4,33 @@ //! intended to be used when the solidity type is not known at compile time. //! This is particularly useful for EIP-712 signing interfaces. //! +//! We **strongly** recommend using the static encoder/decoder when possible. +//! The dyanmic encoder/decoder is significantly more expensive, especially for +//! complex types. It is also significantly more error prone, as the mapping +//! between solidity types and rust types is not enforced by the compiler. +//! +//! ## Example +//! +//! ``` +//! # use ethers_abi_enc::{Decoder, Encoder, DynSolType, DynSolValue, parse}; +//! // parse a type from a string +//! let my_type: DynSolType = parse("uint8[2][]").unwrap(); +//! +//! // set values +//! let uints = DynSolValue::FixedArray(vec![0u8.into(), 1u8.into()]); +//! let my_values = DynSolValue::Array(vec![uints]); +//! +//! // encode +//! let encoded = my_type.encode_single(my_values.clone()).unwrap(); +//! +//! // decode +//! let decoded = my_type.decode_single(&encoded).unwrap(); +//! +//! assert_eq!(decoded, my_values); +//! ``` +//! +//! ## How it works +//! //! The dynamic encodr/decoder is implemented as a set of enums that represent //! solidity types, solidity values (in rust representation form), and ABI //! tokens. Unlike the static encoder, each of these must be instantiated at @@ -25,7 +52,7 @@ //! Detokenizing - `DynSolType` + `DynToken` = `DynSolValue` //! //! Users must manually handle the conversions between [`DynSolValue`] and their -//! own rust types. We provide several From implementations, but they fall +//! own rust types. We provide several `From` implementations, but they fall //! short when dealing with arrays and tuples. We also provide fallible casts //! into the contents of each variant. //! @@ -36,27 +63,9 @@ //! pre-allocate a [`DynToken`] with the same shape as the expected type, and //! empty values. We then populate the empty values with the decoded data. //! -//! This is a significant behavior departure from the static decoder. -//! -//! ## Example -//! -//! ``` -//! # use ethers_abi_enc::{Decoder, Encoder, DynSolType, DynSolValue, parse}; -//! // parse a type from a string -//! let my_type: DynSolType = parse("uint8[2][]").unwrap(); -//! -//! // set values -//! let uints = DynSolValue::FixedArray(vec![0u8.into(), 1u8.into()]); -//! let my_values = DynSolValue::Array(vec![uints]); -//! -//! // encode -//! let encoded = my_type.encode_single(my_values.clone()).unwrap(); -//! -//! // decode -//! let decoded = my_type.decode_single(&encoded).unwrap(); -//! -//! assert_eq!(decoded, my_values); -//! ``` +//! This is a significant behavior departure from the static decoder. We do not +//! recommend using the [`DynToken`] type directly. Instead, we recommend using +//! the encoding and decoding methods on [`DynSolType`]. mod sol_type; pub use sol_type::DynSolType; diff --git a/abi/src/dyn_abi/sol_type.rs b/abi/src/dyn_abi/sol_type.rs index c00b0fbe3..699a719dd 100644 --- a/abi/src/dyn_abi/sol_type.rs +++ b/abi/src/dyn_abi/sol_type.rs @@ -7,7 +7,37 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq)] /// A Dynamic SolType. Equivalent to an enum wrapper around all implementers of -/// [`crate::SolType`] +/// [`crate::SolType`]. This is used to represent solidity types that are not +/// known at compile time. It is used in conjunction with [`DynToken`] and +/// [`DynSolValue`] to allow for dynamic ABI encoding and decoding. +/// +/// Users will generally want to instantiate via the [`crate::dyn_abi::parse`] +/// function. This will parse a string into a [`DynSolType`]. User-defined +/// types be instantiated directly. +/// +/// # Example +/// ``` +/// # use ethers_abi_enc::{DynSolType, DynToken, DynSolValue, AbiResult}; +/// # use ethers_primitives::U256; +/// # pub fn main() -> AbiResult<()> { +/// let my_type = DynSolType::Uint(256); +/// let my_data: DynSolValue = U256::from(183).into(); +/// +/// let encoded = my_type.encode_single(my_data.clone())?; +/// let decoded = my_type.decode_single(&encoded)?; +/// +/// assert_eq!(decoded, my_data); +/// +/// let my_type = DynSolType::Array(Box::new(DynSolType::Uint(256))); +/// let my_data = DynSolValue::Array(vec![my_data.clone()]); +/// +/// let encoded = my_type.encode_single(my_data.clone())?; +/// let decoded = my_type.decode_single(&encoded)?; +/// +/// assert_eq!(decoded, my_data); +/// # Ok(()) +/// # } +/// ``` pub enum DynSolType { /// Address Address, diff --git a/abi/src/dyn_abi/token.rs b/abi/src/dyn_abi/token.rs index bd0a97d8f..a6ba17a04 100644 --- a/abi/src/dyn_abi/token.rs +++ b/abi/src/dyn_abi/token.rs @@ -196,9 +196,7 @@ impl DynToken { } } - /// Encode this data, if it is a sequence. No-op otherwise - /// - /// TODO: should this panic otherwise? + /// Encode this data, if it is a sequence. Error otherwise pub(crate) fn encode_sequence(&self, enc: &mut Encoder) -> AbiResult<()> { match self { DynToken::FixedSeq(tokens, _) => { From 0848f0c75cd4d6ecf1a22537f92a52d4329e1f56 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Apr 2023 11:26:52 -0500 Subject: [PATCH 6/9] readme: todo --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index debbbb376..21c7f7585 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,4 @@ foundry. - Set up branch protection - fix uint CI - maybe: integrate uint CI with local CI? +- fix wasm in uint From 912bd4f94b96c4eaf4471b7e19074dc29246aaa8 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Apr 2023 16:07:58 -0700 Subject: [PATCH 7/9] feature: encode_packed for DynSolValue --- abi/src/dyn_abi/sol_value.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/abi/src/dyn_abi/sol_value.rs b/abi/src/dyn_abi/sol_value.rs index cf931373b..31f3f50b0 100644 --- a/abi/src/dyn_abi/sol_value.rs +++ b/abi/src/dyn_abi/sol_value.rs @@ -150,6 +150,39 @@ impl DynSolValue { _ => None, } } + + /// Encodes the packed value and appends it to the end of a byte array + pub fn encode_packed_to(&self, buf: &mut Vec) { + match self { + DynSolValue::Address(addr) => buf.extend_from_slice(addr.as_bytes()), + DynSolValue::Bool(b) => buf.push(*b as u8), + DynSolValue::Bytes(bytes) => buf.extend_from_slice(bytes), + DynSolValue::FixedBytes(word, size) => buf.extend_from_slice(&word.as_bytes()[..*size]), + DynSolValue::Int(num, size) => buf.extend_from_slice(&num[(32 - *size)..]), + DynSolValue::Uint(num, size) => { + buf.extend_from_slice(&num.to_be_bytes::<32>().as_slice()[(32 - *size)..]) + } + DynSolValue::Function(addr, selector) => { + buf.extend_from_slice(addr.as_bytes()); + buf.extend_from_slice(selector.as_slice()); + } + DynSolValue::String(s) => buf.extend_from_slice(s.as_bytes()), + DynSolValue::Tuple(inner) + | DynSolValue::Array(inner) + | DynSolValue::FixedArray(inner) + | DynSolValue::CustomStruct { tuple: inner, .. } => { + inner.iter().for_each(|v| v.encode_packed_to(buf)); + } + DynSolValue::CustomValue { inner, .. } => buf.extend_from_slice(inner.as_bytes()), + } + } + + /// Encodes the value into a packed byte array + pub fn encode_packed(&self) -> Vec { + let mut buf = Vec::new(); + self.encode_packed_to(&mut buf); + buf + } } impl From for DynSolValue { From 9ede137c5243b68871fdb152fce407095d424fdb Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Apr 2023 16:26:52 -0700 Subject: [PATCH 8/9] refactor: drop function type, use FromStr for DynSolType --- abi/src/dyn_abi/mod.rs | 10 ++--- abi/src/dyn_abi/parser.rs | 23 +++++++++--- abi/src/dyn_abi/sol_type.rs | 28 +------------- abi/src/dyn_abi/sol_value.rs | 72 ++++++++++++++++++++++++++---------- abi/src/errors.rs | 11 ++++++ abi/src/lib.rs | 6 +-- abi/src/sol_type.rs | 45 ---------------------- 7 files changed, 90 insertions(+), 105 deletions(-) diff --git a/abi/src/dyn_abi/mod.rs b/abi/src/dyn_abi/mod.rs index 9ed8235a6..38aa2ae6f 100644 --- a/abi/src/dyn_abi/mod.rs +++ b/abi/src/dyn_abi/mod.rs @@ -12,9 +12,9 @@ //! ## Example //! //! ``` -//! # use ethers_abi_enc::{Decoder, Encoder, DynSolType, DynSolValue, parse}; +//! # use ethers_abi_enc::{Decoder, Encoder, DynSolType, DynSolValue}; //! // parse a type from a string -//! let my_type: DynSolType = parse("uint8[2][]").unwrap(); +//! let my_type: DynSolType = "uint8[2][]".parse().unwrap(); //! //! // set values //! let uints = DynSolValue::FixedArray(vec![0u8.into(), 1u8.into()]); @@ -76,16 +76,16 @@ mod token; pub use token::DynToken; mod parser; -pub use parser::parse; +pub use parser::ParserError; #[cfg(test)] mod test { - use crate::{decoder::Decoder, encoder::Encoder, parse, DynSolValue}; + use crate::{decoder::Decoder, encoder::Encoder, DynSolType, DynSolValue}; #[test] fn simple_e2e() { // parse a type from a string - let my_type = parse("uint8[2][]").unwrap(); + let my_type: DynSolType = "uint8[2][]".parse().unwrap(); // set values let uints = DynSolValue::FixedArray(vec![64u8.into(), 128u8.into()]); diff --git a/abi/src/dyn_abi/parser.rs b/abi/src/dyn_abi/parser.rs index 45c2c68f2..46434a262 100644 --- a/abi/src/dyn_abi/parser.rs +++ b/abi/src/dyn_abi/parser.rs @@ -1,13 +1,19 @@ -use core::num::ParseIntError; +use core::{num::ParseIntError, str::FromStr}; use super::DynSolType; +/// Error parsing a dynamic solidity type #[derive(Debug, Clone, PartialEq)] pub enum ParserError { + /// Tried to parse a list, but the value was not a list NotAList, + /// Unmatched parenthesis UnmatchedParen, + /// Unmatched bracket UnmatchedBracket, + /// Error parsing int ParseInt(ParseIntError), + /// Unknown type in parser input Unknown(String), } @@ -54,7 +60,7 @@ impl<'a> TryFrom<&'a str> for CommaSeparatedList<'a> { } /// Parse a solidity type from a string -pub fn parse(s: &str) -> Result { +pub(crate) fn parse(s: &str) -> Result { let s = s.trim(); if s.ends_with(')') { @@ -91,7 +97,6 @@ pub fn parse(s: &str) -> Result { "bytes" => return Ok(DynSolType::Bytes), "bool" => return Ok(DynSolType::Bool), "string" => return Ok(DynSolType::String), - "function" => return Ok(DynSolType::Function), _ => {} } @@ -102,6 +107,14 @@ pub fn parse(s: &str) -> Result { Err(ParserError::Unknown(s.to_string())) } +impl FromStr for DynSolType { + type Err = crate::Error; + + fn from_str(s: &str) -> crate::AbiResult { + Ok(crate::dyn_abi::parser::parse(s)?) + } +} + #[cfg(test)] mod test { use super::*; @@ -183,11 +196,11 @@ mod test { ); assert_eq!( - parse(r#"tuple(address,function, (bool, (string, uint256)[][3]))[2]"#), + parse(r#"tuple(address,bytes, (bool, (string, uint256)[][3]))[2]"#), Ok(DynSolType::FixedArray( Box::new(DynSolType::Tuple(vec![ DynSolType::Address, - DynSolType::Function, + DynSolType::Bytes, DynSolType::Tuple(vec![ DynSolType::Bool, DynSolType::FixedArray( diff --git a/abi/src/dyn_abi/sol_type.rs b/abi/src/dyn_abi/sol_type.rs index 699a719dd..2089a5679 100644 --- a/abi/src/dyn_abi/sol_type.rs +++ b/abi/src/dyn_abi/sol_type.rs @@ -1,5 +1,3 @@ -use core::str::FromStr; - use crate::{ dyn_abi::{DynSolValue, DynToken}, sol_type, AbiResult, SolType, Word, @@ -59,8 +57,6 @@ pub enum DynSolType { FixedArray(Box, usize), /// Tuple Tuple(Vec), - /// Function - Function, /// User-defined struct CustomStruct { /// Name of the struct @@ -76,21 +72,6 @@ pub enum DynSolType { }, } -impl FromStr for DynSolType { - type Err = crate::Error; - - fn from_str(s: &str) -> AbiResult { - match s { - "address" => Ok(Self::Address), - "bool" => Ok(Self::Bool), - "bytes" => Ok(Self::Bytes), - "function" => Ok(Self::Function), - "string" => Ok(Self::String), - _ => todo!(), - } - } -} - impl DynSolType { /// Dynamic tokenization pub fn tokenize(&self, value: DynSolValue) -> AbiResult { @@ -113,9 +94,6 @@ impl DynSolType { } (DynSolType::Int(_), DynSolValue::Int(word, _)) => Ok(word.into()), (DynSolType::Uint(_), DynSolValue::Uint(num, _)) => Ok(DynToken::Word(num.into())), - (DynSolType::Function, DynSolValue::Function(addr, selector)) => Ok(DynToken::Word( - sol_type::Function::tokenize((addr, selector)).inner(), - )), (DynSolType::String, DynSolValue::String(buf)) => Ok(DynToken::PackedSeq(buf.into())), (DynSolType::Tuple(types), DynSolValue::Tuple(tokens)) => { let tokens = types @@ -245,10 +223,7 @@ impl DynSolType { sol_type::Uint::<256>::detokenize(word.into())?, *size, )), - (DynSolType::Function, DynToken::Word(word)) => { - let detok = sol_type::Function::detokenize(word.into())?; - Ok(DynSolValue::Function(detok.0, detok.1)) - } + (DynSolType::String, DynToken::PackedSeq(buf)) => Ok(DynSolValue::String( sol_type::String::detokenize(buf.into())?, )), @@ -318,7 +293,6 @@ impl DynSolType { DynSolType::FixedBytes(_) => DynToken::Word(Word::default()), DynSolType::Int(_) => DynToken::Word(Word::default()), DynSolType::Uint(_) => DynToken::Word(Word::default()), - DynSolType::Function => DynToken::Word(Word::default()), DynSolType::String => DynToken::PackedSeq(Vec::new()), DynSolType::Tuple(types) => DynToken::FixedSeq( types.iter().map(|t| t.empty_dyn_token()).collect(), diff --git a/abi/src/dyn_abi/sol_value.rs b/abi/src/dyn_abi/sol_value.rs index 31f3f50b0..f06f261f1 100644 --- a/abi/src/dyn_abi/sol_value.rs +++ b/abi/src/dyn_abi/sol_value.rs @@ -19,8 +19,6 @@ pub enum DynSolValue { Int(Word, usize), /// An unsigned integer Uint(U256, usize), - /// A function - Function(B160, [u8; 4]), /// A string String(String), /// A tuple of values @@ -95,14 +93,6 @@ impl DynSolValue { } } - /// Fallible cast to the contents of a variant - pub fn as_function(&self) -> Option<(B160, [u8; 4])> { - match self { - Self::Function(a, f) => Some((*a, *f)), - _ => None, - } - } - /// Fallible cast to the contents of a variant pub fn as_str(&self) -> Option<&str> { match self { @@ -162,10 +152,6 @@ impl DynSolValue { DynSolValue::Uint(num, size) => { buf.extend_from_slice(&num.to_be_bytes::<32>().as_slice()[(32 - *size)..]) } - DynSolValue::Function(addr, selector) => { - buf.extend_from_slice(addr.as_bytes()); - buf.extend_from_slice(selector.as_slice()); - } DynSolValue::String(s) => buf.extend_from_slice(s.as_bytes()), DynSolValue::Tuple(inner) | DynSolValue::Array(inner) @@ -247,14 +233,60 @@ impl_from_uint!(usize); // TODO: more? impl_from_uint!(U256); -impl From<(B160, [u8; 4])> for DynSolValue { - fn from(value: (B160, [u8; 4])) -> Self { - Self::Function(value.0, value.1) - } -} - impl From for DynSolValue { fn from(value: String) -> Self { Self::String(value) } } + +macro_rules! impl_from_tuple { + ($num:expr, $( $ty:ident : $no:tt ),+ $(,)?) => { + impl<$($ty,)+> From<($( $ty, )+)> for DynSolValue + where + $( + $ty: Into, + )+ + { + fn from(value: ($( $ty, )+)) -> Self { + Self::Tuple(vec![$( value.$no.into(), )+]) + } + } + } +} + +impl_from_tuple!(1, A:0, ); +impl_from_tuple!(2, A:0, B:1, ); +impl_from_tuple!(3, A:0, B:1, C:2, ); +impl_from_tuple!(4, A:0, B:1, C:2, D:3, ); +impl_from_tuple!(5, A:0, B:1, C:2, D:3, E:4, ); +impl_from_tuple!(6, A:0, B:1, C:2, D:3, E:4, F:5, ); +impl_from_tuple!(7, A:0, B:1, C:2, D:3, E:4, F:5, G:6, ); +impl_from_tuple!(8, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, ); +impl_from_tuple!(9, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, ); +impl_from_tuple!(10, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, ); +impl_from_tuple!(11, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, ); +impl_from_tuple!(12, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, ); +impl_from_tuple!(13, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, ); +impl_from_tuple!(14, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, ); +impl_from_tuple!(15, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, ); +impl_from_tuple!(16, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, ); +impl_from_tuple!(17, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, Q:16,); +impl_from_tuple!(18, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, Q:16, R:17,); +impl_from_tuple!(19, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, Q:16, R:17, S:18,); +impl_from_tuple!(20, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, Q:16, R:17, S:18, T:19,); +impl_from_tuple!(21, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, Q:16, R:17, S:18, T:19, U:20,); + +impl From> for DynSolValue { + fn from(value: Vec) -> Self { + Self::Array(value) + } +} + +impl From<[T; N]> for DynSolValue +where + T: Into, +{ + fn from(value: [T; N]) -> Self { + Self::Array(value.into_iter().map(|v| v.into()).collect()) + } +} diff --git a/abi/src/errors.rs b/abi/src/errors.rs index de9cdd9ac..c6faadcf8 100644 --- a/abi/src/errors.rs +++ b/abi/src/errors.rs @@ -41,6 +41,10 @@ pub enum Error { /// Other errors. #[cfg_attr(feature = "std", error("{0}"))] Other(Cow<'static, str>), + #[cfg(feature = "dynamic")] + /// Parser Error + #[cfg_attr(feature = "std", error("{0:?}"))] + ParserError(crate::dyn_abi::ParserError), } impl Error { @@ -60,3 +64,10 @@ impl From for Error { Self::FromHexError(value) } } + +#[cfg(feature = "dynamic")] +impl From for Error { + fn from(value: crate::dyn_abi::ParserError) -> Self { + Self::ParserError(value) + } +} diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 829c496b6..90f9fa1e9 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -78,12 +78,12 @@ //! type Gamut = sol! { //! ( //! address, bool[], bytes15[12], uint256, uint24, int8, int56, -//! (function, string, bytes,) +//! (bytes17, string, bytes,) //! ) //! }; //! assert_eq!( //! Gamut::sol_type_name(), -//! "tuple(address,bool[],bytes15[12],uint256,uint24,int8,int56,tuple(function,string,bytes))" +//! "tuple(address,bool[],bytes15[12],uint256,uint24,int8,int56,tuple(bytes17,string,bytes))" //! ); // Amazing! //! //! // `sol!` supports late binding of types, and your own custom types! @@ -161,7 +161,7 @@ pub use decoder::{decode, decode_params, decode_single}; #[cfg(feature = "dynamic")] mod dyn_abi; #[cfg(feature = "dynamic")] -pub use dyn_abi::{parse, DynSolType, DynSolValue, DynToken}; +pub use dyn_abi::{DynSolType, DynSolValue, DynToken, ParserError}; pub(crate) mod encoder; #[doc(hidden)] diff --git a/abi/src/sol_type.rs b/abi/src/sol_type.rs index ee6d04b71..f6a0ead85 100644 --- a/abi/src/sol_type.rs +++ b/abi/src/sol_type.rs @@ -827,48 +827,3 @@ impl_tuple_sol_type!(18, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, impl_tuple_sol_type!(19, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, Q:16, R:17, S:18,); impl_tuple_sol_type!(20, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, Q:16, R:17, S:18, T:19,); impl_tuple_sol_type!(21, A:0, B:1, C:2, D:3, E:4, F:5, G:6, H:7, I:8, J:9, K:10, L:11, M:12, N:13, O:14, P:15, Q:16, R:17, S:18, T:19, U:20,); - -/// Function - `function` -pub struct Function; - -impl SolType for Function { - type RustType = (B160, [u8; 4]); - type TokenType = WordToken; - - fn sol_type_name() -> RustString { - "function".to_string() - } - - fn is_dynamic() -> bool { - false - } - - fn type_check(token: &Self::TokenType) -> AbiResult<()> { - if !crate::decoder::check_fixed_bytes(token.inner(), 24) { - return Err(Self::type_check_fail(token.as_slice())); - } - Ok(()) - } - - fn detokenize(token: Self::TokenType) -> AbiResult { - let t = token.as_slice(); - - let mut address = [0u8; 20]; - let mut selector = [0u8; 4]; - address.copy_from_slice(&t[..20]); - selector.copy_from_slice(&t[20..24]); - Ok((B160(address), selector)) - } - - fn tokenize(rust: Self::RustType) -> Self::TokenType { - let mut word = Word::default(); - word[..20].copy_from_slice(&rust.0[..]); - word[20..24].copy_from_slice(&rust.1[..]); - word.into() - } - - fn encode_packed_to(target: &mut Vec, rust: Self::RustType) { - target.extend_from_slice(&rust.0[..]); - target.extend_from_slice(&rust.1[..]); - } -} From ab2a6f8354f78db57da8a9d4863077f2489eccb6 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 2 Apr 2023 16:33:07 -0700 Subject: [PATCH 9/9] chore: fix no-std compilation --- abi/src/decoder.rs | 8 ++++---- abi/src/errors.rs | 24 ++++++++++++++++++++---- abi/src/lib.rs | 2 -- abi/src/sol_type.rs | 5 +---- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/abi/src/decoder.rs b/abi/src/decoder.rs index 8f1aa3024..d5e632a40 100644 --- a/abi/src/decoder.rs +++ b/abi/src/decoder.rs @@ -79,10 +79,10 @@ pub(crate) fn check_fixed_bytes(word: Word, len: usize) -> bool { pub(crate) fn as_u32(word: Word, type_check: bool) -> AbiResult { if type_check && !check_zeroes(&word.as_slice()[..28]) { - return Err(Error::TypeCheckFail { - data: hex::encode(word), - expected_type: "Solidity pointer (uint32)".to_owned(), - }); + return Err(Error::type_check_fail( + hex::encode(word), + "Solidity pointer (uint32)", + )); } let result = ((word[28] as u32) << 24) diff --git a/abi/src/errors.rs b/abi/src/errors.rs index c6faadcf8..993d1d605 100644 --- a/abi/src/errors.rs +++ b/abi/src/errors.rs @@ -7,10 +7,15 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::no_std_prelude::Cow; #[cfg(feature = "std")] use thiserror::Error; +#[cfg(feature = "std")] +use std::borrow::Cow; + +#[cfg(not(feature = "std"))] +use crate::no_std_prelude::*; + /// ABI result type pub type AbiResult = core::result::Result; @@ -25,9 +30,9 @@ pub enum Error { )] TypeCheckFail { /// Hex-encoded data - data: alloc::string::String, + data: Cow<'static, str>, /// The Solidity type we failed to produce - expected_type: alloc::string::String, + expected_type: Cow<'static, str>, }, #[cfg_attr(feature = "std", error("Buffer overrun in deser"))] /// Overran deser buffer @@ -54,9 +59,20 @@ impl Error { } /// Instantiates a new error with a string - pub fn custom_owned(s: String) -> Self { + pub fn custom_owned(s: alloc::string::String) -> Self { Self::Other(s.into()) } + + /// Instantiates a [`Error::TypeCheckFail`] with the provided data + pub fn type_check_fail( + data: impl Into>, + expected_type: impl Into>, + ) -> Self { + Self::TypeCheckFail { + data: data.into(), + expected_type: expected_type.into(), + } + } } impl From for Error { diff --git a/abi/src/lib.rs b/abi/src/lib.rs index 90f9fa1e9..3673067db 100644 --- a/abi/src/lib.rs +++ b/abi/src/lib.rs @@ -150,8 +150,6 @@ mod no_std_prelude { } use ethers_primitives::B256; -#[cfg(not(feature = "std"))] -use no_std_prelude::*; pub(crate) mod decoder; #[doc(hidden)] diff --git a/abi/src/sol_type.rs b/abi/src/sol_type.rs index f6a0ead85..6d420b435 100644 --- a/abi/src/sol_type.rs +++ b/abi/src/sol_type.rs @@ -177,10 +177,7 @@ pub trait SolType { #[doc(hidden)] fn type_check_fail(data: &[u8]) -> Error { - Error::TypeCheckFail { - data: hex::encode(data), - expected_type: Self::sol_type_name(), - } + Error::type_check_fail(hex::encode(data), Self::sol_type_name()) } /// Encode a single ABI token by wrapping it in a 1-length sequence