Skip to content

Commit 14a5a46

Browse files
committed
primitives: Solidity ABI encode/decode for function params
1 parent bfae0b1 commit 14a5a46

File tree

3 files changed

+149
-0
lines changed

3 files changed

+149
-0
lines changed

crates/primitives/src/sol.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//! Abstractions for implementing Solidity ABI encoding/decoding for arbitrary Rust types.
1616
1717
mod bytes;
18+
mod params;
1819
mod types;
1920

2021
#[cfg(test)]
@@ -40,6 +41,10 @@ use primitive_types::{
4041

4142
pub use self::{
4243
bytes::SolBytes,
44+
params::{
45+
SolParamsDecode,
46+
SolParamsEncode,
47+
},
4348
types::{
4449
SolTypeDecode,
4550
SolTypeEncode,
@@ -149,6 +154,11 @@ pub trait SolEncode<'a> {
149154
const SOL_NAME: &'static str =
150155
<<Self::SolType as SolTypeEncode>::AlloyType as AlloySolType>::SOL_NAME;
151156

157+
/// Whether the ABI encoded size is dynamic.
158+
#[doc(hidden)]
159+
const DYNAMIC: bool =
160+
<<Self::SolType as SolTypeEncode>::AlloyType as AlloySolType>::DYNAMIC;
161+
152162
/// Solidity ABI encode the value.
153163
fn encode(&'a self) -> Vec<u8> {
154164
<Self::SolType as SolTypeEncode>::encode(&self.to_sol_type())

crates/primitives/src/sol/params.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
15+
use alloy_sol_types::{
16+
abi,
17+
SolType as AlloySolType,
18+
};
19+
use impl_trait_for_tuples::impl_for_tuples;
20+
21+
use super::{
22+
SolTypeDecode,
23+
SolTypeEncode,
24+
};
25+
26+
/// Solidity ABI decode function parameters.
27+
///
28+
/// # Note
29+
///
30+
/// This trait is sealed and cannot be implemented for types outside `ink_primitives`.
31+
pub trait SolParamsDecode: Sized + super::types::private::Sealed {
32+
/// Solidity ABI decode function parameters into this type.
33+
fn decode(data: &[u8]) -> Result<Self, alloy_sol_types::Error>;
34+
}
35+
36+
/// Solidity ABI encode function parameters.
37+
///
38+
/// # Note
39+
///
40+
/// This trait is sealed and cannot be implemented for types outside `ink_primitives`.
41+
pub trait SolParamsEncode: super::types::private::Sealed {
42+
/// Solidity ABI encode the value as function parameters.
43+
fn encode(&self) -> Vec<u8>;
44+
}
45+
46+
// We follow the Rust standard library's convention of implementing traits for tuples up
47+
// to twelve items long.
48+
// Ref: <https://doc.rust-lang.org/std/primitive.tuple.html#trait-implementations>
49+
#[impl_for_tuples(12)]
50+
#[tuple_types_custom_trait_bound(SolTypeDecode)]
51+
impl SolParamsDecode for Tuple {
52+
fn decode(data: &[u8]) -> Result<Self, alloy_sol_types::Error> {
53+
abi::decode_params::<
54+
<<Self as SolTypeDecode>::AlloyType as AlloySolType>::Token<'_>,
55+
>(data)
56+
.and_then(<Self as SolTypeDecode>::detokenize)
57+
}
58+
}
59+
60+
#[impl_for_tuples(12)]
61+
#[tuple_types_custom_trait_bound(SolTypeEncode)]
62+
impl SolParamsEncode for Tuple {
63+
fn encode(&self) -> Vec<u8> {
64+
abi::encode_params(&self.tokenize())
65+
}
66+
}

crates/primitives/src/sol/tests.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ use crate::{
3939
SolBytes,
4040
SolDecode,
4141
SolEncode,
42+
SolParamsDecode,
43+
SolParamsEncode,
4244
SolTypeDecode,
4345
SolTypeEncode,
4446
},
@@ -548,3 +550,74 @@ fn encode_refs_works() {
548550
[]
549551
);
550552
}
553+
554+
macro_rules! test_case_params {
555+
($ty: ty, $val: expr) => {
556+
test_case_params!($ty, $val, $ty, alloy_sol_types::SolValue, $val, [], [])
557+
};
558+
($ty: ty, $val: expr, $sol_ty: ty, $sol_trait: ty) => {
559+
test_case_params!($ty, $val, $sol_ty, $sol_trait, $val, [], [])
560+
};
561+
($ty: ty, $val: expr, $sol_ty: ty, $sol_trait: ty, $sol_val: expr, [$($ty_cvt: tt)*], [$($sol_ty_cvt: tt)*]) => {
562+
// `SolParamsEncode` test.
563+
let encoded = <$ty as SolParamsEncode>::encode(&$val);
564+
let encoded_alloy = <$sol_ty as $sol_trait>::abi_encode_params(&$sol_val);
565+
assert_eq!(encoded, encoded_alloy);
566+
567+
// `SolParamsDecode` test.
568+
let decoded = <$ty as SolParamsDecode>::decode(&encoded);
569+
let decoded_alloy = <$sol_ty as $sol_trait>::abi_decode_params(&encoded);
570+
assert_eq!(decoded$($ty_cvt)*, decoded_alloy$($sol_ty_cvt)*);
571+
};
572+
}
573+
574+
#[test]
575+
fn params_works() {
576+
test_case_params!((), ());
577+
test_case_params!((bool,), (true,));
578+
// `SolValue` isn't implemented for `u8`.
579+
test_case_params!((u8,), (100u8,), (sol_data::Uint<8>,), AlloySolType);
580+
test_case_params!(
581+
(bool, i8, u32, String),
582+
(true, 100i8, 1_000_000u32, String::from("Hello, world!"))
583+
);
584+
585+
// simple sequences/collections.
586+
test_case_params!(([i8; 32],), ([100i8; 32],));
587+
test_case_params!((Vec<i8>,), (Vec::from([100i8; 64]),));
588+
test_case_params!(([i8; 32], Vec<i8>), ([100i8; 32], Vec::from([100i8; 64])));
589+
590+
// sequences of addresses.
591+
test_case_params!(
592+
([Address; 4],), ([Address::from([1; 20]); 4],),
593+
([AlloyAddress; 4],), SolValue, ([AlloyAddress::from([1; 20]); 4],),
594+
[.unwrap().0.map(|val| val.0)], [.unwrap().0.map(|val| val.0)]
595+
);
596+
test_case_params!(
597+
(Vec<Address>,), (Vec::from([Address::from([1; 20]); 4]),),
598+
(Vec<AlloyAddress>,), SolValue, (Vec::from([AlloyAddress::from([1; 20]); 4]),),
599+
[.unwrap().0.into_iter().map(|val| val.0).collect::<Vec<_>>()], [.unwrap().0.into_iter().map(|val| val.0).collect::<Vec<_>>()]
600+
);
601+
602+
// fixed-size byte arrays.
603+
test_case_params!(
604+
(SolBytes<[u8; 32]>,),
605+
(SolBytes([100u8; 32]),),
606+
(AlloyFixedBytes<32>,),
607+
SolValue,
608+
(AlloyFixedBytes([100u8; 32]),),
609+
[.unwrap().0.0],
610+
[.unwrap().0.0]
611+
);
612+
613+
// dynamic size byte arrays.
614+
test_case_params!(
615+
(SolBytes<Vec<u8>>,),
616+
(SolBytes(Vec::from([100u8; 64])),),
617+
(AlloyBytes,),
618+
SolValue,
619+
(AlloyBytes::from([100u8; 64]),),
620+
[.unwrap().0.0],
621+
[.unwrap().0.0]
622+
);
623+
}

0 commit comments

Comments
 (0)