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 pub fn check_input_len(self, len: usize) -> Result<(), DecodeError> {
59 if len > self.max_input_bytes {
60 return Err(DecodeError::InputTooLarge);
61 }
62 Ok(())
63 }
64
65 pub fn check_list_count(self, count: usize) -> Result<(), DecodeError> {
67 if count > self.max_list_items {
68 return Err(DecodeError::ListTooLong);
69 }
70 Ok(())
71 }
72
73 pub fn check_nesting_depth(self, depth: usize) -> Result<(), DecodeError> {
78 if depth > self.max_nesting_depth {
79 return Err(DecodeError::NestingTooDeep);
80 }
81 Ok(())
82 }
83
84 pub fn check_single_allocation_limit(self, size: usize) -> Result<(), DecodeError> {
90 if size > self.max_total_allocation {
91 return Err(DecodeError::AllocationExceeded);
92 }
93 Ok(())
94 }
95
96 pub fn check_proof_node_count(self, count: usize) -> Result<(), DecodeError> {
101 if count > self.max_proof_nodes {
102 return Err(DecodeError::ProofTooLarge);
103 }
104 Ok(())
105 }
106
107 pub fn check_item_count(self, count: usize) -> Result<(), DecodeError> {
112 if count > self.max_total_items {
113 return Err(DecodeError::ItemCountExceeded);
114 }
115 Ok(())
116 }
117
118 #[must_use]
120 pub const fn accumulator(self) -> DecodeAccumulator {
121 DecodeAccumulator {
122 limits: self,
123 total_allocated: 0,
124 total_items: 0,
125 proof_nodes: 0,
126 }
127 }
128
129 pub fn validate_deployment_policy(self) -> Result<(), DecodeError> {
137 if self == Self::DEPLOYMENT_STARTING_POINT {
138 return Err(DecodeError::UnreviewedDeploymentPolicy);
139 }
140 Ok(())
141 }
142}
143
144#[derive(Debug, Eq, PartialEq)]
146pub struct DecodeAccumulator {
147 limits: DecodeLimits,
148 total_allocated: usize,
149 total_items: usize,
150 proof_nodes: usize,
151}
152
153impl DecodeAccumulator {
154 #[must_use]
156 pub const fn limits(&self) -> DecodeLimits {
157 self.limits
158 }
159
160 #[must_use]
162 pub const fn total_allocated(&self) -> usize {
163 self.total_allocated
164 }
165
166 #[must_use]
168 pub const fn total_items(&self) -> usize {
169 self.total_items
170 }
171
172 #[must_use]
174 pub const fn proof_nodes(&self) -> usize {
175 self.proof_nodes
176 }
177
178 pub fn check_input_len(&self, len: usize) -> Result<(), DecodeError> {
180 self.limits.check_input_len(len)
181 }
182
183 pub fn check_list_count(&self, count: usize) -> Result<(), DecodeError> {
185 self.limits.check_list_count(count)
186 }
187
188 pub fn check_nesting_depth(&self, depth: usize) -> Result<(), DecodeError> {
190 self.limits.check_nesting_depth(depth)
191 }
192
193 pub fn check_allocation(&mut self, size: usize) -> Result<(), DecodeError> {
195 let new_total = self
196 .total_allocated
197 .checked_add(size)
198 .ok_or(DecodeError::AllocationExceeded)?;
199 if new_total > self.limits.max_total_allocation {
200 return Err(DecodeError::AllocationExceeded);
201 }
202 self.total_allocated = new_total;
203 Ok(())
204 }
205
206 pub fn account_items(&mut self, count: usize) -> Result<(), DecodeError> {
208 let new_total = self
209 .total_items
210 .checked_add(count)
211 .ok_or(DecodeError::ItemCountExceeded)?;
212 if new_total > self.limits.max_total_items {
213 return Err(DecodeError::ItemCountExceeded);
214 }
215 self.total_items = new_total;
216 Ok(())
217 }
218
219 pub fn account_proof_nodes(&mut self, count: usize) -> Result<(), DecodeError> {
221 let new_total = self
222 .proof_nodes
223 .checked_add(count)
224 .ok_or(DecodeError::ProofTooLarge)?;
225 if new_total > self.limits.max_proof_nodes {
226 return Err(DecodeError::ProofTooLarge);
227 }
228 self.proof_nodes = new_total;
229 Ok(())
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn rejects_oversized_input() {
239 let limits = DecodeLimits {
240 max_input_bytes: 2,
241 ..DecodeLimits::TEST_FIXTURE
242 };
243 assert_eq!(limits.check_input_len(3), Err(DecodeError::InputTooLarge));
244 }
245
246 #[test]
247 fn rejects_oversized_list() {
248 let limits = DecodeLimits {
249 max_list_items: 2,
250 ..DecodeLimits::TEST_FIXTURE
251 };
252 assert_eq!(limits.check_list_count(3), Err(DecodeError::ListTooLong));
253 }
254
255 #[test]
256 fn rejects_excessive_nesting_depth() {
257 let limits = DecodeLimits {
258 max_nesting_depth: 2,
259 ..DecodeLimits::TEST_FIXTURE
260 };
261 assert_eq!(
262 limits.check_nesting_depth(3),
263 Err(DecodeError::NestingTooDeep)
264 );
265 }
266
267 #[test]
268 fn rejects_excessive_allocation() {
269 let limits = DecodeLimits {
270 max_total_allocation: 2,
271 ..DecodeLimits::TEST_FIXTURE
272 };
273 assert_eq!(
274 limits.check_single_allocation_limit(3),
275 Err(DecodeError::AllocationExceeded)
276 );
277 }
278
279 #[test]
280 fn rejects_excessive_proof_nodes() {
281 let limits = DecodeLimits {
282 max_proof_nodes: 2,
283 ..DecodeLimits::TEST_FIXTURE
284 };
285 assert_eq!(
286 limits.check_proof_node_count(3),
287 Err(DecodeError::ProofTooLarge)
288 );
289 }
290
291 #[test]
292 fn rejects_excessive_total_items() {
293 let limits = DecodeLimits {
294 max_total_items: 2,
295 ..DecodeLimits::TEST_FIXTURE
296 };
297 assert_eq!(
298 limits.check_item_count(3),
299 Err(DecodeError::ItemCountExceeded)
300 );
301 }
302
303 #[test]
304 fn fixture_and_deployment_starting_point_limits_are_distinct() {
305 let production = DecodeLimits::DEPLOYMENT_STARTING_POINT;
306 let fixture = DecodeLimits::TEST_FIXTURE;
307
308 assert!(production.max_input_bytes > fixture.max_input_bytes);
309 assert!(production.max_total_allocation > fixture.max_total_allocation);
310 assert!(production.max_proof_nodes > fixture.max_proof_nodes);
311 assert!(production.max_total_items > fixture.max_total_items);
312 }
313
314 #[test]
315 fn accumulator_rejects_cumulative_allocation_over_budget() {
316 let limits = DecodeLimits {
317 max_total_allocation: 4,
318 ..DecodeLimits::TEST_FIXTURE
319 };
320 let mut accumulator = limits.accumulator();
321
322 assert_eq!(accumulator.check_allocation(3), Ok(()));
323 assert_eq!(accumulator.total_allocated(), 3);
324 assert_eq!(
325 accumulator.check_allocation(2),
326 Err(DecodeError::AllocationExceeded)
327 );
328 assert_eq!(accumulator.total_allocated(), 3);
329 }
330
331 #[test]
332 fn accumulator_rejects_allocation_counter_overflow() {
333 let limits = DecodeLimits {
334 max_total_allocation: usize::MAX,
335 ..DecodeLimits::TEST_FIXTURE
336 };
337 let mut accumulator = limits.accumulator();
338
339 assert_eq!(accumulator.check_allocation(usize::MAX), Ok(()));
340 assert_eq!(
341 accumulator.check_allocation(1),
342 Err(DecodeError::AllocationExceeded)
343 );
344 }
345
346 #[test]
347 fn accumulator_rejects_cumulative_items_over_budget() {
348 let limits = DecodeLimits {
349 max_total_items: 4,
350 ..DecodeLimits::TEST_FIXTURE
351 };
352 let mut accumulator = limits.accumulator();
353
354 assert_eq!(accumulator.account_items(3), Ok(()));
355 assert_eq!(accumulator.total_items(), 3);
356 assert_eq!(
357 accumulator.account_items(2),
358 Err(DecodeError::ItemCountExceeded)
359 );
360 assert_eq!(accumulator.total_items(), 3);
361 }
362
363 #[test]
364 fn accumulator_rejects_cumulative_proof_nodes_over_budget() {
365 let limits = DecodeLimits {
366 max_proof_nodes: 4,
367 ..DecodeLimits::TEST_FIXTURE
368 };
369 let mut accumulator = limits.accumulator();
370
371 assert_eq!(accumulator.account_proof_nodes(3), Ok(()));
372 assert_eq!(accumulator.proof_nodes(), 3);
373 assert_eq!(
374 accumulator.account_proof_nodes(2),
375 Err(DecodeError::ProofTooLarge)
376 );
377 assert_eq!(accumulator.proof_nodes(), 3);
378 }
379
380 #[test]
381 fn deployment_starting_point_must_be_reviewed_before_use() {
382 assert_eq!(
383 DecodeLimits::DEPLOYMENT_STARTING_POINT.validate_deployment_policy(),
384 Err(DecodeError::UnreviewedDeploymentPolicy)
385 );
386
387 let reviewed = DecodeLimits {
388 max_input_bytes: DecodeLimits::DEPLOYMENT_STARTING_POINT.max_input_bytes / 2,
389 ..DecodeLimits::DEPLOYMENT_STARTING_POINT
390 };
391 assert_eq!(reviewed.validate_deployment_policy(), Ok(()));
392 }
393}