Sitelet https://github.com/alloy-rs/core/commit/dd2b02da25b1b0a111f38b01767720413882e56c
Skip to content

Commit dd2b02d

Browse files
authored
feat(sol-types): add AbiDecoderConfig (#1167)
* fix(sol-types): limit ABI decoder memory Track cumulative allocations while decoding dynamic ABI values so aliased offsets cannot amplify small calldata into unbounded memory use. Add configurable recursion, memory, and strict validation limits while preserving existing decode APIs. * fix(sol-types): track tuple decoder memory * refactor(sol-types): scope decoder children * refactor(sol-types): share decoder state * refactor(sol-types): remove child decode helpers * refactor(sol-types): add decoder child ctor * test(sol-types): scope aliased call types * fix(sol-types): use unsafe cell for decoder state * chore: cleanup * fix: lifetime children * refactor(sol-types): rename decoder validation config
1 parent 6cddb17 commit dd2b02d

17 files changed

Lines changed: 836 additions & 140 deletions

File tree

‎crates/dyn-abi/src/dynamic/token.rs‎

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,20 @@ impl<'a> DynToken<'a> {
139139

140140
/// Decodes from a decoder, populating the structure with the decoded data.
141141
#[inline]
142-
pub(crate) fn decode_populate(&mut self, dec: &mut Decoder<'a>) -> Result<()> {
142+
pub(crate) fn decode_populate(&mut self, dec: &mut Decoder<'a, '_>) -> Result<()> {
143143
match self {
144144
Self::Word(w) => *w = WordToken::decode_from(dec)?.0,
145145
Self::FixedSeq(..) => {
146-
let dynamic = self.is_dynamic();
147-
let mut child = if dynamic { dec.take_indirection() } else { dec.raw_child() }?;
148-
149-
self.decode_sequence_populate(&mut child)?;
150-
151-
if !dynamic {
152-
dec.take_offset_from(&child);
146+
if self.is_dynamic() {
147+
let mut child = dec.take_indirection()?;
148+
self.decode_sequence_populate(&mut child)?;
149+
} else {
150+
let offset = {
151+
let mut child = dec.raw_child()?;
152+
self.decode_sequence_populate(&mut child)?;
153+
dec.offset_from_child(&child)
154+
};
155+
dec.set_offset(offset);
153156
}
154157
}
155158
Self::DynSeq { contents, template } => {
@@ -189,6 +192,7 @@ impl<'a> DynToken<'a> {
189192
// re-use the box allocation
190193
unsafe { Vec::from_raw_parts(Box::into_raw(template), 1, 1) }
191194
} else {
195+
child.reserve_elements::<Self>(size)?;
192196
try_vec![*template; size]?
193197
};
194198

@@ -206,7 +210,7 @@ impl<'a> DynToken<'a> {
206210
/// Decode a sequence from the decoder, populating the data by consuming
207211
/// decoder words.
208212
#[inline]
209-
pub(crate) fn decode_sequence_populate(&mut self, dec: &mut Decoder<'a>) -> Result<()> {
213+
pub(crate) fn decode_sequence_populate(&mut self, dec: &mut Decoder<'a, '_>) -> Result<()> {
210214
match self {
211215
Self::FixedSeq(buf, size) => {
212216
buf.to_mut().iter_mut().take(*size).try_for_each(|item| item.decode_populate(dec))
@@ -218,7 +222,7 @@ impl<'a> DynToken<'a> {
218222

219223
/// Decode a single item of this type, as a sequence of length 1.
220224
#[inline]
221-
pub(crate) fn decode_single_populate(&mut self, dec: &mut Decoder<'a>) -> Result<()> {
225+
pub(crate) fn decode_single_populate(&mut self, dec: &mut Decoder<'a, '_>) -> Result<()> {
222226
// This is what
223227
// `Self::FixedSeq(vec![self.clone()], 1).decode_populate()`
224228
// would do, so we skip the allocation.

‎crates/dyn-abi/src/dynamic/ty.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,11 +602,11 @@ impl DynSolType {
602602
#[cfg_attr(debug_assertions, track_caller)]
603603
pub(crate) fn abi_decode_inner<'d, F>(
604604
&self,
605-
decoder: &mut Decoder<'d>,
605+
decoder: &mut Decoder<'d, '_>,
606606
f: F,
607607
) -> Result<DynSolValue>
608608
where
609-
F: FnOnce(&mut DynToken<'d>, &mut Decoder<'d>) -> Result<()>,
609+
F: FnOnce(&mut DynToken<'d>, &mut Decoder<'d, '_>) -> Result<()>,
610610
{
611611
if self.is_zst() {
612612
return Ok(self.zero_sized_value().expect("checked"));

‎crates/sol-macro-expander/src/expand/contract.rs‎

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -757,11 +757,34 @@ impl CallLikeExpander<'_> {
757757
selector: [u8; 4],
758758
data: &[u8],
759759
)-> alloy_sol_types::Result<Self> {
760-
static DECODE_SHIMS: &[fn(&[u8]) -> alloy_sol_types::Result<#name>] = &[
760+
Self::abi_decode_raw_with_config(
761+
selector,
762+
data,
763+
alloy_sol_types::abi::AbiDecoderConfig::default(),
764+
)
765+
}
766+
767+
#[inline]
768+
#[allow(non_snake_case)]
769+
fn abi_decode_raw_with_config(
770+
selector: [u8; 4],
771+
data: &[u8],
772+
config: alloy_sol_types::abi::AbiDecoderConfig,
773+
) -> alloy_sol_types::Result<Self> {
774+
static DECODE_SHIMS: &[fn(
775+
&[u8],
776+
alloy_sol_types::abi::AbiDecoderConfig,
777+
) -> alloy_sol_types::Result<#name>] = &[
761778
#({
762-
fn #sorted_variants(data: &[u8]) -> alloy_sol_types::Result<#name> {
763-
<#sorted_types as alloy_sol_types::#trait_>::abi_decode_raw(data)
764-
.map(#name::#sorted_variants)
779+
fn #sorted_variants(
780+
data: &[u8],
781+
config: alloy_sol_types::abi::AbiDecoderConfig,
782+
) -> alloy_sol_types::Result<#name> {
783+
<#sorted_types as alloy_sol_types::#trait_>::abi_decode_raw_with_config(
784+
data,
785+
config,
786+
)
787+
.map(#name::#sorted_variants)
765788
}
766789
#sorted_variants
767790
}),*
@@ -774,33 +797,22 @@ impl CallLikeExpander<'_> {
774797
));
775798
};
776799
// `SELECTORS` and `DECODE_SHIMS` have the same length and are sorted in the same order.
777-
DECODE_SHIMS[idx](data)
800+
DECODE_SHIMS[idx](data, config)
778801
}
779802

780803
#[inline]
781804
#[allow(non_snake_case)]
805+
// TODO: Deprecate in favor of a validating decoder configuration.
806+
// #[deprecated(note = "use a validating decoder configuration")]
782807
fn abi_decode_raw_validate(
783808
selector: [u8; 4],
784809
data: &[u8],
785810
) -> alloy_sol_types::Result<Self> {
786-
static DECODE_VALIDATE_SHIMS: &[fn(&[u8]) -> alloy_sol_types::Result<#name>] = &[
787-
#({
788-
fn #sorted_variants(data: &[u8]) -> alloy_sol_types::Result<#name> {
789-
<#sorted_types as alloy_sol_types::#trait_>::abi_decode_raw_validate(data)
790-
.map(#name::#sorted_variants)
791-
}
792-
#sorted_variants
793-
}),*
794-
];
795-
796-
let Ok(idx) = Self::SELECTORS.binary_search(&selector) else {
797-
return Err(alloy_sol_types::Error::unknown_selector(
798-
<Self as alloy_sol_types::SolInterface>::NAME,
799-
selector,
800-
));
801-
};
802-
// `SELECTORS` and `DECODE_VALIDATE_SHIMS` have the same length and are sorted in the same order.
803-
DECODE_VALIDATE_SHIMS[idx](data)
811+
Self::abi_decode_raw_with_config(
812+
selector,
813+
data,
814+
alloy_sol_types::abi::AbiDecoderConfig::new().validate(true),
815+
)
804816
}
805817

806818
#[inline]

‎crates/sol-macro-expander/src/expand/error.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,13 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, error: &ItemError) -> Result<TokenStream>
116116
}
117117

118118
#[inline]
119+
// TODO: Deprecate in favor of a validating decoder configuration.
120+
// #[deprecated(note = "use a validating decoder configuration")]
119121
fn abi_decode_raw_validate(data: &[u8]) -> alloy_sol_types::Result<Self> {
120-
<Self::Parameters<'_> as alloy_sol_types::SolType>::abi_decode_sequence_validate(data).map(Self::new)
122+
Self::abi_decode_raw_with_config(
123+
data,
124+
alloy_sol_types::abi::AbiDecoderConfig::new().validate(true),
125+
)
121126
}
122127
}
123128

‎crates/sol-macro-expander/src/expand/function.rs‎

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,19 +155,21 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result<TokenS
155155
quote!(#decode_sequence.map(Into::into))
156156
};
157157

158-
let decode_sequence_validate = quote!(
159-
<Self::ReturnTuple<'_> as alloy_sol_types::SolType>::abi_decode_sequence_validate(data)
158+
let decode_sequence_with_config = quote!(
159+
<Self::ReturnTuple<'_> as alloy_sol_types::SolType>::abi_decode_sequence_with_config(
160+
data, config,
161+
)
160162
);
161-
let decode_returns_validate = if is_single_return {
163+
let decode_returns_with_config = if is_single_return {
162164
let name = anon_name((0, returns[0].name.as_ref()));
163165
quote! {
164-
#decode_sequence_validate.map(|r| {
166+
#decode_sequence_with_config.map(|r| {
165167
let r: #return_name = r.into();
166168
r.#name
167169
})
168170
}
169171
} else {
170-
quote!(#decode_sequence_validate.map(Into::into))
172+
quote!(#decode_sequence_with_config.map(Into::into))
171173
};
172174

173175
let tokens = quote! {
@@ -228,8 +230,21 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, function: &ItemFunction) -> Result<TokenS
228230
}
229231

230232
#[inline]
233+
fn abi_decode_returns_with_config(
234+
data: &[u8],
235+
config: alloy_sol_types::abi::AbiDecoderConfig,
236+
) -> alloy_sol_types::Result<Self::Return> {
237+
#decode_returns_with_config
238+
}
239+
240+
#[inline]
241+
// TODO: Deprecate in favor of a validating decoder configuration.
242+
// #[deprecated(note = "use a validating decoder configuration")]
231243
fn abi_decode_returns_validate(data: &[u8]) -> alloy_sol_types::Result<Self::Return> {
232-
#decode_returns_validate
244+
Self::abi_decode_returns_with_config(
245+
data,
246+
alloy_sol_types::abi::AbiDecoderConfig::new().validate(true),
247+
)
233248
}
234249
}
235250

0 commit comments

Comments
 (0)