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