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;
26
27#[cfg(test)]
28mod tests;
29
30use core::ops::Deref;
31
32use alloy_sol_types::{
33    sol_data,
34    SolType as AlloySolType,
35};
36use impl_trait_for_tuples::impl_for_tuples;
37use ink_prelude::{
38    borrow::Cow,
39    boxed::Box,
40    string::String,
41    vec::Vec,
42};
43use itertools::Itertools;
44use primitive_types::{
45    H256,
46    U256,
47};
48use sp_weights::Weight;
49
50pub use self::{
51    bytes::{
52        DynBytes,
53        FixedBytes,
54    },
55    error::{
56        SolErrorDecode,
57        SolErrorEncode,
58    },
59    params::{
60        SolParamsDecode,
61        SolParamsEncode,
62    },
63    result::{
64        SolResultDecode,
65        SolResultDecodeError,
66    },
67    types::{
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;
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    /// Converts from `Self` to `Self::SolType` via either a borrow (if possible), or
190    /// a possibly expensive conversion otherwise.
191    fn to_sol_type(&'a self) -> Self::SolType;
192}
193
194/// Solidity ABI encoding/decoding error.
195#[derive(Debug, PartialEq)]
196pub struct Error;
197
198impl From<alloy_sol_types::Error> for Error {
199    fn from(_: alloy_sol_types::Error) -> Self {
200        Self
201    }
202}
203
204impl core::fmt::Display for Error {
205    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
206        f.write_str("Solidity ABI encode/decode error")
207    }
208}
209
210macro_rules! impl_primitive_decode {
211    ($($ty: ty),+ $(,)*) => {
212        $(
213            impl SolDecode for $ty {
214                type SolType = $ty;
215
216                fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
217                    Ok(value)
218                }
219            }
220        )*
221    };
222}
223
224macro_rules! impl_primitive_encode {
225    ($($ty: ty),+ $(,)*) => {
226        $(
227            impl SolEncode<'_> for $ty {
228                type SolType = $ty;
229
230                fn to_sol_type(&self) -> Self::SolType {
231                    *self
232                }
233            }
234        )*
235    };
236}
237
238macro_rules! impl_primitive {
239    ($($ty: ty),+ $(,)*) => {
240        $(
241            impl_primitive_decode!($ty);
242
243            impl_primitive_encode!($ty);
244        )*
245    };
246}
247
248macro_rules! impl_primitive_encode_by_ref {
249    ($($ty: ty, $ref_ty: ty),+ $(,)*) => {
250        $(
251            impl<'a> SolEncode<'a> for $ty {
252                type SolType = &'a $ref_ty;
253
254                fn to_sol_type(&'a self) -> Self::SolType {
255                    self
256                }
257            }
258        )*
259    };
260}
261
262macro_rules! impl_primitive_by_ref {
263    ($($ty: ty, $ref_ty: ty),+ $(,)*) => {
264        $(
265            impl_primitive_decode!($ty);
266
267            impl_primitive_encode_by_ref!($ty, $ref_ty);
268        )*
269    };
270}
271
272impl_primitive! {
273    // bool
274    bool,
275    // signed integers
276    i8, i16, i32, i64, i128,
277    // unsigned integers
278    u8, u16, u32, u64, u128, U256,
279    // address
280    Address,
281}
282
283impl_primitive_by_ref! {
284    // string
285    String, str,
286    Box<str>, str,
287}
288
289// Rust array <-> Solidity fixed-sized array (i.e. `T[N]`).
290impl<T: SolDecode, const N: usize> SolDecode for [T; N] {
291    type SolType = [T::SolType; N];
292
293    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
294        // FIXME: (@davidsemakula) replace with `array::try_map` if it's ever stabilized.
295        // Ref: <https://github.com/rust-lang/rust/issues/79711>
296        // Ref: <https://doc.rust-lang.org/nightly/std/primitive.array.html#method.try_map>
297        value
298            .into_iter()
299            .map(<T as SolDecode>::from_sol_type)
300            .process_results(|iter| iter.collect_array())?
301            .ok_or(Error)
302    }
303}
304
305impl<'a, T: SolEncode<'a>, const N: usize> SolEncode<'a> for [T; N] {
306    type SolType = [T::SolType; N];
307
308    fn to_sol_type(&'a self) -> Self::SolType {
309        self.each_ref().map(<T as SolEncode>::to_sol_type)
310    }
311}
312
313// Rust `Vec` <-> Solidity dynamic size array (i.e. `T[]`).
314impl<T: SolDecode> SolDecode for Vec<T> {
315    type SolType = Vec<T::SolType>;
316
317    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
318        value
319            .into_iter()
320            .map(<T as SolDecode>::from_sol_type)
321            .collect()
322    }
323}
324
325impl<'a, T: SolEncode<'a>> SolEncode<'a> for Vec<T> {
326    type SolType = Vec<T::SolType>;
327
328    fn to_sol_type(&'a self) -> Self::SolType {
329        self.iter().map(<T as SolEncode>::to_sol_type).collect()
330    }
331}
332
333// Rust `Box<[T]>` (i.e. boxed slice) <-> Solidity dynamic size array (i.e. `T[]`).
334impl<T: SolDecode> SolDecode for Box<[T]> {
335    type SolType = Box<[T::SolType]>;
336
337    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
338        // TODO: (@davidsemakula) Switch to method call syntax when edition is 2024
339        // (i.e. `value.into_iter()`).
340        // See <https://doc.rust-lang.org/edition-guide/rust-2024/intoiterator-box-slice.html> for details.
341        core::iter::IntoIterator::into_iter(value)
342            .map(<T as SolDecode>::from_sol_type)
343            .process_results(|iter| iter.collect())
344    }
345}
346
347impl<'a, T: SolEncode<'a>> SolEncode<'a> for Box<[T]> {
348    type SolType = Box<[T::SolType]>;
349
350    fn to_sol_type(&'a self) -> Self::SolType {
351        self.iter().map(<T as SolEncode>::to_sol_type).collect()
352    }
353}
354
355// We follow the Rust standard library's convention of implementing traits for tuples up
356// to twelve items long.
357// Ref: <https://doc.rust-lang.org/std/primitive.tuple.html#trait-implementations>
358#[impl_for_tuples(12)]
359impl SolDecode for Tuple {
360    for_tuples!( type SolType = ( #( Tuple::SolType ),* ); );
361
362    #[allow(clippy::unused_unit)]
363    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
364        Ok(for_tuples! { ( #( Tuple::from_sol_type(value.Tuple)? ),* ) })
365    }
366}
367
368#[impl_for_tuples(12)]
369impl<'a> SolEncode<'a> for Tuple {
370    for_tuples!( type SolType = ( #( Tuple::SolType ),* ); );
371
372    #[allow(clippy::unused_unit)]
373    fn to_sol_type(&'a self) -> Self::SolType {
374        for_tuples!( ( #( self.Tuple.to_sol_type() ),* ) )
375    }
376}
377
378// Implements `SolEncode` for reference types.
379macro_rules! impl_refs_encode {
380    ($($ty: ty), +$(,)*) => {
381        $(
382            impl<'a, T> SolEncode<'a> for $ty
383            where
384                T: SolEncode<'a>,
385            {
386                type SolType = T::SolType;
387
388                fn to_sol_type(&'a self) -> Self::SolType {
389                    <T as SolEncode>::to_sol_type(self)
390                }
391            }
392        )*
393    };
394}
395
396impl_refs_encode! {
397    &T,
398    &mut T,
399    Box<T>,
400}
401
402impl<'a, T> SolEncode<'a> for Cow<'_, T>
403where
404    T: SolEncode<'a> + Clone,
405{
406    type SolType = T::SolType;
407
408    fn to_sol_type(&'a self) -> Self::SolType {
409        <T as SolEncode>::to_sol_type(self.deref())
410    }
411}
412
413// Implements `SolEncode` for references to `str` and `[T]` DSTs.
414macro_rules! impl_str_ref_encode {
415    ($($ty: ty),+ $(,)*) => {
416        $(
417            impl<'a> SolEncode<'a> for $ty {
418                type SolType = &'a str;
419
420                fn to_sol_type(&'a self) -> Self::SolType {
421                    self
422                }
423            }
424        )*
425    };
426}
427
428impl_str_ref_encode!(&str, &mut str);
429
430macro_rules! impl_slice_ref_encode {
431    ($($ty: ty),+ $(,)*) => {
432        $(
433            impl<'a, T> SolEncode<'a> for $ty
434            where
435                T: SolEncode<'a>,
436            {
437                type SolType = Vec<T::SolType>;
438
439                fn to_sol_type(&'a self) -> Self::SolType {
440                    self.iter().map(<T as SolEncode>::to_sol_type).collect()
441                }
442            }
443        )*
444    };
445}
446
447impl_slice_ref_encode!(&[T], &mut [T]);
448
449// Option<T> <-> (bool, T)
450//
451// `bool` is a "flag" indicating the variant i.e. `false` for `None` and `true` for `Some`
452// such that:
453//  - `Option::None` is mapped to `(false, <default_value>)` where `<default_value>` is
454//    the zero bytes only representation of `T` (e.g. `0u8` for `u8` or `Vec::<T>::new()`
455//    for `Vec<T>`)
456//  - `Option::Some(value)` is mapped to `(true, value)`
457//
458// # Note
459//
460// The resulting type in Solidity can be represented as struct with a field for the "flag"
461// and another for the data.
462//
463// Note that `enum` in Solidity is encoded as `uint8` in Solidity ABI encoding, while the
464// encoding for `bool` is equivalent to the encoding of `uint8` with `true` equivalent to
465// `1` and `false` equivalent to `0`. Therefore, the `bool` "flag" can be safely
466// interpreted as a `bool` or `enum` (or even `uint8`) in Solidity code.
467//
468// Ref: <https://docs.soliditylang.org/en/latest/abi-spec.html#mapping-solidity-to-abi-types>
469impl<T> SolDecode for Option<T>
470where
471    T: SolDecode,
472{
473    type SolType = Option<T::SolType>;
474
475    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
476        value.map(<T as SolDecode>::from_sol_type).transpose()
477    }
478}
479
480impl<'a, T> SolEncode<'a> for Option<T>
481where
482    T: SolEncode<'a>,
483{
484    type SolType = Option<T::SolType>;
485
486    fn to_sol_type(&'a self) -> Self::SolType {
487        self.as_ref().map(T::to_sol_type)
488    }
489}
490
491// Rust `PhantomData` <-> Solidity zero-tuple `()`.
492impl<T> SolDecode for core::marker::PhantomData<T> {
493    type SolType = ();
494
495    fn decode(data: &[u8]) -> Result<Self, Error>
496    where
497        Self: Sized,
498    {
499        if data.is_empty() {
500            Ok(core::marker::PhantomData)
501        } else {
502            Err(Error)
503        }
504    }
505
506    fn from_sol_type(_: Self::SolType) -> Result<Self, Error> {
507        Ok(core::marker::PhantomData)
508    }
509}
510
511impl<T> SolEncode<'_> for core::marker::PhantomData<T> {
512    type SolType = ();
513
514    fn encode(&self) -> Vec<u8> {
515        Vec::new()
516    }
517
518    fn to_sol_type(&self) {}
519}
520
521// AccountId <-> bytes32
522impl SolDecode for AccountId {
523    type SolType = FixedBytes<32>;
524
525    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
526        Ok(AccountId(value.0))
527    }
528}
529
530impl SolEncode<'_> for AccountId {
531    type SolType = FixedBytes<32>;
532
533    fn encode(&self) -> Vec<u8> {
534        // Override for better performance.
535        sol_data::FixedBytes::abi_encode(self)
536    }
537
538    fn to_sol_type(&self) -> Self::SolType {
539        // NOTE: Not actually used for encoding because of `encode` override above (for
540        // better performance).
541        // Arbitrary newtype wrappers can achieve similar performance (without overriding
542        // `encode`) by using `FixedBytes<32>` as the inner type and returning
543        // `&self.0`.
544        FixedBytes(self.0)
545    }
546}
547
548// Hash <-> bytes32
549impl SolDecode for Hash {
550    type SolType = FixedBytes<32>;
551
552    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
553        Ok(Hash::from(value.0))
554    }
555}
556
557impl SolEncode<'_> for Hash {
558    type SolType = FixedBytes<32>;
559
560    fn encode(&self) -> Vec<u8> {
561        // Override for better performance.
562        sol_data::FixedBytes::abi_encode(self)
563    }
564
565    fn to_sol_type(&self) -> Self::SolType {
566        // NOTE: Not actually used for encoding because of `encode` override above (for
567        // better performance).
568        // Arbitrary newtype wrappers can achieve similar performance (without overriding
569        // `encode`) by using `FixedBytes<32>` as the inner type and returning
570        // `&self.0`.
571        FixedBytes((*self).into())
572    }
573}
574
575// H256 <-> bytes32
576impl SolDecode for H256 {
577    type SolType = FixedBytes<32>;
578
579    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
580        Ok(H256(value.0))
581    }
582}
583
584impl SolEncode<'_> for H256 {
585    type SolType = FixedBytes<32>;
586
587    fn encode(&self) -> Vec<u8> {
588        // Override for better performance.
589        sol_data::FixedBytes::abi_encode(&self.0)
590    }
591
592    fn to_sol_type(&self) -> Self::SolType {
593        // NOTE: Not actually used for encoding because of `encode` override above (for
594        // better performance).
595        // Arbitrary newtype wrappers can achieve similar performance (without overriding
596        // `encode`) by using `FixedBytes<32>` as the inner type and returning
597        // `&self.0`.
598        FixedBytes(self.0)
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}