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