Skip to content

Commit 11887f1

Browse files
Implement SolDecode, SolTypeDecode and support SolBytes for boxed slices (#2476)
* Implement `SolDecode` and `SolTypeDecode` for boxed slices (i.e. `Box<[T]>` and Box<str>) * tests: boxed slices (i.e. `Box<[T]>` and `Box<str>`) * docs: Update Solidity mappings * Make `SolTypeDecode::detokenize` fallible * Add support for `Box<[u8]>` to `SolBytes` * docs: Update Solidity mappings * tests: boxed byte slices * Update changelog * fix: `SolEncode` for `Box<[T]>` * fix: core/std imports * docs: Update Solidity mappings
1 parent b4cc207 commit 11887f1

File tree

8 files changed

+260
-63
lines changed

8 files changed

+260
-63
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ You can find binary releases of the node [here](https://github.com/use-ink/ink-n
221221
- Implement contract invocation in off-chain environment engine - [#1957](https://github.com/paritytech/ink/pull/1988)
222222
- Abstractions for mapping arbitrary Rust types to Solidity ABI compatible types - [#2441](https://github.com/use-ink/ink/pull/2441)
223223
- Documentation for contract abi arg and provided Rust/ink! to Solidity type mappings - [2463](https://github.com/use-ink/ink/pull/2463)
224+
- Implement `SolDecode`, `SolTypeDecode` and support `SolBytes` for boxed slices - [2476](https://github.com/use-ink/ink/pull/2476)
224225

225226
## Fixed
226227
- [E2E] Have port parsing handle comma-separated list ‒ [#2336](https://github.com/use-ink/ink/pull/2336)

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ink/macro/src/lib.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,22 +201,25 @@ pub fn selector_bytes(input: TokenStream) -> TokenStream {
201201
/// | `uN` for `N ∈ {8,16,32,64,128}` | `uintN` | e.g `u8` ↔ `uint8` |
202202
/// | [`ink::U256`][ink-u256] | `uint256` ||
203203
/// | `String` | `string` ||
204-
/// | [`ink::Address`][ink-address] / [`H160`][ink-h160] | `address` | `ink::Address` is a type alias for the `ink::H160` type used for addresses in `pallet-revive` |
204+
/// | `Box<str>` | `string` ||
205+
/// | [`ink::Address`][ink-address] / [`ink::H160`][ink-h160] | `address` | `ink::Address` is a type alias for the `ink::H160` type used for addresses in `pallet-revive` |
205206
/// | `[T; N]` for `const N: usize` | `T[N]` | e.g. `[i8; 64]` ↔ `int8[64]` |
206207
/// | `Vec<T>` | `T[]` | e.g. `Vec<i8>` ↔ `int8[]` |
207-
/// | [`ink::SolBytes<u8>`][ink-sol-bytes] | `bytes1` ||
208-
/// | [`ink::SolBytes<[u8; N]>`][ink-sol-bytes] for `1 <= N <= 32` | `bytesN` | e.g. `ink::SolBytes<[u8; 1]>` ↔ `bytes1` |
209-
/// | [`ink::SolBytes<Vec<u8>>`][ink-sol-bytes] | `bytes` ||
208+
/// | `Box<[T]>` | `T[]` | e.g. `Box<[i8]>` ↔ `int8[]` |
209+
/// | [`ink::SolBytes<u8>`][ink-sol-bytes] | `bytes1` ||
210+
/// | [`ink::SolBytes<[u8; N]>`][ink-sol-bytes] for `1 <= N <= 32` | `bytesN` | e.g. `ink::SolBytes<[u8; 1]>` ↔ `bytes1` |
211+
/// | [`ink::SolBytes<Vec<u8>>`][ink-sol-bytes] | `bytes` ||
212+
/// | [`ink::SolBytes<Box<[u8]>>`][ink-sol-bytes] | `bytes` ||
210213
/// | `(T1, T2, T3, ... T12)` | `(U1, U2, U3, ... U12)` | where `T1` ↔ `U1`, ... `T12` ↔ `U12` e.g. `(bool, u8, Address)` ↔ `(bool, uint8, address)` |
211214
///
212215
/// [`SolEncode`][sol-trait-encode] is additionally implemented for reference and smart
213216
/// pointer types below:
214217
///
215218
/// | Rust/ink! type | Solidity ABI type | Notes |
216219
/// | -------------- | ----------------- | ----- |
217-
/// | `&str`, `&mut str`, `Box<str>` | `string` ||
220+
/// | `&str`, `&mut str` | `string` ||
218221
/// | `&T`, `&mut T`, `Box<T>` | `T` | e.g. `&i8 ↔ int8` |
219-
/// | `&[T]`, `&mut [T]`, `Box<[T]>` | `T[]` | e.g. `&[i8]` ↔ `int8[]` |
222+
/// | `&[T]`, `&mut [T]` | `T[]` | e.g. `&[i8]` ↔ `int8[]` |
220223
///
221224
/// See the rustdoc for [`SolEncode`][sol-trait-encode] and
222225
/// [`SolDecode`][sol-trait-decode] for instructions for implementing the traits for

crates/primitives/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ sp-core = { workspace = true }
3535
sp-io = { git = "https://github.com/paritytech/polkadot-sdk", rev = "28a7ae71cc0eac747bf346804279217a68f700da", default-features = false, features = ["disable_panic_handler", "disable_oom", "disable_allocator"] }
3636
sp-runtime-interface = { git = "https://github.com/paritytech/polkadot-sdk", rev = "28a7ae71cc0eac747bf346804279217a68f700da", default-features = false, features = ["disable_target_static_assertions"] }
3737
impl-trait-for-tuples = { workspace = true }
38+
itertools = { workspace = true }
3839

3940
[dev-dependencies]
4041
ink = { workspace = true, default-features = false }

crates/primitives/src/sol.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ impl_primitive! {
206206
u8, u16, u32, u64, u128, U256,
207207
// string
208208
String,
209+
Box<str>,
209210
// address
210211
Address,
211212
}
@@ -247,6 +248,29 @@ impl<'a, T: SolEncode<'a>> SolEncode<'a> for Vec<T> {
247248
}
248249
}
249250

251+
// Rust `Box<[T]>` (i.e. boxed slice) <-> Solidity dynamic size array (i.e. `T[]`).
252+
impl<T: SolDecode> SolDecode for Box<[T]> {
253+
type SolType = Box<[T::SolType]>;
254+
255+
fn from_sol_type(value: Self::SolType) -> Self {
256+
// TODO: (@davidsemakula) Switch to method call syntax when edition is 2024
257+
// (i.e. `value.into_iter()`).
258+
// See <https://doc.rust-lang.org/edition-guide/rust-2024/intoiterator-box-slice.html> for details.
259+
Box::from_iter(
260+
core::iter::IntoIterator::into_iter(value)
261+
.map(<T as SolDecode>::from_sol_type),
262+
)
263+
}
264+
}
265+
266+
impl<'a, T: SolEncode<'a>> SolEncode<'a> for Box<[T]> {
267+
type SolType = Box<[T::SolType]>;
268+
269+
fn to_sol_type(&'a self) -> Self::SolType {
270+
self.iter().map(<T as SolEncode>::to_sol_type).collect()
271+
}
272+
}
273+
250274
// We follow the Rust standard library's convention of implementing traits for tuples up
251275
// to twelve items long.
252276
// Ref: <https://doc.rust-lang.org/std/primitive.tuple.html#trait-implementations>
@@ -314,7 +338,7 @@ macro_rules! impl_str_ref_encode {
314338
};
315339
}
316340

317-
impl_str_ref_encode!(&'a str, &'a mut str, Box<str>);
341+
impl_str_ref_encode!(&'a str, &'a mut str);
318342

319343
macro_rules! impl_slice_ref_encode {
320344
($($ty: ty),+ $(,)*) => {
@@ -330,7 +354,7 @@ macro_rules! impl_slice_ref_encode {
330354
};
331355
}
332356

333-
impl_slice_ref_encode!(&'a [T], &'a mut [T], Box<[T]>);
357+
impl_slice_ref_encode!(&'a [T], &'a mut [T]);
334358

335359
// AccountId <-> bytes32
336360
impl SolDecode for AccountId {

crates/primitives/src/sol/bytes.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ use core::{
2424
borrow::Borrow,
2525
ops::Deref,
2626
};
27-
use ink_prelude::vec::Vec;
27+
use ink_prelude::{
28+
boxed::Box,
29+
vec::Vec,
30+
};
2831
use scale::{
2932
Decode,
3033
Encode,
@@ -46,7 +49,8 @@ use crate::sol::{
4649
/// | -------------- | ----------------- | ----- |
4750
/// | `SolBytes<u8>` | `bytes1` ||
4851
/// | `SolBytes<[u8; N]>` for `1 <= N <= 32` | `bytesN` | e.g. `SolBytes<[u8; 32]>` <=> `bytes32` |
49-
/// | `SolBytes<Vec<u8>>` | `bytes` ||
52+
/// | `SolBytes<Vec<u8>>` | `bytes` ||
53+
/// | `SolBytes<Box<[u8]>>` | `bytes` ||
5054
///
5155
/// Ref: <https://docs.soliditylang.org/en/latest/types.html#fixed-size-byte-arrays>
5256
///
@@ -55,14 +59,16 @@ use crate::sol::{
5559
#[cfg_attr(feature = "std", derive(TypeInfo))]
5660
pub struct SolBytes<T: SolBytesType>(pub T);
5761

58-
// Implement `SolTypeDecode` and `SolTypeEncode` for `SolBytes<T>`.
62+
// Implements `SolTypeDecode` and `SolTypeEncode` for `SolBytes<T>`.
5963
impl<T: SolBytesType> SolTypeDecode for SolBytes<T> {
6064
type AlloyType = T::AlloyType;
6165

62-
fn detokenize(token: <Self::AlloyType as AlloySolType>::Token<'_>) -> Self {
66+
fn detokenize(
67+
token: <Self::AlloyType as AlloySolType>::Token<'_>,
68+
) -> Result<Self, alloy_sol_types::Error> {
6369
// Takes advantage of optimized `SolBytesType::detokenize` implementations and
6470
// skips unnecessary conversions to `T::AlloyType::RustType`.
65-
Self(<T as SolBytesType>::detokenize(token))
71+
Ok(Self(<T as SolBytesType>::detokenize(token)))
6672
}
6773
}
6874

@@ -76,7 +82,7 @@ impl<T: SolBytesType> SolTypeEncode for SolBytes<T> {
7682

7783
impl<T: SolBytesType> crate::sol::types::private::Sealed for SolBytes<T> {}
7884

79-
// Implement `SolDecode` and `SolEncode` for `SolBytes<T>`.
85+
// Implements `SolDecode` and `SolEncode` for `SolBytes<T>`.
8086
impl<T: SolBytesType> SolDecode for SolBytes<T> {
8187
type SolType = SolBytes<T>;
8288

@@ -93,7 +99,7 @@ impl<'a, T: SolBytesType + 'a> SolEncode<'a> for SolBytes<T> {
9399
}
94100
}
95101

96-
// Implement core/standard traits for cheap representations as the inner type.
102+
// Implements core/standard traits for cheap representations as the inner type.
97103
impl<T: SolBytesType> Deref for SolBytes<T> {
98104
type Target = T;
99105

@@ -141,7 +147,7 @@ pub trait SolBytesType: private::Sealed {
141147
fn detokenize(token: <Self::AlloyType as AlloySolType>::Token<'_>) -> Self;
142148
}
143149

144-
// Implement `SolBytesType` for `u8`, `[u8; N]` and `Vec<u8>`.
150+
// Implements `SolBytesType` for `u8`, `[u8; N]`, `Vec<u8>` and `Box<[u8]>`.
145151
impl SolBytesType for u8
146152
where
147153
sol_data::ByteCount<1>: sol_data::SupportedFixedBytes,
@@ -210,6 +216,25 @@ impl SolBytesType for Vec<u8> {
210216

211217
impl private::Sealed for Vec<u8> {}
212218

219+
impl 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+
236+
impl private::Sealed for Box<[u8]> {}
237+
213238
mod private {
214239
/// Seals the implementation of `SolBytesType`.
215240
pub trait Sealed {}

crates/primitives/src/sol/tests.rs

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,29 @@ fn unsigned_int_works() {
129129

130130
#[test]
131131
fn string_works() {
132+
// String
132133
test_case!(String, String::from(""));
133134
test_case!(String, String::from("Hello, world!"));
135+
136+
// `Box<str>`
137+
test_case!(
138+
Box<str>,
139+
Box::from(""),
140+
String,
141+
SolValue,
142+
String::from(""),
143+
[.unwrap().as_ref()],
144+
[.unwrap().as_str()]
145+
);
146+
test_case!(
147+
Box<str>,
148+
Box::from("Hello, world!"),
149+
String,
150+
SolValue,
151+
String::from("Hello, world!"),
152+
[.unwrap().as_ref()],
153+
[.unwrap().as_str()]
154+
);
134155
}
135156

136157
#[test]
@@ -180,12 +201,32 @@ fn fixed_array_works() {
180201
fn dynamic_array_works() {
181202
test_case!(Vec<bool>, vec![true, false, false, true]);
182203

204+
test_case!(
205+
Box<[bool]>,
206+
Box::from([true, false, false, true]),
207+
Vec<bool>,
208+
SolValue,
209+
vec![true, false, false, true],
210+
[.unwrap().as_ref()],
211+
[.unwrap().as_slice()]
212+
);
213+
183214
test_case!(Vec<i8>, Vec::from([100i8; 8]));
184215
test_case!(Vec<i16>, Vec::from([-10_000i16; 16]));
185216
test_case!(Vec<i32>, Vec::from([1_000_000i32; 32]));
186217
test_case!(Vec<i64>, Vec::from([-1_000_000_000i64; 64]));
187218
test_case!(Vec<i128>, Vec::from([1_000_000_000_000i128; 128]));
188219

220+
test_case!(
221+
Box<[i8]>,
222+
Box::from([100i8; 8]),
223+
Vec<i8>,
224+
SolValue,
225+
Vec::from([100i8; 8]),
226+
[.unwrap().as_ref()],
227+
[.unwrap().as_slice()]
228+
);
229+
189230
// `SolValue` for `Vec<u8>` maps to `bytes`.
190231
test_case!(
191232
Vec<u8>,
@@ -203,6 +244,16 @@ fn dynamic_array_works() {
203244
vec![String::from(""), String::from("Hello, world!")]
204245
);
205246

247+
test_case!(
248+
Box<[String]>,
249+
Box::from([String::from(""), String::from("Hello, world!")]),
250+
Vec<String>,
251+
SolValue,
252+
vec![String::from(""), String::from("Hello, world!")],
253+
[.unwrap().as_ref()],
254+
[.unwrap().as_slice()]
255+
);
256+
206257
test_case!(
207258
Vec<Address>, Vec::from([Address::from([1; 20]); 4]),
208259
Vec<AlloyAddress>, SolValue, Vec::from([AlloyAddress::from([1; 20]); 4]),
@@ -219,16 +270,16 @@ fn fixed_bytes_works() {
219270
);
220271

221272
macro_rules! fixed_bytes_test_case {
222-
($($size: literal),+ $(,)*) => {
223-
$(
224-
test_case!(
225-
SolBytes<[u8; $size]>, SolBytes([100u8; $size]),
226-
AlloyFixedBytes<$size>, SolValue, AlloyFixedBytes([100u8; $size]),
227-
[.unwrap().0], [.unwrap().0]
228-
);
229-
)+
230-
};
231-
}
273+
($($size: literal),+ $(,)*) => {
274+
$(
275+
test_case!(
276+
SolBytes<[u8; $size]>, SolBytes([100u8; $size]),
277+
AlloyFixedBytes<$size>, SolValue, AlloyFixedBytes([100u8; $size]),
278+
[.unwrap().0], [.unwrap().0]
279+
);
280+
)+
281+
};
282+
}
232283

233284
fixed_bytes_test_case!(
234285
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
@@ -239,23 +290,30 @@ fn fixed_bytes_works() {
239290
#[test]
240291
fn bytes_works() {
241292
macro_rules! bytes_test_case {
242-
($($fixture_size: literal),+ $(,)*) => {
243-
$(
244-
let data = Vec::from([100u8; $fixture_size]);
245-
let bytes = SolBytes(data.clone());
246-
let sol_bytes = AlloyBytes::from(data);
247-
248-
test_case!(
249-
SolBytes<Vec<u8>>, bytes,
250-
AlloyBytes, SolValue, sol_bytes,
251-
[.unwrap().as_slice()], [.unwrap().as_ref()]
252-
);
253-
)+
254-
};
255-
}
293+
($($fixture_size: literal),+ $(,)*) => {
294+
$(
295+
let data = Vec::from([100u8; $fixture_size]);
296+
let vec_bytes = SolBytes(data.clone());
297+
let sol_bytes = AlloyBytes::from(data);
298+
299+
test_case!(
300+
SolBytes<Vec<u8>>, vec_bytes,
301+
AlloyBytes, SolValue, sol_bytes,
302+
[.unwrap().as_slice()], [.unwrap().as_ref()]
303+
);
304+
305+
let box_bytes = SolBytes(Box::from([100u8; $fixture_size]));
306+
test_case!(
307+
SolBytes<Box<[u8]>>, box_bytes,
308+
AlloyBytes, SolValue, sol_bytes,
309+
[.unwrap().0.as_ref()], [.unwrap().as_ref()]
310+
);
311+
)+
312+
};
313+
}
256314

257315
// Number/size is the dynamic size of the `Vec`.
258-
bytes_test_case!(0, 1, 10, 20, 30, 40, 50, 60, 70);
316+
bytes_test_case!(0, 1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
259317
}
260318

261319
#[test]

0 commit comments

Comments
 (0)