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