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}