ink_primitives/
sol.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
15//! Abstractions for implementing Solidity ABI encoding/decoding for arbitrary Rust types.
16
17#[macro_use]
18mod macros;
19
20mod bytes;
21mod encodable;
22mod error;
23mod params;
24mod result;
25mod types;
26mod utils;
27
28#[cfg(test)]
29mod tests;
30
31use core::ops::Deref;
32
33use alloy_sol_types::SolType as AlloySolType;
34use impl_trait_for_tuples::impl_for_tuples;
35use ink_prelude::{
36    borrow::Cow,
37    boxed::Box,
38    string::String,
39    vec::Vec,
40};
41use itertools::Itertools;
42use primitive_types::{
43    H256,
44    U256,
45};
46use sp_weights::Weight;
47
48pub use self::{
49    bytes::{
50        ByteSlice,
51        DynBytes,
52        FixedBytes,
53    },
54    error::{
55        SolErrorDecode,
56        SolErrorEncode,
57    },
58    params::{
59        SolParamsDecode,
60        SolParamsEncode,
61    },
62    result::{
63        SolResultDecode,
64        SolResultDecodeError,
65    },
66    types::{
67        SolTopicEncode,
68        SolTypeDecode,
69        SolTypeEncode,
70    },
71};
72
73use crate::types::{
74    AccountId,
75    Address,
76    Hash,
77};
78
79/// Maps an arbitrary Rust/ink! type to a Solidity ABI type equivalent for Solidity
80/// ABI decoding.
81///
82/// # Note
83///
84/// Implementing this trait entails:
85/// - Declaring the equivalent Solidity ABI type via the `SolType` associated type. See
86///   the [docs for sealed `SolTypeDecode` trait][SolTypeDecode] for a table of Rust/ink!
87///   primitive types mapped to their equivalent Solidity ABI type.
88/// - Implementing the `from_sol_type` method which defines how to convert from the
89///   Solidity ABI representation (i.e. `Self::SolType`) to this type.
90///
91/// # Example
92///
93/// ```
94/// use ink_primitives::{
95///     sol::Error,
96///     SolDecode,
97/// };
98///
99/// // Example arbitrary type.
100/// struct MyType {
101///     size: u8,
102///     status: bool,
103/// }
104///
105/// // `SolDecode` implementation/mapping.
106/// impl SolDecode for MyType {
107///     type SolType = (u8, bool);
108///
109///     fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
110///         Ok(Self {
111///             size: value.0,
112///             status: value.1,
113///         })
114///     }
115/// }
116/// ```
117pub trait SolDecode: Sized {
118    /// Equivalent Solidity ABI type representation.
119    type SolType: SolTypeDecode;
120
121    /// Name of equivalent Solidity ABI type.
122    const SOL_NAME: &'static str =
123        <<Self::SolType as SolTypeDecode>::AlloyType as AlloySolType>::SOL_NAME;
124
125    /// Solidity ABI decode into this type.
126    fn decode(data: &[u8]) -> Result<Self, Error> {
127        <Self::SolType as SolTypeDecode>::decode(data).and_then(Self::from_sol_type)
128    }
129
130    /// Converts to `Self` from `Self::SolType`.
131    fn from_sol_type(value: Self::SolType) -> Result<Self, Error>;
132}
133
134/// Maps an arbitrary Rust/ink! type to a Solidity ABI type equivalent for Solidity
135/// ABI encoding.
136///
137/// # Note
138///
139/// Implementing this trait entails:
140/// - Declaring the equivalent Solidity ABI type via the `SolType` associated type. See
141///   the [docs for sealed `SolTypeEncode` trait][SolTypeEncode] for a table of Rust/ink!
142///   primitive types mapped to their equivalent Solidity ABI type.
143/// - Implementing the `to_sol_type` method which defines how to convert (preferably via a
144///   borrow) from `&self` to `&Self::SolType` (i.e. the Solidity ABI representation).
145///
146/// # Example
147///
148/// ```
149/// use ink_primitives::SolEncode;
150///
151/// // Example arbitrary type.
152/// struct MyType {
153///     size: u8,
154///     status: bool,
155/// }
156///
157/// // `SolEncode` implementation/mapping.
158/// impl<'a> SolEncode<'a> for MyType {
159///     // NOTE: Prefer reference based representation for better performance.
160///     type SolType = (&'a u8, &'a bool);
161///
162///     fn to_sol_type(&'a self) -> Self::SolType {
163///         (&self.size, &self.status)
164///     }
165/// }
166/// ```
167pub trait SolEncode<'a> {
168    /// Equivalent Solidity ABI type representation.
169    ///
170    /// # Note
171    ///
172    /// Prefer reference based representation for better performance.
173    type SolType: SolTypeEncode + SolTopicEncode;
174
175    /// Name of equivalent Solidity ABI type.
176    const SOL_NAME: &'static str =
177        <<Self::SolType as SolTypeEncode>::AlloyType as AlloySolType>::SOL_NAME;
178
179    /// Whether the ABI encoded size is dynamic.
180    #[doc(hidden)]
181    const DYNAMIC: bool =
182        <<Self::SolType as SolTypeEncode>::AlloyType as AlloySolType>::DYNAMIC;
183
184    /// Solidity ABI encode the value.
185    fn encode(&'a self) -> Vec<u8> {
186        <Self::SolType as SolTypeEncode>::encode(&self.to_sol_type())
187    }
188
189    /// Solidity ABI encode the value as a topic (i.e. an indexed event parameter).
190    fn encode_topic<H>(&'a self, hasher: H) -> [u8; 32]
191    where
192        H: Fn(&[u8], &mut [u8; 32]),
193    {
194        <Self::SolType as SolTopicEncode>::encode_topic(&self.to_sol_type(), hasher)
195    }
196
197    /// Converts from `Self` to `Self::SolType` via either a borrow (if possible), or
198    /// a possibly expensive conversion otherwise.
199    fn to_sol_type(&'a self) -> Self::SolType;
200}
201
202/// Solidity ABI encode the given value as a parameter sequence.
203///
204/// # Note
205///
206/// - `T` must be a tuple type where each member implements [`SolEncode`].
207/// - The result can be different from [`SolEncode::encode`] for the given tuple because
208///   this function always returns the encoded data in place, even for tuples containing
209///   dynamic types (i.e. no offset is included for dynamic tuples).
210///
211/// This function is a convenience wrapper for [`SolParamsEncode::encode`].
212pub fn encode_sequence<T: for<'a> SolParamsEncode<'a>>(value: &T) -> Vec<u8> {
213    SolParamsEncode::encode(value)
214}
215
216/// Solidity ABI decode the given data as a parameter sequence.
217///
218/// # Note
219///
220/// - `T` must be a tuple type where each member implements [`SolDecode`].
221/// - See notes for [`encode_sequence`] for the difference between this function and
222///   [`SolDecode::decode`] for the given tuple.
223pub fn decode_sequence<T: SolParamsDecode>(data: &[u8]) -> Result<T, Error> {
224    SolParamsDecode::decode(data)
225}
226
227/// Solidity ABI encoding/decoding error.
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub struct Error;
230
231impl From<alloy_sol_types::Error> for Error {
232    fn from(_: alloy_sol_types::Error) -> Self {
233        Self
234    }
235}
236
237impl core::fmt::Display for Error {
238    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
239        f.write_str("Solidity ABI encode/decode error")
240    }
241}
242
243macro_rules! impl_primitive_decode {
244    ($($ty: ty),+ $(,)*) => {
245        $(
246            impl SolDecode for $ty {
247                type SolType = $ty;
248
249                fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
250                    Ok(value)
251                }
252            }
253        )*
254    };
255}
256
257macro_rules! impl_primitive_encode {
258    ($($ty: ty),+ $(,)*) => {
259        $(
260            impl SolEncode<'_> for $ty {
261                type SolType = $ty;
262
263                fn to_sol_type(&self) -> Self::SolType {
264                    *self
265                }
266            }
267        )*
268    };
269}
270
271macro_rules! impl_primitive {
272    ($($ty: ty),+ $(,)*) => {
273        $(
274            impl_primitive_decode!($ty);
275
276            impl_primitive_encode!($ty);
277        )*
278    };
279}
280
281macro_rules! impl_primitive_encode_by_ref {
282    ($($ty: ty, $ref_ty: ty),+ $(,)*) => {
283        $(
284            impl<'a> SolEncode<'a> for $ty {
285                type SolType = &'a $ref_ty;
286
287                fn to_sol_type(&'a self) -> Self::SolType {
288                    self
289                }
290            }
291        )*
292    };
293}
294
295macro_rules! impl_primitive_by_ref {
296    ($($ty: ty, $ref_ty: ty),+ $(,)*) => {
297        $(
298            impl_primitive_decode!($ty);
299
300            impl_primitive_encode_by_ref!($ty, $ref_ty);
301        )*
302    };
303}
304
305impl_primitive! {
306    // bool
307    bool,
308    // signed integers
309    i8, i16, i32, i64, i128,
310    // unsigned integers
311    u8, u16, u32, u64, u128, U256,
312    // address
313    Address,
314}
315
316impl_primitive_by_ref! {
317    // string
318    String, str,
319    Box<str>, str,
320}
321
322// Rust array <-> Solidity fixed-sized array (i.e. `T[N]`).
323impl<T: SolDecode, const N: usize> SolDecode for [T; N] {
324    type SolType = [T::SolType; N];
325
326    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
327        // FIXME: (@davidsemakula) replace with `array::try_map` if it's ever stabilized.
328        // Ref: <https://github.com/rust-lang/rust/issues/79711>
329        // Ref: <https://doc.rust-lang.org/nightly/std/primitive.array.html#method.try_map>
330        value
331            .into_iter()
332            .map(<T as SolDecode>::from_sol_type)
333            .process_results(|iter| iter.collect_array())?
334            .ok_or(Error)
335    }
336}
337
338impl<'a, T: SolEncode<'a>, const N: usize> SolEncode<'a> for [T; N] {
339    type SolType = [T::SolType; N];
340
341    fn to_sol_type(&'a self) -> Self::SolType {
342        self.each_ref().map(<T as SolEncode>::to_sol_type)
343    }
344}
345
346// Rust `Vec` <-> Solidity dynamic size array (i.e. `T[]`).
347impl<T: SolDecode> SolDecode for Vec<T> {
348    type SolType = Vec<T::SolType>;
349
350    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
351        value
352            .into_iter()
353            .map(<T as SolDecode>::from_sol_type)
354            .collect()
355    }
356}
357
358impl<'a, T: SolEncode<'a>> SolEncode<'a> for Vec<T> {
359    type SolType = Vec<T::SolType>;
360
361    fn to_sol_type(&'a self) -> Self::SolType {
362        self.iter().map(<T as SolEncode>::to_sol_type).collect()
363    }
364}
365
366// Rust `Box<[T]>` (i.e. boxed slice) <-> Solidity dynamic size array (i.e. `T[]`).
367impl<T: SolDecode> SolDecode for Box<[T]> {
368    type SolType = Box<[T::SolType]>;
369
370    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
371        // TODO: (@davidsemakula) Switch to method call syntax when edition is 2024
372        // (i.e. `value.into_iter()`).
373        // See <https://doc.rust-lang.org/edition-guide/rust-2024/intoiterator-box-slice.html> for details.
374        core::iter::IntoIterator::into_iter(value)
375            .map(<T as SolDecode>::from_sol_type)
376            .process_results(|iter| iter.collect())
377    }
378}
379
380impl<'a, T: SolEncode<'a>> SolEncode<'a> for Box<[T]> {
381    type SolType = Box<[T::SolType]>;
382
383    fn to_sol_type(&'a self) -> Self::SolType {
384        self.iter().map(<T as SolEncode>::to_sol_type).collect()
385    }
386}
387
388// We follow the Rust standard library's convention of implementing traits for tuples up
389// to twelve items long.
390// Ref: <https://doc.rust-lang.org/std/primitive.tuple.html#trait-implementations>
391#[impl_for_tuples(12)]
392impl SolDecode for Tuple {
393    for_tuples!( type SolType = ( #( Tuple::SolType ),* ); );
394
395    #[allow(clippy::unused_unit)]
396    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
397        Ok(for_tuples! { ( #( Tuple::from_sol_type(value.Tuple)? ),* ) })
398    }
399}
400
401#[impl_for_tuples(12)]
402impl<'a> SolEncode<'a> for Tuple {
403    for_tuples!( type SolType = ( #( Tuple::SolType ),* ); );
404
405    #[allow(clippy::unused_unit)]
406    fn to_sol_type(&'a self) -> Self::SolType {
407        for_tuples!( ( #( self.Tuple.to_sol_type() ),* ) )
408    }
409}
410
411// Implements `SolEncode` for reference types.
412macro_rules! impl_refs_encode {
413    ($($ty: ty), +$(,)*) => {
414        $(
415            impl<'a, T> SolEncode<'a> for $ty
416            where
417                T: SolEncode<'a>,
418            {
419                type SolType = T::SolType;
420
421                fn to_sol_type(&'a self) -> Self::SolType {
422                    <T as SolEncode>::to_sol_type(self)
423                }
424            }
425        )*
426    };
427}
428
429impl_refs_encode! {
430    &T,
431    &mut T,
432    Box<T>,
433}
434
435impl<'a, T> SolEncode<'a> for Cow<'_, T>
436where
437    T: SolEncode<'a> + Clone,
438{
439    type SolType = T::SolType;
440
441    fn to_sol_type(&'a self) -> Self::SolType {
442        <T as SolEncode>::to_sol_type(self.deref())
443    }
444}
445
446// Implements `SolEncode` for references to `str` and `[T]` DSTs.
447macro_rules! impl_str_ref_encode {
448    ($($ty: ty),+ $(,)*) => {
449        $(
450            impl<'a> SolEncode<'a> for $ty {
451                type SolType = &'a str;
452
453                fn to_sol_type(&'a self) -> Self::SolType {
454                    self
455                }
456            }
457        )*
458    };
459}
460
461impl_str_ref_encode!(&str, &mut str);
462
463macro_rules! impl_slice_ref_encode {
464    ($($ty: ty),+ $(,)*) => {
465        $(
466            impl<'a, T> SolEncode<'a> for $ty
467            where
468                T: SolEncode<'a>,
469            {
470                type SolType = Vec<T::SolType>;
471
472                fn to_sol_type(&'a self) -> Self::SolType {
473                    self.iter().map(<T as SolEncode>::to_sol_type).collect()
474                }
475            }
476        )*
477    };
478}
479
480impl_slice_ref_encode!(&[T], &mut [T]);
481
482// Option<T> <-> (bool, T)
483//
484// `bool` is a "flag" indicating the variant i.e. `false` for `None` and `true` for `Some`
485// such that:
486//  - `Option::None` is mapped to `(false, <default_value>)` where `<default_value>` is
487//    the zero bytes only representation of `T` (e.g. `0u8` for `u8` or `Vec::<T>::new()`
488//    for `Vec<T>`)
489//  - `Option::Some(value)` is mapped to `(true, value)`
490//
491// # Note
492//
493// The resulting type in Solidity can be represented as struct with a field for the "flag"
494// and another for the data.
495//
496// Note that `enum` in Solidity is encoded as `uint8` in Solidity ABI encoding, while the
497// encoding for `bool` is equivalent to the encoding of `uint8` with `true` equivalent to
498// `1` and `false` equivalent to `0`. Therefore, the `bool` "flag" can be safely
499// interpreted as a `bool` or `enum` (or even `uint8`) in Solidity code.
500//
501// Ref: <https://docs.soliditylang.org/en/latest/abi-spec.html#mapping-solidity-to-abi-types>
502impl<T> SolDecode for Option<T>
503where
504    T: SolDecode,
505{
506    type SolType = Option<T::SolType>;
507
508    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
509        value.map(<T as SolDecode>::from_sol_type).transpose()
510    }
511}
512
513impl<'a, T> SolEncode<'a> for Option<T>
514where
515    T: SolEncode<'a>,
516{
517    type SolType = Option<T::SolType>;
518
519    fn to_sol_type(&'a self) -> Self::SolType {
520        self.as_ref().map(T::to_sol_type)
521    }
522}
523
524// Rust `PhantomData` <-> Solidity zero-tuple `()`.
525impl<T> SolDecode for core::marker::PhantomData<T> {
526    type SolType = ();
527
528    fn decode(_: &[u8]) -> Result<Self, Error>
529    where
530        Self: Sized,
531    {
532        // NOTE: Solidity ABI decoding doesn't validate input length.
533        Ok(core::marker::PhantomData)
534    }
535
536    fn from_sol_type(_: Self::SolType) -> Result<Self, Error> {
537        Ok(core::marker::PhantomData)
538    }
539}
540
541impl<T> SolEncode<'_> for core::marker::PhantomData<T> {
542    type SolType = ();
543
544    fn encode(&self) -> Vec<u8> {
545        Vec::new()
546    }
547
548    fn to_sol_type(&self) {}
549}
550
551// AccountId <-> bytes32
552impl SolDecode for AccountId {
553    type SolType = FixedBytes<32>;
554
555    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
556        Ok(AccountId(value.0))
557    }
558}
559
560impl<'a> SolEncode<'a> for AccountId {
561    type SolType = &'a FixedBytes<32>;
562
563    fn to_sol_type(&'a self) -> Self::SolType {
564        FixedBytes::from_ref(self.as_ref())
565    }
566}
567
568// Hash <-> bytes32
569impl SolDecode for Hash {
570    type SolType = FixedBytes<32>;
571
572    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
573        Ok(Hash::from(value.0))
574    }
575}
576
577impl<'a> SolEncode<'a> for Hash {
578    type SolType = &'a FixedBytes<32>;
579
580    fn to_sol_type(&'a self) -> Self::SolType {
581        use core::borrow::Borrow;
582        FixedBytes::from_ref(self.borrow())
583    }
584}
585
586// H256 <-> bytes32
587impl SolDecode for H256 {
588    type SolType = FixedBytes<32>;
589
590    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
591        Ok(H256(value.0))
592    }
593}
594
595impl<'a> SolEncode<'a> for H256 {
596    type SolType = &'a FixedBytes<32>;
597
598    fn to_sol_type(&'a self) -> Self::SolType {
599        FixedBytes::from_ref(self.as_fixed_bytes())
600    }
601}
602
603// Weight
604impl SolDecode for Weight {
605    type SolType = (u64, u64);
606
607    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
608        Ok(Weight::from_parts(value.0, value.1))
609    }
610}
611
612impl SolEncode<'_> for Weight {
613    type SolType = (u64, u64);
614
615    fn to_sol_type(&self) -> Self::SolType {
616        (self.ref_time(), self.proof_size())
617    }
618}