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