ink_primitives/sol/
encodable.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
15use alloy_sol_types::{
16    abi::{
17        token::{
18            DynSeqToken,
19            FixedSeqToken,
20            PackedSeqToken,
21            Token,
22            WordToken,
23        },
24        Encoder,
25    },
26    Word,
27};
28use ink_prelude::vec::Vec;
29
30/// A Solidity ABI encodable representation for a type.
31///
32///
33/// # Note
34///
35/// An analog of `Token` and `TokenSeq` with only encoding operations.
36///
37/// References:
38///
39/// - <https://github.com/alloy-rs/core/blob/49b7bce463cce6e987a8fb9a987acbf4ec4297a6/crates/sol-types/src/abi/token.rs#L54>
40/// - <https://github.com/alloy-rs/core/blob/49b7bce463cce6e987a8fb9a987acbf4ec4297a6/crates/sol-types/src/abi/token.rs#L85>
41//
42// # Design Notes
43//
44// This trait allows us to encode using local abstractions, notably
45// `TokenOrDefault`, `FixedSizeDefault` and `DynSizeDefault`, for which we can't implement
46// `Token` nor `TokenSeq` because those are "sealed" traits in `alloy_sol_types`.
47pub trait Encodable: private::Sealed {
48    /// True for dynamic types.
49    const DYNAMIC: bool;
50
51    /// Number of words in the head.
52    fn head_words(&self) -> usize;
53
54    /// Number of words in the tail.
55    fn tail_words(&self) -> usize;
56
57    /// Total number of head and tails words.
58    #[inline(always)]
59    fn total_words(&self) -> usize {
60        self.head_words() + self.tail_words()
61    }
62
63    /// Append head words to the encoder.
64    fn head_append(&self, encoder: &mut Encoder);
65
66    /// Append tail words to the encoder.
67    fn tail_append(&self, encoder: &mut Encoder);
68
69    /// Append both head and tail words to the encoder.
70    fn encode(&self, encoder: &mut Encoder) {
71        // Head is either the actual data (for fixed-sized types) or the offset (for
72        // dynamic types).
73        encoder.push_offset(Encodable::head_words(self));
74        Encodable::head_append(self, encoder);
75        if <Self as Encodable>::DYNAMIC {
76            // Only dynamic types have tails, which contain the "actual data".
77            encoder.bump_offset(Encodable::tail_words(self));
78            Encodable::tail_append(self, encoder);
79        }
80        // Encoder implementation detail for tracking offsets.
81        encoder.pop_offset();
82    }
83}
84
85// NOTE: We use a macro instead of a generic implementation over `T: Token` because
86// that would "conflict" with generic implementations over `T: Encodable`.
87macro_rules! impl_encodable_for_token {
88    ($([$($gen:tt)*] $ty: ty),+ $(,)*) => {
89        $(
90            impl<$($gen)*> Encodable for $ty {
91                const DYNAMIC: bool = <$ty as Token>::DYNAMIC;
92
93                fn head_words(&self) -> usize {
94                    Token::head_words(self)
95                }
96
97                fn tail_words(&self) -> usize {
98                    Token::tail_words(self)
99                }
100
101                fn head_append(&self, encoder: &mut Encoder) {
102                    Token::head_append(self, encoder);
103                }
104
105                fn tail_append(&self, encoder: &mut Encoder) {
106                    Token::tail_append(self, encoder);
107                }
108            }
109
110            impl<$($gen)*> private::Sealed for $ty {}
111        )+
112    };
113}
114
115impl_encodable_for_token! {
116    [] WordToken,
117    [] PackedSeqToken<'_>,
118    [T: for<'a> Token<'a>, const N: usize] FixedSeqToken<T, N>,
119    [T: for<'a> Token<'a>] DynSeqToken<T>,
120}
121
122/// Either a `Token` based (i.e. "actual value") or "default value" (i.e.
123/// `FixedSizeDefault` or `DynSizeDefault`) based representation.
124#[derive(Debug)]
125pub enum TokenOrDefault<T, D> {
126    Token(T),
127    Default(D),
128}
129
130/// A fixed-size type.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub struct FixedSizeDefault(usize);
133
134impl FixedSizeDefault {
135    /// Empty data.
136    pub const EMPTY: Self = Self(0);
137
138    /// A single word.
139    pub const WORD: Self = Self(1);
140
141    /// A fixed size number of words (e.g. for encoding `bytesN`).
142    pub const fn words(size: usize) -> Self {
143        Self(size)
144    }
145}
146
147/// A dynamic type.
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct DynSizeDefault;
150
151impl Encodable for FixedSizeDefault {
152    const DYNAMIC: bool = false;
153
154    fn head_words(&self) -> usize {
155        // All the data is in the head.
156        self.0
157    }
158
159    fn tail_words(&self) -> usize {
160        // No tail words, because all the data is in the head, or it's empty.
161        0
162    }
163
164    fn head_append(&self, encoder: &mut Encoder) {
165        match self.0 {
166            0 => (),
167            1 => {
168                // Appends empty word.
169                encoder.append_word(Word::from([0u8; 32]));
170            }
171            size => {
172                // Appends empty words.
173                // NOTE: Appending bytes directly would be more efficient but `Encoder`
174                // doesn't currently have a public method for doing this.
175                let mut counter = 0;
176                while counter < size {
177                    encoder.append_word(Word::from([0u8; 32]));
178                    counter += 1;
179                }
180            }
181        }
182    }
183
184    fn tail_append(&self, _: &mut Encoder) {}
185}
186
187impl private::Sealed for FixedSizeDefault {}
188
189impl Encodable for DynSizeDefault {
190    const DYNAMIC: bool = true;
191
192    fn head_words(&self) -> usize {
193        // offset.
194        1
195    }
196
197    fn tail_words(&self) -> usize {
198        // length (i.e. zero), no "actual data".
199        1
200    }
201
202    fn head_append(&self, encoder: &mut Encoder) {
203        // Appends offset.
204        encoder.append_indirection();
205    }
206
207    fn tail_append(&self, encoder: &mut Encoder) {
208        encoder.append_seq_len(0);
209    }
210}
211
212impl private::Sealed for DynSizeDefault {}
213
214impl<T, D> Encodable for TokenOrDefault<T, D>
215where
216    T: Encodable,
217    D: Encodable,
218{
219    const DYNAMIC: bool = T::DYNAMIC;
220
221    fn head_words(&self) -> usize {
222        match self {
223            TokenOrDefault::Token(token) => token.head_words(),
224            TokenOrDefault::Default(default) => default.head_words(),
225        }
226    }
227
228    fn tail_words(&self) -> usize {
229        match self {
230            TokenOrDefault::Token(token) => token.tail_words(),
231            TokenOrDefault::Default(default) => default.tail_words(),
232        }
233    }
234
235    fn head_append(&self, encoder: &mut Encoder) {
236        match self {
237            TokenOrDefault::Token(token) => token.head_append(encoder),
238            TokenOrDefault::Default(default) => default.head_append(encoder),
239        }
240    }
241
242    fn tail_append(&self, encoder: &mut Encoder) {
243        match self {
244            TokenOrDefault::Token(token) => token.tail_append(encoder),
245            TokenOrDefault::Default(default) => default.tail_append(encoder),
246        }
247    }
248}
249
250impl<T, D> private::Sealed for TokenOrDefault<T, D> {}
251
252// Analog of `FixedSeqToken` but with `T` bound being `Encodable` instead of `Token` and
253// `TokenSeq`.
254//
255// Ref: <https://github.com/alloy-rs/core/blob/49b7bce463cce6e987a8fb9a987acbf4ec4297a6/crates/sol-types/src/abi/token.rs#L253>
256impl<T, const N: usize> Encodable for [T; N]
257where
258    T: Encodable,
259{
260    const DYNAMIC: bool = T::DYNAMIC;
261
262    fn head_words(&self) -> usize {
263        if Self::DYNAMIC {
264            // offset.
265            1
266        } else {
267            // elements.
268            self.iter().map(T::total_words).sum()
269        }
270    }
271
272    fn tail_words(&self) -> usize {
273        if Self::DYNAMIC {
274            // elements.
275            self.iter().map(T::total_words).sum()
276        } else {
277            0
278        }
279    }
280
281    fn head_append(&self, encoder: &mut Encoder) {
282        if Self::DYNAMIC {
283            // Appends offset.
284            encoder.append_indirection();
285        } else {
286            // Appends "actual data".
287            for inner in self {
288                inner.head_append(encoder);
289            }
290        }
291    }
292
293    fn tail_append(&self, encoder: &mut Encoder) {
294        // Appends "actual data" to the tail for dynamic elements.
295        if Self::DYNAMIC {
296            encode_sequence(self, encoder);
297        }
298    }
299}
300
301impl<T, const N: usize> private::Sealed for [T; N] {}
302
303// Analog of `DynSeqToken` but with `T` bound being `Encodable` instead of `Token` and
304// `TokenSeq`.
305//
306// Ref: <https://github.com/alloy-rs/core/blob/49b7bce463cce6e987a8fb9a987acbf4ec4297a6/crates/sol-types/src/abi/token.rs#L366>
307impl<T> Encodable for Vec<T>
308where
309    T: Encodable,
310{
311    const DYNAMIC: bool = true;
312
313    fn head_words(&self) -> usize {
314        // offset.
315        1
316    }
317
318    fn tail_words(&self) -> usize {
319        // length + elements.
320        1 + self.iter().map(T::total_words).sum::<usize>()
321    }
322
323    fn head_append(&self, encoder: &mut Encoder) {
324        // Adds offset.
325        encoder.append_indirection();
326    }
327
328    fn tail_append(&self, encoder: &mut Encoder) {
329        // Appends length.
330        encoder.append_seq_len(self.len());
331
332        // Appends "actual data".
333        encode_sequence(self, encoder);
334    }
335}
336
337impl<T> private::Sealed for Vec<T> {}
338
339/// Identical to `TokenSeq::encode_sequence` implementations for `FixedSeqToken` and
340/// `DynSeqToken` but with `T` bound being `Encodable` instead of `Token`.
341///
342/// References:
343/// - <https://github.com/alloy-rs/core/blob/49b7bce463cce6e987a8fb9a987acbf4ec4297a6/crates/sol-types/src/abi/token.rs#L305>
344/// - <https://github.com/alloy-rs/core/blob/49b7bce463cce6e987a8fb9a987acbf4ec4297a6/crates/sol-types/src/abi/token.rs#L409>
345fn encode_sequence<T>(tokens: &[T], encoder: &mut Encoder)
346where
347    T: Encodable,
348{
349    encoder.push_offset(tokens.iter().map(T::head_words).sum());
350    for inner in tokens {
351        inner.head_append(encoder);
352        encoder.bump_offset(inner.tail_words());
353    }
354    for inner in tokens {
355        inner.tail_append(encoder);
356    }
357    encoder.pop_offset();
358}
359
360/// A Solidity ABI encodable representation of function parameters.
361///
362/// # Note
363///
364/// This trait is only implemented for tuples which also implement [`Encodable`].
365pub trait EncodableParams: private::Sealed {
366    /// Encode the function parameters into the encoder.
367    fn encode_params(&self, encoder: &mut Encoder);
368}
369
370/// Generates `EncodableParams` implementation body for an n-ary tuple where n >= 1.
371// NOTE: operation is a noop for unit (i.e. `()`).
372macro_rules! impl_encodable_params {
373    ($source: ident, $encoder: ident => ($($ty:ident),+$(,)*)) => {
374        let ($($ty,)+) = $source;
375        $encoder.push_offset(0 $( + $ty.head_words() )+);
376
377        $(
378            $ty.head_append($encoder);
379            $encoder.bump_offset($ty.tail_words());
380        )+
381
382        $(
383            $ty.tail_append($encoder);
384        )+
385
386        $encoder.pop_offset();
387    };
388}
389
390/// Identical to tuple implementations for `T: Token` and `T: TokenSeq` but with
391/// `T: Encodable` as the bound.
392///
393/// Ref: <https://github.com/alloy-rs/core/blob/49b7bce463cce6e987a8fb9a987acbf4ec4297a6/crates/sol-types/src/abi/token.rs#L521>
394// `impl-trait-for-tuples` doesn't support using `||` as a separator needed for
395// `Encodable::DYNAMIC`, so we fallback to a declarative macro.
396macro_rules! impl_encodable {
397    ($($ty:ident),+) => {
398        #[allow(non_snake_case)]
399        impl<$($ty: Encodable,)+> Encodable for ($($ty,)+) {
400            const DYNAMIC: bool = $(<$ty as Encodable>::DYNAMIC )||+;
401
402            #[inline]
403            fn head_words(&self) -> usize {
404                if Self::DYNAMIC {
405                    // offset
406                    1
407                } else {
408                    // elements
409                    let ($($ty,)+) = self;
410                    0 $( + $ty.total_words() )+
411                }
412            }
413
414            #[inline]
415            fn tail_words(&self) -> usize {
416                if Self::DYNAMIC {
417                    // elements
418                    let ($($ty,)+) = self;
419                    0 $( + $ty.total_words() )+
420                } else {
421                    0
422                }
423            }
424
425            #[inline]
426            fn head_append(&self, encoder: &mut Encoder) {
427                if Self::DYNAMIC {
428                    encoder.append_indirection();
429                } else {
430                    let ($($ty,)+) = self;
431                    $(
432                        $ty.head_append(encoder);
433                    )+
434                }
435            }
436
437            #[inline]
438            fn tail_append(&self, encoder: &mut Encoder) {
439                if Self::DYNAMIC {
440                    impl_encodable_params!(self, encoder => ($($ty,)+));
441                }
442            }
443        }
444
445        #[allow(non_snake_case)]
446        impl<$($ty: Encodable,)+> EncodableParams for ($($ty,)+) {
447            fn encode_params(&self, encoder: &mut Encoder) {
448                impl_encodable_params!(self, encoder => ($($ty,)+));
449            }
450        }
451
452        impl<$($ty: Encodable,)+> private::Sealed for ($($ty,)+) {}
453    };
454}
455
456impl_all_tuples!(@nonempty impl_encodable);
457
458// Identical to optimized `Token` and `TokenSeq` implementation for `()`, but for
459// `Encodable`.
460//
461// Ref: <https://github.com/alloy-rs/core/blob/49b7bce463cce6e987a8fb9a987acbf4ec4297a6/crates/sol-types/src/abi/token.rs#L616>
462impl Encodable for () {
463    const DYNAMIC: bool = false;
464
465    #[inline]
466    fn head_words(&self) -> usize {
467        0
468    }
469
470    #[inline]
471    fn tail_words(&self) -> usize {
472        0
473    }
474
475    #[inline]
476    fn head_append(&self, _: &mut Encoder) {}
477
478    #[inline]
479    fn tail_append(&self, _: &mut Encoder) {}
480
481    #[inline]
482    fn encode(&self, _: &mut Encoder) {}
483}
484
485impl EncodableParams for () {
486    fn encode_params(&self, _: &mut Encoder) {}
487}
488
489impl private::Sealed for () {}
490
491pub(super) mod private {
492    /// Seals implementations of `Encodable`.
493    pub trait Sealed {}
494}