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}