ink_primitives/sol/
bytes.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 core::{
16    borrow::Borrow,
17    default::Default,
18    ops::Deref,
19};
20
21use alloy_sol_types::{
22    abi::token::{
23        PackedSeqToken,
24        WordToken,
25    },
26    sol_data,
27    SolType as AlloySolType,
28};
29use ink_prelude::{
30    boxed::Box,
31    vec::Vec,
32};
33use scale::{
34    Decode,
35    Encode,
36};
37#[cfg(feature = "std")]
38use scale_info::TypeInfo;
39
40use crate::sol::{
41    encodable::{
42        DynSizeDefault,
43        Encodable,
44        FixedSizeDefault,
45    },
46    types::SolTokenType,
47    Error,
48    SolDecode,
49    SolEncode,
50    SolTypeDecode,
51    SolTypeEncode,
52};
53
54/// Newtype wrapper for encoding/decoding `u8` sequences/collections as their equivalent
55/// Solidity bytes representations.
56///
57/// | Rust/ink! type | Solidity ABI type | Notes |
58/// | -------------- | ----------------- | ----- |
59/// | `SolBytes<u8>` |  `bytes1` ||
60/// | `SolBytes<[u8; N]>` for `1 <= N <= 32` |  `bytesN` | e.g. `SolBytes<[u8; 32]>` <=> `bytes32` |
61/// | `SolBytes<Vec<u8>>` | `bytes` ||
62/// | `SolBytes<Box<[u8]>>` | `bytes` ||
63///
64/// Ref: <https://docs.soliditylang.org/en/latest/types.html#fixed-size-byte-arrays>
65///
66/// Ref: <https://docs.soliditylang.org/en/latest/types.html#bytes-and-string-as-arrays>
67#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
68#[cfg_attr(feature = "std", derive(TypeInfo))]
69pub struct SolBytes<T: SolBytesType>(pub T);
70
71// Implements `SolTypeDecode` and `SolTypeEncode` for `SolBytes<T>`.
72impl<T: SolBytesType> SolTypeDecode for SolBytes<T> {
73    type AlloyType = T::AlloyType;
74
75    fn detokenize(
76        token: <Self::AlloyType as AlloySolType>::Token<'_>,
77    ) -> Result<Self, Error> {
78        // Takes advantage of optimized `SolBytesType::detokenize` implementations and
79        // skips unnecessary conversions to `T::AlloyType::RustType`.
80        Ok(Self(<T as SolBytesType>::detokenize(token)))
81    }
82}
83
84impl<T: SolBytesType> SolTypeEncode for SolBytes<T> {
85    type AlloyType = T::AlloyType;
86
87    const DEFAULT_VALUE: Self::DefaultType = T::DEFAULT_VALUE;
88
89    fn tokenize(&self) -> Self::TokenType<'_> {
90        <T as SolBytesType>::tokenize(self)
91    }
92}
93
94impl<T: SolBytesType> SolTokenType for SolBytes<T> {
95    type TokenType<'enc> = T::TokenType<'enc>;
96
97    type DefaultType = T::DefaultType;
98}
99
100impl<T: SolBytesType> crate::sol::types::private::Sealed for SolBytes<T> {}
101
102// Implements `SolDecode` and `SolEncode` for `SolBytes<T>`.
103impl<T: SolBytesType> SolDecode for SolBytes<T> {
104    type SolType = SolBytes<T>;
105
106    fn from_sol_type(value: Self::SolType) -> Result<Self, Error> {
107        Ok(value)
108    }
109}
110
111impl<'a, T: SolBytesType + 'a> SolEncode<'a> for SolBytes<T> {
112    type SolType = &'a SolBytes<T>;
113
114    fn to_sol_type(&'a self) -> Self::SolType {
115        self
116    }
117}
118
119// Implements core/standard traits for cheap representations as the inner type.
120impl<T: SolBytesType> Deref for SolBytes<T> {
121    type Target = T;
122
123    fn deref(&self) -> &Self::Target {
124        &self.0
125    }
126}
127
128impl<T: SolBytesType> Borrow<T> for SolBytes<T> {
129    fn borrow(&self) -> &T {
130        &self.0
131    }
132}
133
134impl<T: SolBytesType> AsRef<T> for SolBytes<T> {
135    fn as_ref(&self) -> &T {
136        &self.0
137    }
138}
139
140impl AsRef<[u8]> for SolBytes<Vec<u8>> {
141    fn as_ref(&self) -> &[u8] {
142        &self.0
143    }
144}
145
146/// A Rust/ink! equivalent of a Solidity ABI bytes type that implements logic for Solidity
147/// ABI encoding/decoding.
148///
149/// Ref: <https://docs.soliditylang.org/en/latest/types.html#fixed-size-byte-arrays>
150///
151/// Ref: <https://docs.soliditylang.org/en/latest/types.html#bytes-and-string-as-arrays>
152///
153/// # Note
154///
155/// This trait is sealed and cannot be implemented for types outside `ink_primitives`.
156pub trait SolBytesType: SolBytesTokenType + private::Sealed {
157    /// Equivalent Solidity ABI bytes type from [`alloy_sol_types`].
158    type AlloyType: AlloySolType;
159
160    /// An encodable representation of the default value for this type.
161    const DEFAULT_VALUE: Self::DefaultType;
162
163    /// Tokenizes the given value into a [`Self::AlloyType`] token.
164    fn tokenize(&self) -> Self::TokenType<'_>;
165
166    /// Detokenizes the byte type's value from the given token.
167    fn detokenize(token: <Self::AlloyType as AlloySolType>::Token<'_>) -> Self;
168
169    /// The default value.
170    fn default() -> Self;
171}
172
173/// Analog to [`SolTokenType`].
174pub trait SolBytesTokenType: private::Sealed {
175    /// The type of an encodable representation of this type.
176    type TokenType<'enc>: Encodable;
177
178    /// The type of an encodable "default" representation of this type.
179    type DefaultType: Encodable;
180}
181
182// Implements `SolBytesType` for `u8`, `[u8; N]`, `Vec<u8>` and `Box<[u8]>`.
183impl SolBytesType for u8
184where
185    sol_data::ByteCount<1>: sol_data::SupportedFixedBytes,
186{
187    type AlloyType = sol_data::FixedBytes<1>;
188
189    const DEFAULT_VALUE: Self::DefaultType = FixedSizeDefault::WORD;
190
191    fn tokenize(&self) -> <Self::AlloyType as AlloySolType>::Token<'_> {
192        // `u8` is encoded as `[u8; 1]` (i.e. `bytes1`).
193        let mut word = [0; 32];
194        word[0] = *self;
195        WordToken::from(word)
196    }
197
198    fn detokenize(token: <Self::AlloyType as AlloySolType>::Token<'_>) -> Self {
199        // `u8` is decoded as the first byte since `bytes1` is padded with trailing zeros.
200        // Ref: <https://docs.soliditylang.org/en/latest/abi-spec.html#formal-specification-of-the-encoding>
201        token.0 .0[0]
202    }
203
204    fn default() -> Self {
205        0u8
206    }
207}
208
209impl SolBytesTokenType for u8
210where
211    sol_data::ByteCount<1>: sol_data::SupportedFixedBytes,
212{
213    type TokenType<'enc> = WordToken;
214
215    type DefaultType = FixedSizeDefault;
216}
217
218impl private::Sealed for u8 {}
219
220impl<const N: usize> SolBytesType for [u8; N]
221where
222    sol_data::ByteCount<N>: sol_data::SupportedFixedBytes,
223{
224    type AlloyType = sol_data::FixedBytes<N>;
225
226    const DEFAULT_VALUE: Self::DefaultType = FixedSizeDefault::WORD;
227
228    fn tokenize(&self) -> Self::TokenType<'_> {
229        // Direct implementation simplifies generic implementations by removing
230        // requirement for `SolTypeValue<Self::AlloyType>`.
231        let mut word = [0; 32];
232        word[..N].copy_from_slice(self.as_slice());
233        WordToken::from(word)
234    }
235
236    fn detokenize(token: <Self::AlloyType as AlloySolType>::Token<'_>) -> Self {
237        // Converts token directly into `[u8; N]`, skipping the conversion to
238        // `alloy_sol_types::private::FixedBytes`, which then has to be unpacked to
239        // `[u8; N]`.
240        // Ref: <https://github.com/alloy-rs/core/blob/5ae4fe0b246239602c97cc5a2f2e4bc780e2024a/crates/sol-types/src/types/data_type.rs#L204-L206>
241        token.0 .0[..N]
242            .try_into()
243            .expect("Expected a slice of N bytes")
244    }
245
246    fn default() -> Self {
247        [0u8; N]
248    }
249}
250
251impl<const N: usize> SolBytesTokenType for [u8; N]
252where
253    sol_data::ByteCount<N>: sol_data::SupportedFixedBytes,
254{
255    type TokenType<'enc> = WordToken;
256
257    type DefaultType = FixedSizeDefault;
258}
259
260impl<const N: usize> private::Sealed for [u8; N] {}
261
262impl SolBytesType for Vec<u8> {
263    type AlloyType = sol_data::Bytes;
264
265    const DEFAULT_VALUE: Self::DefaultType = DynSizeDefault;
266
267    fn tokenize(&self) -> Self::TokenType<'_> {
268        // Direct implementation simplifies generic implementations by removing
269        // requirement for `SolTypeValue<Self::AlloyType>`.
270        PackedSeqToken(self.as_slice())
271    }
272
273    fn detokenize(token: <Self::AlloyType as AlloySolType>::Token<'_>) -> Self {
274        // Converts token directly into `Vec<u8>`, skipping the conversion to
275        // `alloy_sol_types::private::Bytes`, which then has to be converted back to
276        // `Vec<u8>`.
277        token.into_vec()
278    }
279
280    fn default() -> Self {
281        Vec::new()
282    }
283}
284
285impl SolBytesTokenType for Vec<u8> {
286    type TokenType<'enc> = PackedSeqToken<'enc>;
287
288    type DefaultType = DynSizeDefault;
289}
290
291impl private::Sealed for Vec<u8> {}
292
293impl SolBytesType for Box<[u8]> {
294    type AlloyType = sol_data::Bytes;
295
296    const DEFAULT_VALUE: Self::DefaultType = DynSizeDefault;
297
298    fn tokenize(&self) -> Self::TokenType<'_> {
299        // Direct implementation simplifies generic implementations by removing
300        // requirement for `SolTypeValue<Self::AlloyType>`.
301        PackedSeqToken(self.as_ref())
302    }
303
304    fn detokenize(token: <Self::AlloyType as AlloySolType>::Token<'_>) -> Self {
305        // Converts token directly into `Box<[u8]>`, skipping the conversion to
306        // `alloy_sol_types::private::Bytes`, which then has to be converted back to
307        // `Box<[u8]>`.
308        Box::from(token.0)
309    }
310
311    fn default() -> Self {
312        <Self as Default>::default()
313    }
314}
315
316impl SolBytesTokenType for Box<[u8]> {
317    type TokenType<'enc> = PackedSeqToken<'enc>;
318
319    type DefaultType = DynSizeDefault;
320}
321
322impl private::Sealed for Box<[u8]> {}
323
324mod private {
325    /// Seals the implementation of `SolBytesType`.
326    pub trait Sealed {}
327}