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