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}