Skip to content

Commit 6199d0b

Browse files
non-allocating Solidity ABI encoder (#2655)
* primitives: Implement `Encodable` for `&[u8]` * primitives: Replace intermediate usage of `PackedSeqToken` with `&[u8]` * primitives: Add local `Word` abstraction * primitives: Replace intermediate usage of `WordToken` with local `Word` abstraction * primitives: Remove `Encodable` implementations for `alloy` token abstractions * primitives: non-allocating `Encoder` * primitives: Add `SolEncode::encode_to` and `SolTypeEncode::encode_to` * tests: sanity checks for `SolEncode::encode_to` and `SolTypeEncode::encode_to` * primitives: Add `SolParamsEncode::encode_to` and `encode_sequence_to` * tests: sanity checks for `SolParamsEncode::encode_to` and `encode_sequence_to` * tests: Add more tests for Solidity ABI encoding nested types * env: Update `ArgumentList` implementation * Update changelog
1 parent fef22a3 commit 6199d0b

File tree

10 files changed

+552
-209
lines changed

10 files changed

+552
-209
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- Implements the API for the `pallet-revive` host function `to_account_id` - [#2578](https://github.com/use-ink/ink/pull/2578)
1111
- Add `#[ink::contract_ref]` attribute - [#2648](https://github.com/use-ink/ink/pull/2648)
1212
- Add `ink_revive_types` (and remove `pallet-revive` dependency from `ink_e2e`) - [#2657](https://github.com/use-ink/ink/pull/2657)
13+
- non-allocating Solidity ABI encoder - [#2655](https://github.com/use-ink/ink/pull/2655)
1314

1415
### Changed
1516
- Marks the `pallet-revive` host function `account_id` stable - [#2578](https://github.com/use-ink/ink/pull/2578)

crates/env/src/call/execution.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,20 @@ where
403403
.collect()
404404
}
405405

406+
fn encode_to(&self, buffer: &mut [u8]) -> usize {
407+
// TODO: (@davidsemakula) Optimized implementation.
408+
let encoded = SolEncode::encode(self);
409+
let len = encoded.len();
410+
debug_assert!(
411+
len <= buffer.len(),
412+
"encode scope buffer overflowed, encoded len is {} but buffer len is {}",
413+
len,
414+
buffer.len()
415+
);
416+
buffer[..len].copy_from_slice(&encoded);
417+
len
418+
}
419+
406420
// NOTE: Not actually used for encoding because of `encode` override above.
407421
fn to_sol_type(&self) {}
408422
}

crates/primitives/src/abi.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,7 @@ where
127127
}
128128

129129
fn encode_to_slice(&self, buffer: &mut [u8]) -> usize {
130-
let encoded = SolEncode::encode(self);
131-
let len = encoded.len();
132-
debug_assert!(
133-
len <= buffer.len(),
134-
"encode scope buffer overflowed, encoded len is {} but buffer len is {}",
135-
len,
136-
buffer.len()
137-
);
138-
buffer[..len].copy_from_slice(&encoded);
139-
len
130+
SolEncode::encode_to(self, buffer)
140131
}
141132

142133
fn encode_to_vec(&self, buffer: &mut Vec<u8>) {

crates/primitives/src/sol.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod macros;
1919

2020
mod bytes;
2121
mod encodable;
22+
mod encoder;
2223
mod error;
2324
mod params;
2425
mod result;
@@ -182,11 +183,21 @@ pub trait SolEncode<'a> {
182183
const DYNAMIC: bool =
183184
<<Self::SolType as SolTypeEncode>::AlloyType as AlloySolType>::DYNAMIC;
184185

185-
/// Solidity ABI encode the value.
186+
/// Solidity ABI encode the value
186187
fn encode(&'a self) -> Vec<u8> {
187188
<Self::SolType as SolTypeEncode>::encode(&self.to_sol_type())
188189
}
189190

191+
/// Solidity ABI encode the value into the given buffer, and returns the number of
192+
/// bytes written.
193+
///
194+
/// # Panics
195+
///
196+
/// Panics if the buffer is not large enough.
197+
fn encode_to(&'a self, buffer: &mut [u8]) -> usize {
198+
<Self::SolType as SolTypeEncode>::encode_to(&self.to_sol_type(), buffer)
199+
}
200+
190201
/// Solidity ABI encode the value as a topic (i.e. an indexed event parameter).
191202
fn encode_topic<H>(&'a self, hasher: H) -> [u8; 32]
192203
where
@@ -207,13 +218,31 @@ pub trait SolEncode<'a> {
207218
/// - `T` must be a tuple type where each member implements [`SolEncode`].
208219
/// - The result can be different from [`SolEncode::encode`] for the given tuple because
209220
/// this function always returns the encoded data in place, even for tuples containing
210-
/// dynamic types (i.e. no offset is included for dynamic tuples).
221+
/// dynamic types (i.e. no top-level offset is included for dynamic tuples).
211222
///
212223
/// This function is a convenience wrapper for [`SolParamsEncode::encode`].
213224
pub fn encode_sequence<T: for<'a> SolParamsEncode<'a>>(value: &T) -> Vec<u8> {
214225
SolParamsEncode::encode(value)
215226
}
216227

228+
/// Solidity ABI encode the given value into the given buffer as a parameter sequence, and
229+
/// returns the number of bytes written.
230+
///
231+
/// # Note
232+
///
233+
/// - `T` must be a tuple type where each member implements [`SolEncode`].
234+
/// - The result can be different from [`SolEncode::encode_to`] for the given tuple
235+
/// because this function always returns the encoded data in place, even for tuples
236+
/// containing dynamic types (i.e. no top-level offset is included for dynamic tuples).
237+
///
238+
/// This function is a convenience wrapper for [`SolParamsEncode::encode_to`].
239+
pub fn encode_sequence_to<T: for<'a> SolParamsEncode<'a>>(
240+
value: &T,
241+
buffer: &mut [u8],
242+
) -> usize {
243+
SolParamsEncode::encode_to(value, buffer)
244+
}
245+
217246
/// Solidity ABI decode the given data as a parameter sequence.
218247
///
219248
/// # Note

crates/primitives/src/sol/bytes.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ use core::{
1919

2020
use alloy_sol_types::{
2121
SolType as AlloySolType,
22-
abi::token::{
23-
PackedSeqToken,
24-
WordToken,
25-
},
2622
sol_data,
2723
};
2824
use ink_prelude::{
@@ -46,6 +42,7 @@ use crate::sol::{
4642
encodable::{
4743
DynSizeDefault,
4844
FixedSizeDefault,
45+
Word,
4946
},
5047
types::SolTokenType,
5148
utils::{
@@ -108,7 +105,7 @@ where
108105
// requirement for `SolTypeValue<Self::AlloyType>`.
109106
let mut word = [0; 32];
110107
word[..N].copy_from_slice(self.0.as_slice());
111-
WordToken::from(word)
108+
Word(word)
112109
}
113110
}
114111

@@ -120,11 +117,11 @@ where
120117
where
121118
H: Fn(&[u8], &mut [u8; 32]),
122119
{
123-
self.tokenize().0.0
120+
self.tokenize().0
124121
}
125122

126123
fn topic_preimage(&self, buffer: &mut Vec<u8>) {
127-
buffer.extend(self.tokenize().0.0);
124+
buffer.extend(self.tokenize().0);
128125
}
129126

130127
fn default_topic_preimage(buffer: &mut Vec<u8>) {
@@ -144,7 +141,7 @@ impl<const N: usize> SolTokenType for FixedBytes<N>
144141
where
145142
sol_data::ByteCount<N>: sol_data::SupportedFixedBytes,
146143
{
147-
type TokenType<'enc> = WordToken;
144+
type TokenType<'enc> = Word;
148145

149146
type DefaultType = FixedSizeDefault;
150147
}
@@ -256,9 +253,7 @@ impl SolTypeEncode for DynBytes {
256253
const DEFAULT_VALUE: Self::DefaultType = DynSizeDefault;
257254

258255
fn tokenize(&self) -> Self::TokenType<'_> {
259-
// Direct implementation simplifies generic implementations by removing
260-
// requirement for `SolTypeValue<Self::AlloyType>`.
261-
PackedSeqToken(self.0.as_slice())
256+
self.0.as_slice()
262257
}
263258
}
264259

@@ -290,7 +285,7 @@ impl SolTopicEncode for DynBytes {
290285
}
291286

292287
impl SolTokenType for DynBytes {
293-
type TokenType<'enc> = PackedSeqToken<'enc>;
288+
type TokenType<'enc> = &'enc [u8];
294289

295290
type DefaultType = DynSizeDefault;
296291
}
@@ -369,9 +364,7 @@ impl SolTypeEncode for ByteSlice<'_> {
369364
const DEFAULT_VALUE: Self::DefaultType = DynSizeDefault;
370365

371366
fn tokenize(&self) -> Self::TokenType<'_> {
372-
// Direct implementation simplifies generic implementations by removing
373-
// requirement for `SolTypeValue<Self::AlloyType>`.
374-
PackedSeqToken(self.0)
367+
self.0
375368
}
376369
}
377370

@@ -403,7 +396,7 @@ impl SolTopicEncode for ByteSlice<'_> {
403396
}
404397

405398
impl SolTokenType for ByteSlice<'_> {
406-
type TokenType<'enc> = PackedSeqToken<'enc>;
399+
type TokenType<'enc> = &'enc [u8];
407400

408401
type DefaultType = DynSizeDefault;
409402
}

0 commit comments

Comments
 (0)