ink_primitives/sol/
types.rs

1// Copyright (C) ink! contributors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use core::clone::Clone;
16
17use alloy_sol_types::{
18    SolType as AlloySolType,
19    abi,
20    sol_data,
21};
22use impl_trait_for_tuples::impl_for_tuples;
23use ink_prelude::{
24    borrow::{
25        Cow,
26        ToOwned,
27    },
28    boxed::Box,
29    string::String,
30    vec::Vec,
31};
32use itertools::Itertools;
33use paste::paste;
34use primitive_types::U256;
35
36use crate::{
37    sol::{
38        Error,
39        encodable::{
40            DynSizeDefault,
41            Encodable,
42            FixedSizeDefault,
43            TokenOrDefault,
44            Word,
45        },
46        encoder::Encoder,
47        utils::{
48            append_non_empty_member_topic_bytes,
49            non_zero_multiple_of_32,
50        },
51    },
52    types::Address,
53};
54
55/// A Rust/ink! equivalent of a Solidity ABI type that implements logic for Solidity ABI
56/// decoding.
57///
58/// # Rust/ink! to Solidity ABI type mapping
59///
60/// | Rust/ink! type | Solidity ABI type | Notes |
61/// | -------------- | ----------------- | ----- |
62/// | `bool` | `bool` ||
63/// | `iN` for `N ∈ {8,16,32,64,128}` | `intN` | e.g `i8` ↔ `int8` |
64/// | `uN` for `N ∈ {8,16,32,64,128}` | `uintN` | e.g `u8` ↔ `uint8` |
65/// | `U256` | `uint256` ||
66/// | `String` | `string` ||
67/// | `Box<str>` | `string` ||
68/// | `Address` / `H160` | `address` | `Address` is a type alias for the `H160` type used for addresses in `pallet-revive` |
69/// | `[T; N]` for `const N: usize` | `T[N]` | e.g. `[i8; 64]` ↔ `int8[64]` |
70/// | `Vec<T>` | `T[]` | e.g. `Vec<i8>` ↔ `int8[]` |
71/// | `Box<[T]>` | `T[]` | e.g. `Box<[i8]>` ↔ `int8[]` |
72/// | `FixedBytes<N>` for `1 <= N <= 32` | `bytesN` | e.g. `FixedBytes<32>` ↔ `bytes32`, `FixedBytes<N>` is just a newtype wrapper for `[u8; N]` that also implements `From<u8>` |
73/// | `DynBytes` | `bytes` | `DynBytes` is just a newtype wrapper for `Vec<u8>` that also implements `From<Box<[u8]>>` |
74/// | `(T1, T2, T3, ... T12)` | `(U1, U2, U3, ... U12)` | where `T1` ↔ `U1`, ... `T12` ↔ `U12` e.g. `(bool, u8, Address)` ↔ `(bool, uint8, address)` |
75/// | `Option<T>` | `(bool, T)` | e.g. `Option<u8>` ↔ `(bool, uint8)`|
76///
77/// Ref: <https://docs.soliditylang.org/en/latest/abi-spec.html#types>
78///
79/// ## `Option<T>` representation
80///
81/// Rust's `Option<T>` type doesn't have a **semantically** equivalent Solidity ABI type,
82/// because [Solidity enums][sol-enum] are field-less.
83///
84/// So `Option<T>` is mapped to a tuple representation instead (i.e. `(bool, T)`),
85/// because this representation allows preservation of semantic information in Solidity,
86/// by using the `bool` as a "flag" indicating the variant
87/// (i.e. `false` for `None` and `true` for `Some`) such that:
88/// - `Option::None` is mapped to `(false, <default_value>)` where `<default_value>` is
89///   the zero bytes only representation of `T` (e.g. `0u8` for `u8` or `Vec::new()` for
90///   `Vec<T>`)
91/// - `Option::Some(value)` is mapped to `(true, value)`
92///
93/// The resulting type in Solidity can be represented as a struct with a field for the
94/// "flag" and another for the data.
95///
96/// Note that `enum` in Solidity is encoded as `uint8` in [Solidity ABI
97/// encoding][sol-abi-types], while the encoding for `bool` is equivalent to the encoding
98/// of `uint8`, with `true` equivalent to `1` and `false` equivalent to `0`.
99/// Therefore, the `bool` "flag" can be safely interpreted as a `bool` or `enum` (or even
100/// `uint8`) in Solidity code.
101///
102/// [sol-enum]: https://docs.soliditylang.org/en/latest/types.html#enums
103/// [sol-abi-types]: https://docs.soliditylang.org/en/latest/abi-spec.html#mapping-solidity-to-abi-types
104///
105/// # Note
106///
107/// This trait is sealed and cannot be implemented for types outside `ink_primitives`.
108pub trait SolTypeDecode: Sized + private::Sealed {
109    /// Equivalent Solidity ABI type from [`alloy_sol_types`].
110    type AlloyType: AlloySolType;
111
112    /// Solidity ABI decode into this type.
113    fn decode(data: &[u8]) -> Result<Self, Error> {
114        abi::decode::<<Self::AlloyType as AlloySolType>::Token<'_>>(data)
115            .map_err(Error::from)
116            .and_then(Self::detokenize)
117    }
118
119    /// Detokenizes this type's value from the given token.
120    fn detokenize(
121        token: <Self::AlloyType as AlloySolType>::Token<'_>,
122    ) -> Result<Self, Error>;
123}
124
125/// A Rust/ink! equivalent of a Solidity ABI type that implements logic for Solidity ABI
126/// encoding.
127///
128/// # Rust/ink! to Solidity ABI type mapping
129///
130/// | Rust/ink! type | Solidity ABI type | Notes |
131/// | -------------- | ----------------- | ----- |
132/// | `bool` | `bool` ||
133/// | `iN` for `N ∈ {8,16,32,64,128}` | `intN` | e.g `i8` ↔ `int8` |
134/// | `uN` for `N ∈ {8,16,32,64,128}` | `uintN` | e.g `u8` ↔ `uint8` |
135/// | `U256` | `uint256` ||
136/// | `String` | `string` ||
137/// | `Box<str>` | `string` ||
138/// | `Address` / `H160` | `address` | `Address` is a type alias for the `H160` type used for addresses in `pallet-revive` |
139/// | `[T; N]` for `const N: usize` | `T[N]` | e.g. `[i8; 64]` ↔ `int8[64]` |
140/// | `Vec<T>` | `T[]` | e.g. `Vec<i8>` ↔ `int8[]` |
141/// | `Box<[T]>` | `T[]` | e.g. `Box<[i8]>` ↔ `int8[]` |
142/// | `FixedBytes<N>` for `1 <= N <= 32` | `bytesN` | e.g. `FixedBytes<32>` ↔ `bytes32`, `FixedBytes<N>` is just a newtype wrapper for `[u8; N]` |
143/// | `DynBytes` | `bytes` | `DynBytes` is just a newtype wrapper for `Vec<u8>` that also implements `From<Box<[u8]>>` |
144/// | `(T1, T2, T3, ... T12)` | `(U1, U2, U3, ... U12)` | where `T1` ↔ `U1`, ... `T12` ↔ `U12` e.g. `(bool, u8, Address)` ↔ `(bool, uint8, address)` |
145/// | `&str`, `&mut str` | `string` ||
146/// | `&T`, `&mut T`, `Box<T>` | `T` | e.g. `&i8 ↔ int8` |
147/// | `&[T]`, `&mut [T]` | `T[]` | e.g. `&[i8]` ↔ `int8[]` |
148/// | `Option<T>` | `(bool, T)` | e.g. `Option<u8>` ↔ `(bool, uint8)`|
149///
150/// Ref: <https://docs.soliditylang.org/en/latest/abi-spec.html#types>
151///
152/// ## `Option<T>` representation
153///
154/// Rust's `Option<T>` type doesn't have a **semantically** equivalent Solidity ABI type,
155/// because [Solidity enums][sol-enum] are field-less.
156///
157/// So `Option<T>` is mapped to a tuple representation instead (i.e. `(bool, T)`),
158/// because this representation allows preservation of semantic information in Solidity,
159/// by using the `bool` as a "flag" indicating the variant
160/// (i.e. `false` for `None` and `true` for `Some`) such that:
161/// - `Option::None` is mapped to `(false, <default_value>)` where `<default_value>` is
162///   the zero bytes only representation of `T` (e.g. `0u8` for `u8` or `Vec::new()` for
163///   `Vec<T>`)
164/// - `Option::Some(value)` is mapped to `(true, value)`
165///
166/// The resulting type in Solidity can be represented as a struct with a field for the
167/// "flag" and another for the data.
168///
169/// Note that `enum` in Solidity is encoded as `uint8` in [Solidity ABI
170/// encoding][sol-abi-types], while the encoding for `bool` is equivalent to the encoding
171/// of `uint8`, with `true` equivalent to `1` and `false` equivalent to `0`.
172/// Therefore, the `bool` "flag" can be safely interpreted as a `bool` or `enum` (or even
173/// `uint8`) in Solidity code.
174///
175/// [sol-enum]: https://docs.soliditylang.org/en/latest/types.html#enums
176/// [sol-abi-types]: https://docs.soliditylang.org/en/latest/abi-spec.html#mapping-solidity-to-abi-types
177///
178/// # Note
179///
180/// This trait is sealed and cannot be implemented for types outside `ink_primitives`.
181pub trait SolTypeEncode: SolTokenType + private::Sealed {
182    /// Equivalent Solidity ABI type from [`alloy_sol_types`].
183    type AlloyType: AlloySolType;
184
185    /// An encodable representation of the default value for this type.
186    const DEFAULT_VALUE: Self::DefaultType;
187
188    /// Solidity ABI encode the value.
189    fn encode(&self) -> Vec<u8> {
190        let token = self.tokenize();
191        let mut buffer =
192            ink_prelude::vec![0u8; token.total_words().checked_mul(32).unwrap()];
193        let mut encoder = Encoder::new(buffer.as_mut_slice());
194        token.encode(&mut encoder);
195        buffer
196    }
197
198    /// Solidity ABI encode the value into the given buffer, and returns the number of
199    /// bytes written.
200    ///
201    /// # Panics
202    ///
203    /// Panics if the buffer is not large enough.
204    fn encode_to(&self, buffer: &mut [u8]) -> usize {
205        let token = self.tokenize();
206        let mut encoder = Encoder::new(buffer);
207        token.encode(&mut encoder);
208        token.total_words().checked_mul(32).unwrap()
209    }
210
211    /// Tokenizes the given value into a [`Self::AlloyType`] token.
212    fn tokenize(&self) -> Self::TokenType<'_>;
213}
214
215/// Describes a "tokenizable" representation of [`SolTypeEncode`] implementing type.
216///
217/// # Note
218///
219/// The `TokenType` representation is similar to alloy types that implement `Token` and
220/// `TokenSeq` traits, but is instead based on local trait [`Encodable`] which allows us
221/// to implement it for custom abstractions that allow for "default" representations of
222/// [`Self::TokenType`], most notably [`TokenOrDefault`].
223//
224// # Design Notes
225//
226// These abstractions are mainly necessary because the return type of
227// [`alloy_sol_types::SolType::tokenize`] is encumbered by the lifetime of it's input.
228//
229// In the case of a "default" representation, this input would be an owned value defined
230// in [`SolTypeEncode::tokenize`], and thus it's lifetime would be too short for the
231// return type of [`SolTypeEncode::tokenize`] when using a `Token<'a>` based return type
232// (i.e. `'a` would be lifetime of `&self`).
233//
234// Static references as solution are too cumbersome because:
235// - `SolTypeEncode` implementing types are composable (i.e. arrays, `Vec`s and tuples of
236//   `T: SolTypeEncode` implement `SolTypeEncode` generically)
237// - Tokenizable default representations of some types are based on alloy types that use
238//   "interior mutability" (e.g. the tokenizable default for `DynBytes` would be based on
239//   `alloy_primitives::bytes::Bytes`)
240// Ref: <https://doc.rust-lang.org/reference/interior-mutability.html>
241//
242// Lastly, this trait only exists separate from `SolTypeEncode` so that the
243// `TokenType<'enc>` GAT (Generic Associated Type) does NOT have a `where Self: 'a` bound
244// which is too limiting for our use case.
245//
246// See <https://github.com/rust-lang/rust/issues/87479> for details.
247pub trait SolTokenType: private::Sealed {
248    /// The type of an encodable representation of this type.
249    type TokenType<'enc>: Encodable;
250
251    /// The type of an encodable "default" representation of this type.
252    type DefaultType: Encodable;
253}
254
255/// Solidity ABI encode this type as a topic (i.e. an indexed event parameter).
256///
257/// # References
258///
259/// - <https://docs.soliditylang.org/en/latest/abi-spec.html#events>
260/// - <https://docs.soliditylang.org/en/latest/abi-spec.html#indexed-event-encoding>
261///
262/// # Note
263///
264/// This trait is sealed and cannot be implemented for types outside `ink_primitives`.
265//
266// # Design Notes
267//
268// One reason for not relying on `alloy`'s `EventTopic` and `SolEvent`
269// abstractions is that they're tightly coupled with the `alloy_primitives::keccak256`
270// utility, while we need to use the hasher provided by our execution environment.
271//
272// Ref: <https://github.com/alloy-rs/core/blob/10aed0012d59a571f35235a5f9c6ad03076bf62b/crates/primitives/src/utils/mod.rs#L148>
273pub trait SolTopicEncode: private::Sealed {
274    /// Solidity ABI encode the value as a topic (i.e. an indexed event parameter).
275    fn encode_topic<H>(&self, hasher: H) -> [u8; 32]
276    where
277        H: Fn(&[u8], &mut [u8; 32]),
278    {
279        let mut buffer = Vec::with_capacity(self.topic_preimage_size());
280        self.topic_preimage(&mut buffer);
281        let mut output = [0u8; 32];
282        hasher(buffer.as_slice(), &mut output);
283        output
284    }
285
286    /// Encode this type as input bytes for the hasher, when this type is member of a
287    /// complex topic type (e.g. a member of array or struct/tuple).
288    fn topic_preimage(&self, buffer: &mut Vec<u8>);
289
290    /// [`Self::topic_preimage`] equivalent for the default value representation of this
291    /// type.
292    fn default_topic_preimage(buffer: &mut Vec<u8>);
293
294    /// Size in bytes of the [`Self::topic_preimage`] encoding of this type.
295    fn topic_preimage_size(&self) -> usize;
296
297    /// [`Self::topic_preimage_size`] equivalent for the default value representation of
298    /// this type.
299    fn default_topic_preimage_size() -> usize;
300}
301
302macro_rules! impl_primitive_decode {
303    ($($ty: ty => $sol_ty: ty),+ $(,)*) => {
304        $(
305            impl SolTypeDecode for $ty {
306                type AlloyType = $sol_ty;
307
308                fn detokenize(token: <Self::AlloyType as AlloySolType>::Token<'_>) -> Result<Self, Error> {
309                    Ok(<Self::AlloyType as AlloySolType>::detokenize(token))
310                }
311            }
312        )*
313    };
314}
315
316macro_rules! impl_topic_encode_word {
317    ($($ty: ty),+ $(,)*) => {
318        $(
319            impl SolTopicEncode for $ty {
320                fn encode_topic<H>(&self, _: H) -> [u8; 32]
321                where
322                    H: Fn(&[u8], &mut [u8; 32]),
323                {
324                    self.tokenize().0
325                }
326
327                fn topic_preimage(&self, buffer: &mut Vec<u8>) {
328                    buffer.extend(self.tokenize().0);
329                }
330
331                fn default_topic_preimage(buffer: &mut Vec<u8>) {
332                    buffer.extend([0u8; 32]);
333                }
334
335                fn topic_preimage_size(&self) -> usize {
336                    Self::default_topic_preimage_size()
337                }
338
339                fn default_topic_preimage_size() -> usize {
340                    32
341                }
342            }
343        )*
344    };
345}
346
347macro_rules! impl_primitive_encode {
348    ($($ty: ty => $sol_ty: ty),+ $(,)*) => {
349        $(
350            impl SolTypeEncode for $ty {
351                type AlloyType = $sol_ty;
352
353                const DEFAULT_VALUE: Self::DefaultType = FixedSizeDefault::WORD;
354
355                fn tokenize(&self) -> Self::TokenType<'_> {
356                    Word(<Self::AlloyType as AlloySolType>::tokenize(self).0.0)
357                }
358            }
359
360            impl_topic_encode_word!($ty);
361
362            impl SolTokenType for $ty {
363                type TokenType<'enc> = Word;
364
365                type DefaultType = FixedSizeDefault;
366            }
367        )*
368    };
369}
370
371macro_rules! impl_primitive {
372    ($($ty: ty => $sol_ty: ty),+ $(,)*) => {
373        $(
374            impl_primitive_decode!($ty => $sol_ty);
375
376            impl_primitive_encode!($ty => $sol_ty);
377
378            impl private::Sealed for $ty {}
379        )*
380    };
381}
382
383macro_rules! impl_native_int {
384    ($($bits: literal),+$(,)*) => {
385        $(
386            impl_primitive! {
387                // signed
388                paste!([<i $bits>]) => sol_data::Int<$bits>,
389                // unsigned
390                paste!([<u $bits>]) => sol_data::Uint<$bits>,
391            }
392        )*
393    };
394}
395
396impl_native_int!(8, 16, 32, 64, 128);
397
398impl_primitive! {
399    // bool
400    bool => sol_data::Bool,
401}
402
403// Rust `String` <-> Solidity `string`.
404impl_primitive_decode!(String => sol_data::String);
405
406// Implements `SolTypeEncode` and `SolTokenType` for string representations.
407macro_rules! impl_str_encode {
408    ($($ty: ty),+ $(,)*) => {
409        $(
410            impl SolTypeEncode for $ty {
411                type AlloyType = sol_data::String;
412
413                const DEFAULT_VALUE: Self::DefaultType = DynSizeDefault;
414
415                fn tokenize(&self) -> Self::TokenType<'_> {
416                    self.as_bytes()
417                }
418            }
419
420            impl SolTopicEncode for $ty {
421                fn encode_topic<H>(&self, hasher: H) -> [u8; 32]
422                where
423                    H: Fn(&[u8], &mut [u8; 32]),
424                {
425                    let mut output = [0u8; 32];
426                    hasher(self.as_bytes(), &mut output);
427                    output
428                }
429
430                fn topic_preimage(&self, buffer: &mut Vec<u8>) {
431                    append_non_empty_member_topic_bytes(self.as_bytes(), buffer);
432                }
433
434                fn default_topic_preimage(buffer: &mut Vec<u8>) {
435                    buffer.extend([0u8; 32]);
436                }
437
438                fn topic_preimage_size(&self) -> usize {
439                    non_zero_multiple_of_32(self.as_bytes().len())
440                }
441
442                fn default_topic_preimage_size() -> usize {
443                    32
444                }
445            }
446
447            impl SolTokenType for $ty {
448                type TokenType<'enc> = &'enc [u8];
449
450                type DefaultType = DynSizeDefault;
451            }
452
453            impl private::Sealed for $ty {}
454        )*
455    };
456}
457
458impl_str_encode!(String);
459
460// Rust `Box<str>` (i.e. boxed string slice) <-> Solidity `string`.
461impl SolTypeDecode for Box<str> {
462    type AlloyType = sol_data::String;
463
464    fn detokenize(
465        token: <Self::AlloyType as AlloySolType>::Token<'_>,
466    ) -> Result<Self, Error> {
467        Ok(Box::from(core::str::from_utf8(token.0).map_err(|_| Error)?))
468    }
469}
470
471impl_str_encode!(Box<str>);
472
473// Address <-> address
474impl SolTypeDecode for Address {
475    type AlloyType = sol_data::Address;
476
477    fn detokenize(
478        token: <Self::AlloyType as AlloySolType>::Token<'_>,
479    ) -> Result<Self, Error> {
480        // We skip the conversion to `alloy_sol_types::private::Address` which will end up
481        // just taking the last 20 bytes of `alloy_sol_types::abi::token::WordToken` as
482        // well anyway.
483        // Ref: <https://github.com/alloy-rs/core/blob/5ae4fe0b246239602c97cc5a2f2e4bc780e2024a/crates/primitives/src/bits/address.rs#L132-L134>
484        Ok(Address::from_slice(&token.0.0[12..]))
485    }
486}
487
488impl SolTypeEncode for Address {
489    type AlloyType = sol_data::Address;
490
491    const DEFAULT_VALUE: Self::DefaultType = FixedSizeDefault::WORD;
492
493    fn tokenize(&self) -> Self::TokenType<'_> {
494        let mut word = [0; 32];
495        word[12..].copy_from_slice(self.0.as_slice());
496        Word(word)
497    }
498}
499
500impl_topic_encode_word!(Address);
501
502impl SolTokenType for Address {
503    type TokenType<'enc> = Word;
504
505    type DefaultType = FixedSizeDefault;
506}
507
508impl private::Sealed for Address {}
509
510// U256 <-> uint256
511impl SolTypeDecode for U256 {
512    type AlloyType = sol_data::Uint<256>;
513
514    fn detokenize(
515        token: <Self::AlloyType as AlloySolType>::Token<'_>,
516    ) -> Result<Self, Error> {
517        Ok(U256::from_big_endian(token.0.0.as_slice()))
518    }
519}
520
521impl SolTypeEncode for U256 {
522    type AlloyType = sol_data::Uint<256>;
523
524    const DEFAULT_VALUE: Self::DefaultType = FixedSizeDefault::WORD;
525
526    fn tokenize(&self) -> Self::TokenType<'_> {
527        Word(self.to_big_endian())
528    }
529}
530
531impl_topic_encode_word!(U256);
532
533impl SolTokenType for U256 {
534    type TokenType<'enc> = Word;
535
536    type DefaultType = FixedSizeDefault;
537}
538
539impl private::Sealed for U256 {}
540
541// Rust array <-> Solidity fixed-sized array (i.e. `T[N]`).
542impl<T: SolTypeDecode, const N: usize> SolTypeDecode for [T; N] {
543    type AlloyType = sol_data::FixedArray<T::AlloyType, N>;
544
545    fn detokenize(
546        token: <Self::AlloyType as AlloySolType>::Token<'_>,
547    ) -> Result<Self, Error> {
548        // Takes advantage of optimized `SolTypeDecode::detokenize` implementations and
549        // skips unnecessary conversions to `T::AlloyType::RustType`.
550        // FIXME: (@davidsemakula) replace with `array::try_map` if it's ever stabilized.
551        // Ref: <https://github.com/rust-lang/rust/issues/79711>
552        // Ref: <https://doc.rust-lang.org/nightly/std/primitive.array.html#method.try_map>
553        token
554            .0
555            .into_iter()
556            .map(<T as SolTypeDecode>::detokenize)
557            .process_results(|iter| iter.collect_array())?
558            .ok_or(Error)
559    }
560}
561
562impl<T: SolTypeEncode, const N: usize> SolTypeEncode for [T; N] {
563    type AlloyType = sol_data::FixedArray<T::AlloyType, N>;
564
565    const DEFAULT_VALUE: Self::DefaultType = [T::DEFAULT_VALUE; N];
566
567    fn tokenize(&self) -> Self::TokenType<'_> {
568        // Does NOT require `SolTypeValue<Self::AlloyType>` and instead relies on
569        // `SolTypeEncode::tokenize`.
570        core::array::from_fn(|i| <T as SolTypeEncode>::tokenize(&self[i]))
571    }
572}
573
574impl<T: SolTopicEncode, const N: usize> SolTopicEncode for [T; N] {
575    fn topic_preimage(&self, buffer: &mut Vec<u8>) {
576        for item in self {
577            item.topic_preimage(buffer);
578        }
579    }
580
581    fn default_topic_preimage(buffer: &mut Vec<u8>) {
582        // Empty array or array of "empty by default" types is a noop.
583        if N == 0 || Self::default_topic_preimage_size() == 0 {
584            return;
585        }
586
587        for _ in 0..N {
588            T::default_topic_preimage(buffer);
589        }
590    }
591
592    fn topic_preimage_size(&self) -> usize {
593        self.iter().map(T::topic_preimage_size).sum()
594    }
595
596    fn default_topic_preimage_size() -> usize {
597        N * T::default_topic_preimage_size()
598    }
599}
600
601impl<T: SolTokenType, const N: usize> SolTokenType for [T; N] {
602    type TokenType<'enc> = [T::TokenType<'enc>; N];
603
604    type DefaultType = [T::DefaultType; N];
605}
606
607impl<T: private::Sealed, const N: usize> private::Sealed for [T; N] {}
608
609// Rust `Vec` <-> Solidity dynamic size array (i.e. `T[]`).
610impl<T: SolTypeDecode> SolTypeDecode for Vec<T> {
611    type AlloyType = sol_data::Array<T::AlloyType>;
612
613    fn detokenize(
614        token: <Self::AlloyType as AlloySolType>::Token<'_>,
615    ) -> Result<Self, Error> {
616        // Takes advantage of optimized `SolTypeDecode::detokenize` implementations and
617        // skips unnecessary conversions to `T::AlloyType::RustType`.
618        token
619            .0
620            .into_iter()
621            .map(<T as SolTypeDecode>::detokenize)
622            .collect()
623    }
624}
625
626// Implements `SolTypeEncode` and `SolTokenType` for representations of dynamic size
627// collections.
628macro_rules! impl_dyn_collection_encode {
629    ($($ty: ty $([where $($clause:tt)*])?),+ $(,)*) => {
630        $(
631            impl<T: SolTypeEncode> SolTypeEncode for $ty $(where $($clause)*)? {
632                type AlloyType = sol_data::Array<T::AlloyType>;
633
634                const DEFAULT_VALUE: Self::DefaultType = DynSizeDefault;
635
636                fn tokenize(&self) -> Self::TokenType<'_> {
637                    // Does NOT require `SolTypeValue<Self::AlloyType>` and instead relies on
638                    // `SolTypeEncode::tokenize`.
639                    self.iter().map(<T as SolTypeEncode>::tokenize).collect()
640                }
641            }
642
643            impl<T: SolTopicEncode> SolTopicEncode for $ty $(where $($clause)*)? {
644                fn topic_preimage(&self, buffer: &mut Vec<u8>) {
645                    for item in self.iter() {
646                        item.topic_preimage(buffer);
647                    }
648                }
649
650                fn default_topic_preimage(_: &mut Vec<u8>) {}
651
652                fn topic_preimage_size(&self) -> usize {
653                    self.iter().map(T::topic_preimage_size).sum()
654                }
655
656                fn default_topic_preimage_size() -> usize {
657                    0
658                }
659            }
660
661            impl<T: SolTypeEncode> SolTokenType for $ty $(where $($clause)*)? {
662                type TokenType<'enc> = Vec<T::TokenType<'enc>>;
663
664                type DefaultType = DynSizeDefault;
665            }
666
667            impl<T: private::Sealed> private::Sealed for $ty $(where $($clause)*)? {}
668        )*
669    };
670}
671
672impl_dyn_collection_encode!(Vec<T>);
673
674// Rust `Box<[T]>` (i.e. boxed slice) <-> Solidity dynamic size array (i.e. `T[]`).
675impl<T: SolTypeDecode> SolTypeDecode for Box<[T]> {
676    type AlloyType = sol_data::Array<T::AlloyType>;
677
678    fn detokenize(
679        token: <Self::AlloyType as AlloySolType>::Token<'_>,
680    ) -> Result<Self, Error> {
681        // Takes advantage of optimized `SolTypeDecode::detokenize` implementations and
682        // skips unnecessary conversions to `T::AlloyType::RustType`.
683        token
684            .0
685            .into_iter()
686            .map(<T as SolTypeDecode>::detokenize)
687            .collect()
688    }
689}
690
691impl_dyn_collection_encode!(Box<[T]>);
692
693// Rust `Cow<'_, [T]>` (i.e. clone-on-write slice) <-> Solidity dynamic size array (i.e.
694// `T[]`).
695impl<T> SolTypeDecode for Cow<'_, [T]>
696where
697    T: SolTypeDecode + Clone,
698    [T]: ToOwned,
699{
700    type AlloyType = sol_data::Array<T::AlloyType>;
701
702    fn detokenize(
703        token: <Self::AlloyType as AlloySolType>::Token<'_>,
704    ) -> Result<Self, Error> {
705        // Takes advantage of optimized `SolTypeDecode::detokenize` implementations and
706        // skips unnecessary conversions to `T::AlloyType::RustType`.
707        token
708            .0
709            .into_iter()
710            .map(<T as SolTypeDecode>::detokenize)
711            .collect()
712    }
713}
714
715impl_dyn_collection_encode!(Cow<'_, [T]> [where T: Clone, [T]: ToOwned]);
716
717// We follow the Rust standard library's convention of implementing traits for tuples up
718// to twelve items long.
719// Ref: <https://doc.rust-lang.org/std/primitive.tuple.html#trait-implementations>
720#[impl_for_tuples(1, 12)]
721impl SolTypeDecode for Tuple {
722    for_tuples!( type AlloyType = ( #( Tuple::AlloyType ),* ); );
723
724    fn detokenize(
725        token: <Self::AlloyType as AlloySolType>::Token<'_>,
726    ) -> Result<Self, Error> {
727        // Takes advantage of optimized `SolTypeDecode::detokenize` implementations and
728        // skips unnecessary conversions to `T::AlloyType::RustType`.
729        Ok(for_tuples! { ( #( <Tuple as SolTypeDecode>::detokenize(token.Tuple)? ),* ) })
730    }
731}
732
733#[impl_for_tuples(1, 12)]
734impl SolTypeEncode for Tuple {
735    for_tuples!( type AlloyType = ( #( Tuple::AlloyType ),* ); );
736
737    const DEFAULT_VALUE: Self::DefaultType =
738        (for_tuples! { #( Tuple::DEFAULT_VALUE ),* });
739
740    fn tokenize(&self) -> Self::TokenType<'_> {
741        // Does NOT require `SolTypeValue<Self::AlloyType>` and instead relies on
742        // `SolTypeEncode::tokenize`.
743        for_tuples!( ( #( <Tuple as SolTypeEncode>::tokenize(&self.Tuple) ),* ) );
744    }
745}
746
747#[impl_for_tuples(1, 12)]
748impl SolTopicEncode for Tuple {
749    fn topic_preimage(&self, buffer: &mut Vec<u8>) {
750        for_tuples!(
751            #(
752                <Tuple as SolTopicEncode>::topic_preimage(&self.Tuple, buffer);
753            )*
754        );
755    }
756
757    fn default_topic_preimage(buffer: &mut Vec<u8>) {
758        for_tuples!(
759            #(
760                <Tuple as SolTopicEncode>::default_topic_preimage(buffer);
761            )*
762        );
763    }
764
765    fn topic_preimage_size(&self) -> usize {
766        for_tuples!( ( #( <Tuple as SolTopicEncode>::topic_preimage_size(&self.Tuple) )+* ) );
767    }
768
769    fn default_topic_preimage_size() -> usize {
770        for_tuples!( ( #( <Tuple as SolTopicEncode>::default_topic_preimage_size() )+* ) );
771    }
772}
773
774// Optimized implementations for unit (i.e. `()`).
775impl SolTypeDecode for () {
776    type AlloyType = ();
777
778    fn decode(_: &[u8]) -> Result<Self, Error> {
779        // NOTE: Solidity ABI decoding doesn't validate input length.
780        Ok(())
781    }
782
783    fn detokenize(
784        _: <Self::AlloyType as AlloySolType>::Token<'_>,
785    ) -> Result<Self, Error> {
786        Ok(())
787    }
788}
789
790impl SolTypeEncode for () {
791    type AlloyType = ();
792    const DEFAULT_VALUE: Self::DefaultType = ();
793
794    fn encode(&self) -> Vec<u8> {
795        Vec::new()
796    }
797
798    fn tokenize(&self) -> Self::TokenType<'_> {}
799}
800
801impl SolTopicEncode for () {
802    fn encode_topic<H>(&self, _: H) -> [u8; 32]
803    where
804        H: Fn(&[u8], &mut [u8; 32]),
805    {
806        [0u8; 32]
807    }
808
809    fn topic_preimage(&self, buffer: &mut Vec<u8>) {
810        Self::default_topic_preimage(buffer);
811    }
812
813    fn default_topic_preimage(_: &mut Vec<u8>) {}
814
815    fn topic_preimage_size(&self) -> usize {
816        Self::default_topic_preimage_size()
817    }
818
819    fn default_topic_preimage_size() -> usize {
820        0
821    }
822}
823
824// `impl-trait-for-tuples` doesn't support GATs yet, so we fallback to a declarative macro
825// for `SolTokenType`.
826//
827// Ref: <https://github.com/bkchr/impl-trait-for-tuples/issues/11>
828macro_rules! impl_sol_token_type {
829    ( $($ty: ident),* ) => {
830        impl<$( $ty: SolTokenType, )*> SolTokenType for ( $( $ty, )* ) {
831            type TokenType<'enc> = ( $( $ty ::TokenType<'enc>, )* );
832
833            type DefaultType = ( $( $ty ::DefaultType, )* );
834        }
835    };
836}
837
838impl_all_tuples!(impl_sol_token_type);
839
840#[impl_for_tuples(12)]
841impl private::Sealed for Tuple {}
842
843// Rust `Option<T>` <-> Solidity `(bool, T)`.
844impl<T: SolTypeDecode> SolTypeDecode for Option<T> {
845    type AlloyType = (sol_data::Bool, T::AlloyType);
846
847    fn detokenize(
848        token: <Self::AlloyType as AlloySolType>::Token<'_>,
849    ) -> Result<Self, Error> {
850        let flag = bool::detokenize(token.0)?;
851        let value = T::detokenize(token.1)?;
852        Ok(if flag { Some(value) } else { None })
853    }
854}
855
856impl<T: SolTypeEncode> SolTypeEncode for Option<T> {
857    type AlloyType = (sol_data::Bool, T::AlloyType);
858
859    const DEFAULT_VALUE: Self::DefaultType = (FixedSizeDefault::WORD, T::DEFAULT_VALUE);
860
861    fn tokenize(&self) -> Self::TokenType<'_> {
862        match self {
863            None => (false.tokenize(), TokenOrDefault::Default(T::DEFAULT_VALUE)),
864            Some(value) => (true.tokenize(), TokenOrDefault::Token(value.tokenize())),
865        }
866    }
867}
868
869impl<T: SolTopicEncode> SolTopicEncode for Option<T> {
870    fn topic_preimage(&self, buffer: &mut Vec<u8>) {
871        // `bool` variant encoded bytes.
872        buffer.extend(self.is_some().tokenize().0);
873        // "Actual value" encoded bytes.
874        match self {
875            None => T::default_topic_preimage(buffer),
876            Some(value) => value.topic_preimage(buffer),
877        };
878    }
879
880    fn default_topic_preimage(buffer: &mut Vec<u8>) {
881        Self::topic_preimage(&None::<T>, buffer);
882    }
883
884    fn topic_preimage_size(&self) -> usize {
885        32 + match self {
886            None => T::default_topic_preimage_size(),
887            Some(value) => value.topic_preimage_size(),
888        }
889    }
890
891    fn default_topic_preimage_size() -> usize {
892        32 + T::default_topic_preimage_size()
893    }
894}
895
896impl<T: SolTokenType> SolTokenType for Option<T> {
897    type TokenType<'enc> = (Word, TokenOrDefault<T::TokenType<'enc>, T::DefaultType>);
898
899    type DefaultType = (FixedSizeDefault, T::DefaultType);
900}
901
902impl<T: private::Sealed> private::Sealed for Option<T> {}
903
904// Implements `SolTypeEncode` for reference types and smart pointers.
905macro_rules! impl_refs_encode {
906    ($($ty: ty $([where $($clause:tt)*])?), +$(,)*) => {
907        $(
908            impl<T: SolTypeEncode> SolTypeEncode for $ty $(where $($clause)*)? {
909                type AlloyType = T::AlloyType;
910
911                const DEFAULT_VALUE: Self::DefaultType = T::DEFAULT_VALUE;
912
913                fn tokenize(&self) -> Self::TokenType<'_> {
914                    <T as SolTypeEncode>::tokenize(self)
915                }
916            }
917
918            impl<T: SolTopicEncode> SolTopicEncode for $ty $(where $($clause)*)? {
919                fn encode_topic<H>(&self, hasher: H) -> [u8; 32]
920                where
921                    H: Fn(&[u8], &mut [u8; 32]),
922                {
923                    T::encode_topic(self, hasher)
924                }
925
926                fn topic_preimage(&self, buffer: &mut Vec<u8>) {
927                    T::topic_preimage(self, buffer);
928                }
929
930                fn default_topic_preimage(buffer: &mut Vec<u8>) {
931                    T::default_topic_preimage(buffer);
932                }
933
934                fn topic_preimage_size(&self) -> usize {
935                    T::topic_preimage_size(self)
936                }
937
938                fn default_topic_preimage_size() -> usize {
939                    T::default_topic_preimage_size()
940                }
941            }
942
943            impl<T: SolTokenType> SolTokenType for $ty $(where $($clause)*)? {
944                type TokenType<'enc> = T::TokenType<'enc>;
945
946                type DefaultType = T::DefaultType;
947            }
948
949            impl<T: private::Sealed> private::Sealed for $ty $(where $($clause)*)? {}
950        )*
951    };
952}
953
954impl_refs_encode! {
955    &T,
956    &mut T,
957    Box<T>,
958    Cow<'_, T> [where T: Clone],
959}
960
961// Implements `SolTypeEncode` for references and smart pointers to `str`.
962impl_str_encode!(&str, &mut str, Cow<'_, str>);
963
964// Implements `SolTypeEncode` for references to `[T]` DSTs.
965impl_dyn_collection_encode!(&[T], &mut [T]);
966
967pub(super) mod private {
968    /// Seals implementations of `SolTypeEncode` and `SolTypeDecode`.
969    pub trait Sealed {}
970}