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