alloy_dyn_abi/dynamic/
token.rs

1use crate::{Decoder, DynSolValue, Error, Result, Word};
2use alloc::{borrow::Cow, boxed::Box, vec::Vec};
3use alloy_primitives::try_vec;
4use alloy_sol_types::abi::token::{PackedSeqToken, Token, WordToken};
5
6/// A dynamic token.
7///
8/// Equivalent to an enum over all types implementing [`Token`].
9// NOTE: do not derive `Hash` for this type. The derived version is not
10// compatible with the current `PartialEq` implementation. If manually
11// implementing `Hash`, ignore the `template` prop in the `DynSeq` variant
12#[derive(Clone, Debug)]
13pub enum DynToken<'a> {
14    /// A single word.
15    Word(Word),
16    /// A Fixed Sequence.
17    FixedSeq(Cow<'a, [DynToken<'a>]>, usize),
18    /// A dynamic-length sequence.
19    DynSeq {
20        /// The contents of the dynamic sequence.
21        contents: Cow<'a, [DynToken<'a>]>,
22        /// The type template of the dynamic sequence.
23        /// This is used only when decoding. It indicates what the token type
24        /// of the sequence is. During tokenization of data, the type of the
25        /// contents is known, so this is not needed.
26        #[doc(hidden)]
27        template: Option<Box<DynToken<'a>>>,
28    },
29    /// A packed sequence (string or bytes).
30    PackedSeq(&'a [u8]),
31}
32
33impl<T: Into<Word>> From<T> for DynToken<'_> {
34    #[inline]
35    fn from(value: T) -> Self {
36        Self::Word(value.into())
37    }
38}
39
40impl PartialEq<DynToken<'_>> for DynToken<'_> {
41    #[inline]
42    fn eq(&self, other: &DynToken<'_>) -> bool {
43        match (self, other) {
44            (Self::Word(l0), DynToken::Word(r0)) => l0 == r0,
45            (Self::FixedSeq(l0, l1), DynToken::FixedSeq(r0, r1)) => l0 == r0 && l1 == r1,
46            (
47                Self::DynSeq { contents: l_contents, .. },
48                DynToken::DynSeq { contents: r_contents, .. },
49            ) => l_contents == r_contents,
50            (Self::PackedSeq(l0), DynToken::PackedSeq(r0)) => l0 == r0,
51            _ => false,
52        }
53    }
54}
55
56impl Eq for DynToken<'_> {}
57
58impl<'a> DynToken<'a> {
59    /// Calculate the minimum number of words required to encode this token.
60    pub fn minimum_words(&self) -> usize {
61        match self {
62            DynToken::Word(_) => 1,
63            DynToken::PackedSeq(_) => 1,
64            DynToken::FixedSeq(contents, _) => {
65                contents.iter().map(Self::minimum_words).sum::<usize>()
66            }
67            DynToken::DynSeq { .. } => 1,
68        }
69    }
70
71    /// Instantiate a DynToken from a fixed sequence of values.
72    #[inline]
73    pub fn from_fixed_seq(seq: &'a [DynSolValue]) -> Self {
74        let tokens = seq.iter().map(DynSolValue::tokenize).collect();
75        Self::FixedSeq(Cow::Owned(tokens), seq.len())
76    }
77
78    /// Instantiate a DynToken from a dynamic sequence of values.
79    #[inline]
80    pub fn from_dyn_seq(seq: &'a [DynSolValue]) -> Self {
81        let tokens = seq.iter().map(DynSolValue::tokenize).collect();
82        Self::DynSeq { contents: Cow::Owned(tokens), template: None }
83    }
84
85    /// Attempt to cast to a word.
86    #[inline]
87    pub const fn as_word(&self) -> Option<Word> {
88        match self {
89            Self::Word(word) => Some(*word),
90            _ => None,
91        }
92    }
93
94    /// Fallible cast into a fixed sequence.
95    #[inline]
96    pub fn as_fixed_seq(&self) -> Option<(&[Self], usize)> {
97        match self {
98            Self::FixedSeq(tokens, size) => Some((tokens, *size)),
99            _ => None,
100        }
101    }
102
103    /// Fallible cast into a dynamic sequence.
104    #[inline]
105    pub fn as_dynamic_seq(&self) -> Option<&[Self]> {
106        match self {
107            Self::DynSeq { contents, .. } => Some(contents),
108            _ => None,
109        }
110    }
111
112    /// Fallible cast into a sequence, dynamic or fixed-size
113    #[inline]
114    pub fn as_token_seq(&self) -> Option<&[Self]> {
115        match self {
116            Self::FixedSeq(contents, _) | Self::DynSeq { contents, .. } => Some(contents),
117            _ => None,
118        }
119    }
120
121    /// Fallible cast into a packed sequence.
122    #[inline]
123    pub const fn as_packed_seq(&self) -> Option<&[u8]> {
124        match self {
125            Self::PackedSeq(bytes) => Some(bytes),
126            _ => None,
127        }
128    }
129
130    /// True if the type is dynamic, else false.
131    #[inline]
132    pub fn is_dynamic(&self) -> bool {
133        match self {
134            Self::Word(_) => false,
135            Self::FixedSeq(inner, _) => inner.iter().any(Self::is_dynamic),
136            Self::DynSeq { .. } | Self::PackedSeq(_) => true,
137        }
138    }
139
140    /// Decodes from a decoder, populating the structure with the decoded data.
141    #[inline]
142    pub(crate) fn decode_populate(&mut self, dec: &mut Decoder<'a>) -> Result<()> {
143        match self {
144            Self::Word(w) => *w = WordToken::decode_from(dec)?.0,
145            Self::FixedSeq(..) => {
146                let dynamic = self.is_dynamic();
147                let mut child = if dynamic { dec.take_indirection() } else { dec.raw_child() }?;
148
149                self.decode_sequence_populate(&mut child)?;
150
151                if !dynamic {
152                    dec.take_offset_from(&child);
153                }
154            }
155            Self::DynSeq { contents, template } => {
156                let mut child = dec.take_indirection()?;
157                let size = child.take_offset()?;
158                if size == 0 {
159                    // should already be empty from `empty_dyn_token`
160                    debug_assert!(contents.is_empty());
161                    return Ok(());
162                }
163
164                // This expect is safe because this is only invoked after
165                // `empty_dyn_token()` which always sets template
166                let template = template.take().expect("no template for dynamic sequence");
167
168                // This appears to be an unclarity in the Solidity spec. The
169                // spec specifies that offsets are relative to the beginning of
170                // `enc(X)`. But known-good test vectors have it relative to the
171                // word AFTER the array size
172                let mut child = child.raw_child()?;
173
174                // Check that the decoder contains enough words to decode the
175                // sequence. Each item in the sequence is at least one word, so
176                // the remaining words must be at least the size of the sequence
177                if child.remaining_words() < template.minimum_words() * size {
178                    return Err(alloy_sol_types::Error::Overrun.into());
179                }
180
181                let mut new_tokens = if size == 1 {
182                    // re-use the box allocation
183                    unsafe { Vec::from_raw_parts(Box::into_raw(template), 1, 1) }
184                } else {
185                    try_vec![*template; size]?
186                };
187
188                for t in &mut new_tokens {
189                    t.decode_populate(&mut child)?;
190                }
191
192                *contents = new_tokens.into();
193            }
194            Self::PackedSeq(buf) => *buf = PackedSeqToken::decode_from(dec)?.0,
195        }
196        Ok(())
197    }
198
199    /// Decode a sequence from the decoder, populating the data by consuming
200    /// decoder words.
201    #[inline]
202    pub(crate) fn decode_sequence_populate(&mut self, dec: &mut Decoder<'a>) -> Result<()> {
203        match self {
204            Self::FixedSeq(buf, size) => {
205                buf.to_mut().iter_mut().take(*size).try_for_each(|item| item.decode_populate(dec))
206            }
207            Self::DynSeq { .. } => self.decode_populate(dec),
208            _ => Err(Error::custom("Called decode_sequence_populate on non-sequence token")),
209        }
210    }
211
212    /// Decode a single item of this type, as a sequence of length 1.
213    #[inline]
214    pub(crate) fn decode_single_populate(&mut self, dec: &mut Decoder<'a>) -> Result<()> {
215        // This is what
216        // `Self::FixedSeq(vec![self.clone()], 1).decode_populate()`
217        // would do, so we skip the allocation.
218        self.decode_populate(dec)
219    }
220}