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