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