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 alloy_sol_types::{
16    abi::{
17        self,
18        token::{
19            DynSeqToken,
20            FixedSeqToken,
21            PackedSeqToken,
22            WordToken,
23        },
24    },
25    private::SolTypeValue,
26    sol_data,
27    SolType as AlloySolType,
28};
29use core::ops::Deref;
30use impl_trait_for_tuples::impl_for_tuples;
31use ink_prelude::{
32    borrow::Cow,
33    boxed::Box,
34    string::String,
35    vec::Vec,
36};
37use itertools::Itertools;
38use paste::paste;
39use primitive_types::U256;
40
41use crate::types::Address;
42
43/// A Rust/ink! equivalent of a Solidity ABI type that implements logic for Solidity ABI
44/// decoding.
45///
46/// # Rust/ink! to Solidity ABI type mapping
47///
48/// | Rust/ink! type | Solidity ABI type | Notes |
49/// | -------------- | ----------------- | ----- |
50/// | `bool` | `bool` ||
51/// | `iN` for `N ∈ {8,16,32,64,128}` | `intN` | e.g `i8` ↔ `int8` |
52/// | `uN` for `N ∈ {8,16,32,64,128}` | `uintN` | e.g `u8` ↔ `uint8` |
53/// | `U256` | `uint256` ||
54/// | `String` | `string` ||
55/// | `Box<str>` | `string` ||
56/// | `Address` / `H160` | `address` | `Address` is a type alias for the `H160` type used for addresses in `pallet-revive` |
57/// | `[T; N]` for `const N: usize` | `T[N]` | e.g. `[i8; 64]` ↔ `int8[64]` |
58/// | `Vec<T>` | `T[]` | e.g. `Vec<i8>` ↔ `int8[]` |
59/// | `Box<[T]>` | `T[]` | e.g. `Box<[i8]>` ↔ `int8[]` |
60/// | `SolBytes<u8>` | `bytes1` ||
61/// | `SolBytes<[u8; N]>` for `1 <= N <= 32` | `bytesN` | e.g. `SolBytes<[u8; 32]>` ↔ `bytes32` |
62/// | `SolBytes<Vec<u8>>` | `bytes` ||
63/// | `SolBytes<Box<[u8]>>` | `bytes` ||
64/// | `(T1, T2, T3, ... T12)` | `(U1, U2, U3, ... U12)` | where `T1` ↔ `U1`, ... `T12` ↔ `U12` e.g. `(bool, u8, Address)` ↔ `(bool, uint8, address)` |
65///
66/// Ref: <https://docs.soliditylang.org/en/latest/abi-spec.html#types>
67///
68/// # Note
69///
70/// This trait is sealed and cannot be implemented for types outside `ink_primitives`.
71#[allow(private_bounds)]
72pub trait SolTypeDecode: Sized + private::Sealed {
73    /// Equivalent Solidity ABI type from [`alloy_sol_types`].
74    type AlloyType: AlloySolType;
75
76    /// Solidity ABI decode into this type.
77    fn decode(data: &[u8]) -> Result<Self, alloy_sol_types::Error> {
78        abi::decode::<<Self::AlloyType as AlloySolType>::Token<'_>>(data)
79            .and_then(Self::detokenize)
80    }
81
82    /// Detokenizes this type's value from the given token.
83    fn detokenize(
84        token: <Self::AlloyType as AlloySolType>::Token<'_>,
85    ) -> Result<Self, alloy_sol_types::Error>;
86}
87
88/// A Rust/ink! equivalent of a Solidity ABI type that implements logic for Solidity ABI
89/// encoding.
90///
91/// # Rust/ink! to Solidity ABI type mapping
92///
93/// | Rust/ink! type | Solidity ABI type | Notes |
94/// | -------------- | ----------------- | ----- |
95/// | `bool` | `bool` ||
96/// | `iN` for `N ∈ {8,16,32,64,128}` | `intN` | e.g `i8` ↔ `int8` |
97/// | `uN` for `N ∈ {8,16,32,64,128}` | `uintN` | e.g `u8` ↔ `uint8` |
98/// | `U256` | `uint256` ||
99/// | `String` | `string` ||
100/// | `Box<str>` | `string` ||
101/// | `Address` / `H160` | `address` | `Address` is a type alias for the `H160` type used for addresses in `pallet-revive` |
102/// | `[T; N]` for `const N: usize` | `T[N]` | e.g. `[i8; 64]` ↔ `int8[64]` |
103/// | `Vec<T>` | `T[]` | e.g. `Vec<i8>` ↔ `int8[]` |
104/// | `Box<[T]>` | `T[]` | e.g. `Box<[i8]>` ↔ `int8[]` |
105/// | `SolBytes<u8>` |  `bytes1` ||
106/// | `SolBytes<[u8; N]>` for `1 <= N <= 32` | `bytesN` | e.g. `SolBytes<[u8; 32]>` ↔ `bytes32` |
107/// | `SolBytes<Vec<u8>>` | `bytes` ||
108/// | `SolBytes<Box<[u8]>>` | `bytes` ||
109/// | `(T1, T2, T3, ... T12)` | `(U1, U2, U3, ... U12)` | where `T1` ↔ `U1`, ... `T12` ↔ `U12` e.g. `(bool, u8, Address)` ↔ `(bool, uint8, address)` |
110/// | `&str`, `&mut str` | `string` ||
111/// | `&T`, `&mut T`, `Box<T>` | `T` | e.g. `&i8 ↔ int8` |
112/// | `&[T]`, `&mut [T]` | `T[]` | e.g. `&[i8]` ↔ `int8[]` |
113///
114/// Ref: <https://docs.soliditylang.org/en/latest/abi-spec.html#types>
115///
116/// # Note
117///
118/// This trait is sealed and cannot be implemented for types outside `ink_primitives`.
119#[allow(private_bounds)]
120pub trait SolTypeEncode: private::Sealed {
121    /// Equivalent Solidity ABI type from [`alloy_sol_types`].
122    type AlloyType: AlloySolType;
123
124    /// Solidity ABI encode the value.
125    fn encode(&self) -> Vec<u8> {
126        abi::encode(&self.tokenize())
127    }
128
129    /// Tokenizes the given value into a [`Self::AlloyType`] token.
130    fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_>;
131}
132
133macro_rules! impl_primitive_decode {
134    ($($ty: ty => $sol_ty: ty),+ $(,)*) => {
135        $(
136            impl SolTypeDecode for $ty {
137                type AlloyType = $sol_ty;
138
139                fn detokenize(token: <Self::AlloyType as AlloySolType>::Token<'_>) -> Result<Self, alloy_sol_types::Error> {
140                    Ok(<Self::AlloyType as AlloySolType>::detokenize(token))
141                }
142            }
143        )*
144    };
145}
146
147macro_rules! impl_primitive_encode {
148    ($($ty: ty => $sol_ty: ty),+ $(,)*) => {
149        $(
150            impl SolTypeEncode for $ty where Self: SolTypeValue<$sol_ty> {
151                type AlloyType = $sol_ty;
152
153                fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
154                    <Self::AlloyType as AlloySolType>::tokenize(self)
155                }
156            }
157        )*
158    };
159}
160
161macro_rules! impl_primitive {
162    ($($ty: ty => $sol_ty: ty),+ $(,)*) => {
163        $(
164            impl_primitive_decode!($ty => $sol_ty);
165
166            impl_primitive_encode!($ty => $sol_ty);
167
168            impl private::Sealed for $ty {}
169        )*
170    };
171}
172
173macro_rules! impl_native_int {
174    ($($bits: literal),+$(,)*) => {
175        $(
176            impl_primitive! {
177                // signed
178                paste!([<i $bits>]) => sol_data::Int<$bits>,
179                // unsigned
180                paste!([<u $bits>]) => sol_data::Uint<$bits>,
181            }
182        )*
183    };
184}
185
186impl_native_int!(8, 16, 32, 64, 128);
187
188impl_primitive! {
189    // bool
190    bool => sol_data::Bool,
191    // string
192    String => sol_data::String,
193}
194
195// Rust `Box<str>` (i.e. boxed string slice) <-> Solidity `string`.
196impl SolTypeDecode for Box<str> {
197    type AlloyType = sol_data::String;
198
199    fn detokenize(
200        token: <Self::AlloyType as AlloySolType>::Token<'_>,
201    ) -> Result<Self, alloy_sol_types::Error> {
202        Ok(Box::from(core::str::from_utf8(token.0).map_err(|_| {
203            alloy_sol_types::Error::type_check_fail(token.0, "string")
204        })?))
205    }
206}
207
208impl SolTypeEncode for Box<str> {
209    type AlloyType = sol_data::String;
210
211    fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
212        PackedSeqToken(self.as_bytes())
213    }
214}
215
216impl private::Sealed for Box<str> {}
217
218// Address <-> address
219impl SolTypeDecode for Address {
220    type AlloyType = sol_data::Address;
221
222    fn detokenize(
223        token: <Self::AlloyType as AlloySolType>::Token<'_>,
224    ) -> Result<Self, alloy_sol_types::Error> {
225        // We skip the conversion to `alloy_sol_types::private::Address` which will end up
226        // just taking the last 20 bytes of `alloy_sol_types::abi::token::WordToken` as
227        // well anyway.
228        // Ref: <https://github.com/alloy-rs/core/blob/5ae4fe0b246239602c97cc5a2f2e4bc780e2024a/crates/primitives/src/bits/address.rs#L132-L134>
229        Ok(Address::from_slice(&token.0 .0[12..]))
230    }
231}
232
233impl SolTypeEncode for Address {
234    type AlloyType = sol_data::Address;
235
236    fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
237        // We skip the conversion to `alloy_sol_types::private::Address` which will just
238        // end up doing the conversion below anyway.
239        // Ref: <https://github.com/alloy-rs/core/blob/5ae4fe0b246239602c97cc5a2f2e4bc780e2024a/crates/primitives/src/bits/address.rs#L149-L153>
240        let mut word = [0; 32];
241        word[12..].copy_from_slice(self.0.as_slice());
242        WordToken::from(word)
243    }
244}
245
246impl private::Sealed for Address {}
247
248// U256 <-> uint256
249impl SolTypeDecode for U256 {
250    type AlloyType = sol_data::Uint<256>;
251
252    fn detokenize(
253        token: <Self::AlloyType as AlloySolType>::Token<'_>,
254    ) -> Result<Self, alloy_sol_types::Error> {
255        Ok(U256::from_big_endian(token.0 .0.as_slice()))
256    }
257}
258
259impl SolTypeEncode for U256 {
260    type AlloyType = sol_data::Uint<256>;
261
262    fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
263        // `<Self::AlloyType as AlloySolType>::tokenize(self)` won't work because
264        // `primitive_types::U256` does NOT implement
265        // `Borrow<alloy_sol_types::private::U256>`. And both the `U256` and
266        // `Borrow` are foreign, so we can't just implement it.
267        WordToken::from(self.to_big_endian())
268    }
269}
270
271impl private::Sealed for U256 {}
272
273// Rust array <-> Solidity fixed-sized array (i.e. `T[N]`).
274impl<T: SolTypeDecode, const N: usize> SolTypeDecode for [T; N] {
275    type AlloyType = sol_data::FixedArray<T::AlloyType, N>;
276
277    fn detokenize(
278        token: <Self::AlloyType as AlloySolType>::Token<'_>,
279    ) -> Result<Self, alloy_sol_types::Error> {
280        // Takes advantage of optimized `SolTypeDecode::detokenize` implementations and
281        // skips unnecessary conversions to `T::AlloyType::RustType`.
282        // FIXME: (@davidsemakula) replace with `array::try_map` if it's ever stabilized.
283        // Ref: <https://github.com/rust-lang/rust/issues/79711>
284        // Ref: <https://doc.rust-lang.org/nightly/std/primitive.array.html#method.try_map>
285        token
286            .0
287            .into_iter()
288            .map(<T as SolTypeDecode>::detokenize)
289            .process_results(|iter| iter.collect_array())?
290            .ok_or_else(|| {
291                alloy_sol_types::Error::custom(ink_prelude::format!(
292                    "ABI decoding failed: {}",
293                    Self::AlloyType::SOL_NAME
294                ))
295            })
296    }
297}
298
299impl<T: SolTypeEncode, const N: usize> SolTypeEncode for [T; N] {
300    type AlloyType = sol_data::FixedArray<T::AlloyType, N>;
301
302    fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
303        // Does NOT require `SolValueType<Self::AlloyType>` and instead relies on
304        // `SolTypeEncode::tokenize`.
305        FixedSeqToken(core::array::from_fn(|i| {
306            <T as SolTypeEncode>::tokenize(&self[i])
307        }))
308    }
309}
310
311impl<T: private::Sealed, const N: usize> private::Sealed for [T; N] {}
312
313// Rust `Vec` <-> Solidity dynamic size array (i.e. `T[]`).
314impl<T: SolTypeDecode> SolTypeDecode for Vec<T> {
315    type AlloyType = sol_data::Array<T::AlloyType>;
316
317    fn detokenize(
318        token: <Self::AlloyType as AlloySolType>::Token<'_>,
319    ) -> Result<Self, alloy_sol_types::Error> {
320        // Takes advantage of optimized `SolTypeDecode::detokenize` implementations and
321        // skips unnecessary conversions to `T::AlloyType::RustType`.
322        token
323            .0
324            .into_iter()
325            .map(<T as SolTypeDecode>::detokenize)
326            .collect()
327    }
328}
329
330impl<T: SolTypeEncode> SolTypeEncode for Vec<T> {
331    type AlloyType = sol_data::Array<T::AlloyType>;
332
333    fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
334        // Does NOT require `SolValueType<Self::AlloyType>` and instead relies on
335        // `SolTypeEncode::tokenize`.
336        DynSeqToken(self.iter().map(<T as SolTypeEncode>::tokenize).collect())
337    }
338}
339
340impl<T: private::Sealed> private::Sealed for Vec<T> {}
341
342// Rust `Box<[T]>` (i.e. boxed slice) <-> Solidity dynamic size array (i.e. `T[]`).
343impl<T: SolTypeDecode> SolTypeDecode for Box<[T]> {
344    type AlloyType = sol_data::Array<T::AlloyType>;
345
346    fn detokenize(
347        token: <Self::AlloyType as AlloySolType>::Token<'_>,
348    ) -> Result<Self, alloy_sol_types::Error> {
349        // Takes advantage of optimized `SolTypeDecode::detokenize` implementations and
350        // skips unnecessary conversions to `T::AlloyType::RustType`.
351        token
352            .0
353            .into_iter()
354            .map(<T as SolTypeDecode>::detokenize)
355            .collect()
356    }
357}
358
359impl<T: SolTypeEncode> SolTypeEncode for Box<[T]> {
360    type AlloyType = sol_data::Array<T::AlloyType>;
361
362    fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
363        // Does NOT require `SolValueType<Self::AlloyType>` and instead relies on
364        // `SolTypeEncode::tokenize`.
365        DynSeqToken(self.iter().map(<T as SolTypeEncode>::tokenize).collect())
366    }
367}
368
369impl<T: private::Sealed> private::Sealed for Box<[T]> {}
370
371// We follow the Rust standard library's convention of implementing traits for tuples up
372// to twelve items long.
373// Ref: <https://doc.rust-lang.org/std/primitive.tuple.html#trait-implementations>
374#[impl_for_tuples(12)]
375impl SolTypeDecode for Tuple {
376    for_tuples!( type AlloyType = ( #( Tuple::AlloyType ),* ); );
377
378    #[allow(clippy::unused_unit)]
379    fn detokenize(
380        token: <Self::AlloyType as AlloySolType>::Token<'_>,
381    ) -> Result<Self, alloy_sol_types::Error> {
382        // Takes advantage of optimized `SolTypeDecode::detokenize` implementations and
383        // skips unnecessary conversions to `T::AlloyType::RustType`.
384        Ok(for_tuples! { ( #( <Tuple as SolTypeDecode>::detokenize(token.Tuple)? ),* ) })
385    }
386}
387
388#[impl_for_tuples(12)]
389impl SolTypeEncode for Tuple {
390    for_tuples!( type AlloyType = ( #( Tuple::AlloyType ),* ); );
391
392    #[allow(clippy::unused_unit)]
393    fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
394        // Does NOT require `SolValueType<Self::AlloyType>` and instead relies on
395        // `SolTypeEncode::tokenize`.
396        for_tuples!( ( #( <Tuple as SolTypeEncode>::tokenize(&self.Tuple) ),* ) );
397    }
398}
399
400#[impl_for_tuples(12)]
401impl private::Sealed for Tuple {}
402
403// Implements `SolTypeEncode` for reference types.
404macro_rules! impl_refs_encode {
405    ($([$($gen:tt)*] $ty: ty), +$(,)*) => {
406        $(
407
408            impl<$($gen)* T: SolTypeEncode> SolTypeEncode for $ty {
409                type AlloyType = T::AlloyType;
410
411                fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
412                    <T as SolTypeEncode>::tokenize(self)
413                }
414            }
415
416            impl<$($gen)* T: private::Sealed> private::Sealed for $ty {}
417        )*
418    };
419}
420
421impl_refs_encode! {
422    ['a,] &'a T,
423    ['a,] &'a mut T,
424    [] Box<T>,
425}
426
427impl<T: SolTypeEncode + Clone> SolTypeEncode for Cow<'_, T> {
428    type AlloyType = T::AlloyType;
429
430    fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
431        <T as SolTypeEncode>::tokenize(self.deref())
432    }
433}
434
435impl<T: private::Sealed + Clone> private::Sealed for Cow<'_, T> {}
436
437// Implements `SolTypeEncode` for references to `str` and `[T]` DSTs.
438macro_rules! impl_str_ref_encode {
439    ($($ty: ty),+ $(,)*) => {
440        $(
441            impl SolTypeEncode for $ty {
442                type AlloyType = sol_data::String;
443
444                fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
445                    PackedSeqToken(self.as_bytes())
446                }
447            }
448
449            impl private::Sealed for $ty {}
450        )*
451    };
452}
453
454impl_str_ref_encode!(&str, &mut str);
455
456macro_rules! impl_slice_ref_encode {
457    ($($ty: ty),+ $(,)*) => {
458        $(
459            impl<T: SolTypeEncode> SolTypeEncode for $ty {
460                type AlloyType = sol_data::Array<T::AlloyType>;
461
462                fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
463                    // Does NOT require `SolValueType<Self::AlloyType>` and instead relies on
464                    // `SolTypeEncode::tokenize`.
465                    DynSeqToken(self.iter().map(<T as SolTypeEncode>::tokenize).collect())
466                }
467            }
468
469            impl<T: private::Sealed> private::Sealed for $ty {}
470        )*
471    };
472}
473
474impl_slice_ref_encode!(&[T], &mut [T]);
475
476pub(super) mod private {
477    /// Seals implementations of `SolTypeEncode` and `SolTypeDecode`.
478    pub trait Sealed {}
479}