1use crate::DecodeError;
2
3#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub struct DecodeLimits {
6 pub max_input_bytes: usize,
8 pub max_list_items: usize,
10 pub max_nesting_depth: usize,
16 pub max_total_allocation: usize,
18 pub max_proof_nodes: usize,
20 pub max_total_items: usize,
22}
23
24impl DecodeLimits {
25 #[cfg(any(test, feature = "testing"))]
31 pub const TEST_FIXTURE: Self = Self {
32 max_input_bytes: 1 << 20,
33 max_list_items: 4096,
34 max_nesting_depth: 64,
35 max_total_allocation: 1 << 20,
36 max_proof_nodes: 1024,
37 max_total_items: 8192,
38 };
39
40 #[doc(alias = "DEPLOYMENT_TEMPLATE")]
48 pub const DEPLOYMENT_STARTING_POINT: Self = Self {
49 max_input_bytes: 2 << 20,
50 max_list_items: 16_384,
51 max_nesting_depth: 64,
52 max_total_allocation: 4 << 20,
53 max_proof_nodes: 4096,
54 max_total_items: 65_536,
55 };
56
57 #[must_use]
64 pub const fn reviewed_policy(
65 max_input_bytes: usize,
66 max_list_items: usize,
67 max_nesting_depth: usize,
68 max_total_allocation: usize,
69 max_proof_nodes: usize,
70 max_total_items: usize,
71 ) -> Self {
72 Self {
73 max_input_bytes,
74 max_list_items,
75 max_nesting_depth,
76 max_total_allocation,
77 max_proof_nodes,
78 max_total_items,
79 }
80 }
81
82 pub fn check_input_len(self, len: usize) -> Result<(), DecodeError> {
84 if len > self.max_input_bytes {
85 return Err(DecodeError::InputTooLarge);
86 }
87 Ok(())
88 }
89
90 pub fn check_list_count(self, count: usize) -> Result<(), DecodeError> {
92 if count > self.max_list_items {
93 return Err(DecodeError::ListTooLong);
94 }
95 Ok(())
96 }
97
98 pub fn check_nesting_depth(self, depth: usize) -> Result<(), DecodeError> {
103 if depth > self.max_nesting_depth {
104 return Err(DecodeError::NestingTooDeep);
105 }
106 Ok(())
107 }
108
109 pub fn check_single_allocation_limit(self, size: usize) -> Result<(), DecodeError> {
115 if size > self.max_total_allocation {
116 return Err(DecodeError::AllocationExceeded);
117 }
118 Ok(())
119 }
120
121 pub fn check_proof_node_count(self, count: usize) -> Result<(), DecodeError> {
126 if count > self.max_proof_nodes {
127 return Err(DecodeError::ProofTooLarge);
128 }
129 Ok(())
130 }
131
132 pub fn check_item_count(self, count: usize) -> Result<(), DecodeError> {
137 if count > self.max_total_items {
138 return Err(DecodeError::ItemCountExceeded);
139 }
140 Ok(())
141 }
142
143 #[must_use]
145 pub const fn accumulator(self) -> DecodeAccumulator {
146 DecodeAccumulator {
147 limits: self,
148 total_allocated: 0,
149 total_items: 0,
150 proof_nodes: 0,
151 }
152 }
153
154 pub fn validate_deployment_policy(self) -> Result<(), DecodeError> {
162 if self == Self::DEPLOYMENT_STARTING_POINT {
163 return Err(DecodeError::UnreviewedDeploymentPolicy);
164 }
165 Ok(())
166 }
167}
168
169#[derive(Debug, Eq, PartialEq)]
171pub struct DecodeAccumulator {
172 limits: DecodeLimits,
173 total_allocated: usize,
174 total_items: usize,
175 proof_nodes: usize,
176}
177
178impl DecodeAccumulator {
179 #[must_use]
181 pub const fn limits(&self) -> DecodeLimits {
182 self.limits
183 }
184
185 #[must_use]
187 pub const fn total_allocated(&self) -> usize {
188 self.total_allocated
189 }
190
191 #[must_use]
193 pub const fn total_items(&self) -> usize {
194 self.total_items
195 }
196
197 #[must_use]
199 pub const fn proof_nodes(&self) -> usize {
200 self.proof_nodes
201 }
202
203 pub fn check_input_len(&self, len: usize) -> Result<(), DecodeError> {
205 self.limits.check_input_len(len)
206 }
207
208 pub fn check_list_count(&self, count: usize) -> Result<(), DecodeError> {
210 self.limits.check_list_count(count)
211 }
212
213 pub fn check_nesting_depth(&self, depth: usize) -> Result<(), DecodeError> {
215 self.limits.check_nesting_depth(depth)
216 }
217
218 pub fn check_allocation(&mut self, size: usize) -> Result<(), DecodeError> {
220 let new_total = self
221 .total_allocated
222 .checked_add(size)
223 .ok_or(DecodeError::AllocationExceeded)?;
224 if new_total > self.limits.max_total_allocation {
225 return Err(DecodeError::AllocationExceeded);
226 }
227 self.total_allocated = new_total;
228 Ok(())
229 }
230
231 pub fn account_items(&mut self, count: usize) -> Result<(), DecodeError> {
233 let new_total = self
234 .total_items
235 .checked_add(count)
236 .ok_or(DecodeError::ItemCountExceeded)?;
237 if new_total > self.limits.max_total_items {
238 return Err(DecodeError::ItemCountExceeded);
239 }
240 self.total_items = new_total;
241 Ok(())
242 }
243
244 pub fn account_proof_nodes(&mut self, count: usize) -> Result<(), DecodeError> {
246 let new_total = self
247 .proof_nodes
248 .checked_add(count)
249 .ok_or(DecodeError::ProofTooLarge)?;
250 if new_total > self.limits.max_proof_nodes {
251 return Err(DecodeError::ProofTooLarge);
252 }
253 self.proof_nodes = new_total;
254 Ok(())
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn rejects_oversized_input() {
264 let limits = DecodeLimits {
265 max_input_bytes: 2,
266 ..DecodeLimits::TEST_FIXTURE
267 };
268 assert_eq!(limits.check_input_len(3), Err(DecodeError::InputTooLarge));
269 }
270
271 #[test]
272 fn rejects_oversized_list() {
273 let limits = DecodeLimits {
274 max_list_items: 2,
275 ..DecodeLimits::TEST_FIXTURE
276 };
277 assert_eq!(limits.check_list_count(3), Err(DecodeError::ListTooLong));
278 }
279
280 #[test]
281 fn rejects_excessive_nesting_depth() {
282 let limits = DecodeLimits {
283 max_nesting_depth: 2,
284 ..DecodeLimits::TEST_FIXTURE
285 };
286 assert_eq!(
287 limits.check_nesting_depth(3),
288 Err(DecodeError::NestingTooDeep)
289 );
290 }
291
292 #[test]
293 fn rejects_excessive_allocation() {
294 let limits = DecodeLimits {
295 max_total_allocation: 2,
296 ..DecodeLimits::TEST_FIXTURE
297 };
298 assert_eq!(
299 limits.check_single_allocation_limit(3),
300 Err(DecodeError::AllocationExceeded)
301 );
302 }
303
304 #[test]
305 fn rejects_excessive_proof_nodes() {
306 let limits = DecodeLimits {
307 max_proof_nodes: 2,
308 ..DecodeLimits::TEST_FIXTURE
309 };
310 assert_eq!(
311 limits.check_proof_node_count(3),
312 Err(DecodeError::ProofTooLarge)
313 );
314 }
315
316 #[test]
317 fn rejects_excessive_total_items() {
318 let limits = DecodeLimits {
319 max_total_items: 2,
320 ..DecodeLimits::TEST_FIXTURE
321 };
322 assert_eq!(
323 limits.check_item_count(3),
324 Err(DecodeError::ItemCountExceeded)
325 );
326 }
327
328 #[test]
329 fn fixture_and_deployment_starting_point_limits_are_distinct() {
330 let production = DecodeLimits::DEPLOYMENT_STARTING_POINT;
331 let fixture = DecodeLimits::TEST_FIXTURE;
332
333 assert!(production.max_input_bytes > fixture.max_input_bytes);
334 assert!(production.max_total_allocation > fixture.max_total_allocation);
335 assert!(production.max_proof_nodes > fixture.max_proof_nodes);
336 assert!(production.max_total_items > fixture.max_total_items);
337 }
338
339 #[test]
340 fn accumulator_rejects_cumulative_allocation_over_budget() {
341 let limits = DecodeLimits {
342 max_total_allocation: 4,
343 ..DecodeLimits::TEST_FIXTURE
344 };
345 let mut accumulator = limits.accumulator();
346
347 assert_eq!(accumulator.check_allocation(3), Ok(()));
348 assert_eq!(accumulator.total_allocated(), 3);
349 assert_eq!(
350 accumulator.check_allocation(2),
351 Err(DecodeError::AllocationExceeded)
352 );
353 assert_eq!(accumulator.total_allocated(), 3);
354 }
355
356 #[test]
357 fn accumulator_rejects_allocation_counter_overflow() {
358 let limits = DecodeLimits {
359 max_total_allocation: usize::MAX,
360 ..DecodeLimits::TEST_FIXTURE
361 };
362 let mut accumulator = limits.accumulator();
363
364 assert_eq!(accumulator.check_allocation(usize::MAX), Ok(()));
365 assert_eq!(
366 accumulator.check_allocation(1),
367 Err(DecodeError::AllocationExceeded)
368 );
369 }
370
371 #[test]
372 fn accumulator_rejects_cumulative_items_over_budget() {
373 let limits = DecodeLimits {
374 max_total_items: 4,
375 ..DecodeLimits::TEST_FIXTURE
376 };
377 let mut accumulator = limits.accumulator();
378
379 assert_eq!(accumulator.account_items(3), Ok(()));
380 assert_eq!(accumulator.total_items(), 3);
381 assert_eq!(
382 accumulator.account_items(2),
383 Err(DecodeError::ItemCountExceeded)
384 );
385 assert_eq!(accumulator.total_items(), 3);
386 }
387
388 #[test]
389 fn accumulator_rejects_cumulative_proof_nodes_over_budget() {
390 let limits = DecodeLimits {
391 max_proof_nodes: 4,
392 ..DecodeLimits::TEST_FIXTURE
393 };
394 let mut accumulator = limits.accumulator();
395
396 assert_eq!(accumulator.account_proof_nodes(3), Ok(()));
397 assert_eq!(accumulator.proof_nodes(), 3);
398 assert_eq!(
399 accumulator.account_proof_nodes(2),
400 Err(DecodeError::ProofTooLarge)
401 );
402 assert_eq!(accumulator.proof_nodes(), 3);
403 }
404
405 #[test]
406 fn deployment_starting_point_must_be_reviewed_before_use() {
407 assert_eq!(
408 DecodeLimits::DEPLOYMENT_STARTING_POINT.validate_deployment_policy(),
409 Err(DecodeError::UnreviewedDeploymentPolicy)
410 );
411
412 let reviewed = DecodeLimits {
413 max_input_bytes: DecodeLimits::DEPLOYMENT_STARTING_POINT.max_input_bytes / 2,
414 ..DecodeLimits::DEPLOYMENT_STARTING_POINT
415 };
416 assert_eq!(reviewed.validate_deployment_policy(), Ok(()));
417 }
418
419 #[test]
420 fn reviewed_policy_requires_every_limit_explicitly() {
421 let reviewed = DecodeLimits::reviewed_policy(1024, 32, 8, 4096, 16, 128);
422
423 assert_eq!(reviewed.max_input_bytes, 1024);
424 assert_eq!(reviewed.max_list_items, 32);
425 assert_eq!(reviewed.max_nesting_depth, 8);
426 assert_eq!(reviewed.max_total_allocation, 4096);
427 assert_eq!(reviewed.max_proof_nodes, 16);
428 assert_eq!(reviewed.max_total_items, 128);
429 assert_eq!(reviewed.validate_deployment_policy(), Ok(()));
430 }
431}