1use core::ops::AddAssign;
2
3use crate::internal_prelude::*;
4
5#[derive(Debug, Clone, PartialEq, Eq, Default, ManifestSbor, ScryptoSbor)]
9#[sbor(transparent)]
10pub struct ManifestResourceConstraints {
11 specified_resources: IndexMap<ResourceAddress, ManifestResourceConstraint>,
12}
13
14impl ManifestResourceConstraints {
15 pub fn new() -> Self {
16 Default::default()
17 }
18
19 pub fn with(
23 self,
24 resource_address: ResourceAddress,
25 constraint: ManifestResourceConstraint,
26 ) -> Self {
27 if !constraint.is_valid_for(&resource_address) {
28 panic!("Constraint isn't valid for the resource address");
29 }
30 self.with_unchecked(resource_address, constraint)
31 }
32
33 pub fn with_unchecked(
38 mut self,
39 resource_address: ResourceAddress,
40 constraint: ManifestResourceConstraint,
41 ) -> Self {
42 let replaced = self
43 .specified_resources
44 .insert(resource_address, constraint);
45 if replaced.is_some() {
46 panic!("A constraint has already been specified against the resource");
47 }
48 self
49 }
50
51 pub fn with_exact_amount(
55 self,
56 resource_address: ResourceAddress,
57 amount: impl Resolve<Decimal>,
58 ) -> Self {
59 self.with(
60 resource_address,
61 ManifestResourceConstraint::ExactAmount(amount.resolve()),
62 )
63 }
64
65 pub fn with_at_least_amount(
69 self,
70 resource_address: ResourceAddress,
71 amount: impl Resolve<Decimal>,
72 ) -> Self {
73 self.with(
74 resource_address,
75 ManifestResourceConstraint::AtLeastAmount(amount.resolve()),
76 )
77 }
78
79 pub fn with_amount_range(
83 self,
84 resource_address: ResourceAddress,
85 lower_bound: impl Resolve<LowerBound>,
86 upper_bound: impl Resolve<UpperBound>,
87 ) -> Self {
88 self.with_general_constraint(
89 resource_address,
90 GeneralResourceConstraint {
91 required_ids: Default::default(),
92 lower_bound: lower_bound.resolve(),
93 upper_bound: upper_bound.resolve(),
94 allowed_ids: AllowedIds::Any,
95 },
96 )
97 }
98
99 pub fn with_exact_non_fungibles(
103 self,
104 resource_address: ResourceAddress,
105 non_fungible_ids: impl IntoIterator<Item = NonFungibleLocalId>,
106 ) -> Self {
107 self.with(
108 resource_address,
109 ManifestResourceConstraint::ExactNonFungibles(non_fungible_ids.into_iter().collect()),
110 )
111 }
112
113 pub fn with_at_least_non_fungibles(
117 self,
118 resource_address: ResourceAddress,
119 non_fungible_ids: impl IntoIterator<Item = NonFungibleLocalId>,
120 ) -> Self {
121 self.with(
122 resource_address,
123 ManifestResourceConstraint::AtLeastNonFungibles(non_fungible_ids.into_iter().collect()),
124 )
125 }
126
127 pub fn with_general_constraint(
131 self,
132 resource_address: ResourceAddress,
133 bounds: GeneralResourceConstraint,
134 ) -> Self {
135 self.with(
136 resource_address,
137 ManifestResourceConstraint::General(bounds),
138 )
139 }
140
141 pub fn specified_resources(&self) -> &IndexMap<ResourceAddress, ManifestResourceConstraint> {
142 &self.specified_resources
143 }
144
145 pub fn iter(&self) -> impl Iterator<Item = (&ResourceAddress, &ManifestResourceConstraint)> {
146 self.specified_resources.iter()
147 }
148
149 #[allow(clippy::len_without_is_empty)]
150 pub fn len(&self) -> usize {
151 self.specified_resources().len()
152 }
153
154 pub fn is_valid(&self) -> bool {
155 for (resource_address, constraint) in self.iter() {
156 if !constraint.is_valid_for(resource_address) {
157 return false;
158 }
159 }
160 true
161 }
162
163 pub fn contains_specified_resource(&self, resource_address: &ResourceAddress) -> bool {
164 self.specified_resources.contains_key(resource_address)
165 }
166
167 pub fn validate(
168 self,
169 balances: AggregateResourceBalances,
170 prevent_unspecified_resource_balances: bool,
171 ) -> Result<(), ResourceConstraintsError> {
172 let AggregateResourceBalances {
173 fungible_resources,
174 non_fungible_resources,
175 } = balances;
176
177 if prevent_unspecified_resource_balances {
178 for (resource_address, amount) in fungible_resources.iter() {
179 if !self.specified_resources.contains_key(resource_address) && amount.is_positive()
180 {
181 return Err(
182 ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource {
183 resource_address: *resource_address,
184 },
185 );
186 }
187 }
188
189 for (resource_address, ids) in non_fungible_resources.iter() {
190 if !self.specified_resources.contains_key(resource_address) && !ids.is_empty() {
191 return Err(
192 ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource {
193 resource_address: *resource_address,
194 },
195 );
196 }
197 }
198 }
199
200 let zero_balance = Decimal::ZERO;
201 let empty_ids: IndexSet<NonFungibleLocalId> = Default::default();
202 for (resource_address, constraint) in self.specified_resources {
203 if resource_address.is_fungible() {
204 let amount = fungible_resources
205 .get(&resource_address)
206 .unwrap_or(&zero_balance);
207 constraint.validate_fungible(*amount).map_err(|error| {
208 ResourceConstraintsError::ResourceConstraintFailed {
209 resource_address,
210 error,
211 }
212 })?;
213 } else {
214 let ids = non_fungible_resources
215 .get(&resource_address)
216 .unwrap_or(&empty_ids);
217 constraint.validate_non_fungible(ids).map_err(|error| {
218 ResourceConstraintsError::ResourceConstraintFailed {
219 resource_address,
220 error,
221 }
222 })?;
223 }
224 }
225
226 Ok(())
227 }
228}
229
230pub struct AggregateResourceBalances {
231 fungible_resources: IndexMap<ResourceAddress, Decimal>,
232 non_fungible_resources: IndexMap<ResourceAddress, IndexSet<NonFungibleLocalId>>,
233}
234
235impl Default for AggregateResourceBalances {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241impl AggregateResourceBalances {
242 pub fn new() -> Self {
243 Self {
244 fungible_resources: Default::default(),
245 non_fungible_resources: Default::default(),
246 }
247 }
248
249 pub fn add_fungible(&mut self, resource_address: ResourceAddress, amount: Decimal) {
250 if amount.is_positive() {
251 self.fungible_resources
252 .entry(resource_address)
253 .or_default()
254 .add_assign(amount);
255 }
256 }
257
258 pub fn add_non_fungible(
259 &mut self,
260 resource_address: ResourceAddress,
261 ids: IndexSet<NonFungibleLocalId>,
262 ) {
263 if !ids.is_empty() {
264 self.non_fungible_resources
265 .entry(resource_address)
266 .or_default()
267 .extend(ids);
268 }
269 }
270
271 pub fn validate_only(
272 self,
273 constraints: ManifestResourceConstraints,
274 ) -> Result<(), ResourceConstraintsError> {
275 constraints.validate(self, true)
276 }
277
278 pub fn validate_includes(
279 self,
280 constraints: ManifestResourceConstraints,
281 ) -> Result<(), ResourceConstraintsError> {
282 constraints.validate(self, false)
283 }
284}
285
286impl IntoIterator for ManifestResourceConstraints {
287 type Item = (ResourceAddress, ManifestResourceConstraint);
288 type IntoIter =
289 <IndexMap<ResourceAddress, ManifestResourceConstraint> as IntoIterator>::IntoIter;
290
291 fn into_iter(self) -> Self::IntoIter {
292 self.specified_resources.into_iter()
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, Eq, ManifestSbor, ScryptoSbor)]
297pub enum ManifestResourceConstraint {
298 NonZeroAmount,
299 ExactAmount(Decimal),
300 AtLeastAmount(Decimal),
301 ExactNonFungibles(IndexSet<NonFungibleLocalId>),
302 AtLeastNonFungibles(IndexSet<NonFungibleLocalId>),
303 General(GeneralResourceConstraint),
304}
305
306impl ManifestResourceConstraint {
307 pub fn is_valid_for(&self, resource_address: &ResourceAddress) -> bool {
308 if resource_address.is_fungible() {
309 self.is_valid_for_fungible_use()
310 } else {
311 self.is_valid_for_non_fungible_use()
312 }
313 }
314
315 pub fn is_valid_for_fungible_use(&self) -> bool {
316 match self {
317 ManifestResourceConstraint::NonZeroAmount => true,
318 ManifestResourceConstraint::ExactAmount(amount) => !amount.is_negative(),
319 ManifestResourceConstraint::AtLeastAmount(amount) => !amount.is_negative(),
320 ManifestResourceConstraint::ExactNonFungibles(_) => false,
321 ManifestResourceConstraint::AtLeastNonFungibles(_) => false,
322 ManifestResourceConstraint::General(general) => general.is_valid_for_fungible_use(),
323 }
324 }
325
326 pub fn is_valid_for_non_fungible_use(&self) -> bool {
327 match self {
328 ManifestResourceConstraint::NonZeroAmount => true,
329 ManifestResourceConstraint::ExactAmount(amount) => {
330 !amount.is_negative() && amount.checked_floor() == Some(*amount)
331 }
332 ManifestResourceConstraint::AtLeastAmount(amount) => {
333 !amount.is_negative() && amount.checked_floor() == Some(*amount)
334 }
335 ManifestResourceConstraint::ExactNonFungibles(_) => true,
336 ManifestResourceConstraint::AtLeastNonFungibles(_) => true,
337 ManifestResourceConstraint::General(general) => general.is_valid_for_non_fungible_use(),
338 }
339 }
340
341 pub fn validate_non_fungible(
342 self,
343 ids: &IndexSet<NonFungibleLocalId>,
344 ) -> Result<(), ResourceConstraintError> {
345 let amount = Decimal::from(ids.len());
346 match self {
347 ManifestResourceConstraint::NonZeroAmount => {
348 if ids.is_empty() {
349 return Err(ResourceConstraintError::ExpectedNonZeroAmount);
350 }
351 }
352 ManifestResourceConstraint::ExactAmount(expected_exact_amount) => {
353 if amount.ne(&expected_exact_amount) {
354 return Err(ResourceConstraintError::ExpectedExactAmount {
355 actual_amount: amount,
356 expected_amount: expected_exact_amount,
357 });
358 }
359 }
360 ManifestResourceConstraint::AtLeastAmount(expected_at_least_amount) => {
361 if amount < expected_at_least_amount {
362 return Err(ResourceConstraintError::ExpectedAtLeastAmount {
363 expected_at_least_amount,
364 actual_amount: amount,
365 });
366 }
367 }
368 ManifestResourceConstraint::ExactNonFungibles(expected_exact_ids) => {
369 if let Some(missing_id) = expected_exact_ids.difference(ids).next() {
370 return Err(ResourceConstraintError::NonFungibleMissing {
371 missing_id: missing_id.clone(),
372 });
373 }
374 if let Some(disallowed_id) = ids.difference(&expected_exact_ids).next() {
375 return Err(ResourceConstraintError::NonFungibleNotAllowed {
376 disallowed_id: disallowed_id.clone(),
377 });
378 }
379 }
380 ManifestResourceConstraint::AtLeastNonFungibles(expected_at_least_ids) => {
381 if let Some(missing_id) = expected_at_least_ids.difference(ids).next() {
382 return Err(ResourceConstraintError::NonFungibleMissing {
383 missing_id: missing_id.clone(),
384 });
385 }
386 }
387 ManifestResourceConstraint::General(constraint) => {
388 constraint.validate_non_fungible_ids(ids)?;
389 }
390 }
391
392 Ok(())
393 }
394
395 pub fn validate_fungible(self, amount: Decimal) -> Result<(), ResourceConstraintError> {
396 match self {
397 ManifestResourceConstraint::NonZeroAmount => {
398 if amount.is_zero() {
399 return Err(ResourceConstraintError::ExpectedNonZeroAmount);
400 }
401 }
402 ManifestResourceConstraint::ExactAmount(expected_exact_amount) => {
403 if amount.ne(&expected_exact_amount) {
404 return Err(ResourceConstraintError::ExpectedExactAmount {
405 actual_amount: amount,
406 expected_amount: expected_exact_amount,
407 });
408 }
409 }
410 ManifestResourceConstraint::AtLeastAmount(expected_at_least_amount) => {
411 if amount < expected_at_least_amount {
412 return Err(ResourceConstraintError::ExpectedAtLeastAmount {
413 expected_at_least_amount,
414 actual_amount: amount,
415 });
416 }
417 }
418 ManifestResourceConstraint::ExactNonFungibles(..) => {
419 return Err(
420 ResourceConstraintError::NonFungibleConstraintNotValidForFungibleResource,
421 );
422 }
423 ManifestResourceConstraint::AtLeastNonFungibles(..) => {
424 return Err(
425 ResourceConstraintError::NonFungibleConstraintNotValidForFungibleResource,
426 );
427 }
428 ManifestResourceConstraint::General(constraint) => {
429 constraint.validate_fungible(amount)?;
430 }
431 }
432
433 Ok(())
434 }
435}
436
437#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
438pub enum ResourceConstraintsError {
439 UnexpectedNonZeroBalanceOfUnspecifiedResource {
440 resource_address: ResourceAddress,
441 },
442 ResourceConstraintFailed {
443 resource_address: ResourceAddress,
444 error: ResourceConstraintError,
445 },
446}
447
448#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
449pub enum ResourceConstraintError {
450 NonFungibleConstraintNotValidForFungibleResource,
451 ExpectedNonZeroAmount,
452 ExpectedExactAmount {
453 expected_amount: Decimal,
454 actual_amount: Decimal,
455 },
456 ExpectedAtLeastAmount {
457 expected_at_least_amount: Decimal,
458 actual_amount: Decimal,
459 },
460 ExpectedAtMostAmount {
461 expected_at_most_amount: Decimal,
462 actual_amount: Decimal,
463 },
464 NonFungibleMissing {
469 missing_id: NonFungibleLocalId,
470 },
471 NonFungibleNotAllowed {
472 disallowed_id: NonFungibleLocalId,
473 },
474}
475
476#[derive(Debug, Clone, PartialEq, Eq, ManifestSbor, ScryptoSbor)]
534pub struct GeneralResourceConstraint {
535 pub required_ids: IndexSet<NonFungibleLocalId>,
536 pub lower_bound: LowerBound,
537 pub upper_bound: UpperBound,
538 pub allowed_ids: AllowedIds,
539}
540
541impl GeneralResourceConstraint {
542 pub fn fungible(
543 lower_bound: impl Resolve<LowerBound>,
544 upper_bound: impl Resolve<UpperBound>,
545 ) -> Self {
546 let constraint = Self {
547 required_ids: Default::default(),
548 lower_bound: lower_bound.resolve(),
549 upper_bound: upper_bound.resolve(),
550 allowed_ids: AllowedIds::Any,
551 };
552
553 if !constraint.is_valid_for_fungible_use() {
554 panic!("Bounds are invalid for fungible use");
555 }
556
557 constraint
558 }
559
560 pub fn non_fungible_no_allow_list(
561 required_ids: impl IntoIterator<Item = NonFungibleLocalId>,
562 lower_bound: impl Resolve<LowerBound>,
563 upper_bound: impl Resolve<UpperBound>,
564 ) -> Self {
565 let constraint = Self {
566 required_ids: required_ids.into_iter().collect(),
567 lower_bound: lower_bound.resolve(),
568 upper_bound: upper_bound.resolve(),
569 allowed_ids: AllowedIds::Any,
570 };
571
572 if !constraint.is_valid_for_non_fungible_use() {
573 panic!("Bounds are invalid for non-fungible use");
574 }
575
576 constraint
577 }
578
579 pub fn non_fungible_with_allow_list(
580 required_ids: impl IntoIterator<Item = NonFungibleLocalId>,
581 lower_bound: impl Resolve<LowerBound>,
582 upper_bound: impl Resolve<UpperBound>,
583 allowed_ids: impl IntoIterator<Item = NonFungibleLocalId>,
584 ) -> Self {
585 let constraint = Self {
586 required_ids: required_ids.into_iter().collect(),
587 lower_bound: lower_bound.resolve(),
588 upper_bound: upper_bound.resolve(),
589 allowed_ids: AllowedIds::allowlist(allowed_ids),
590 };
591
592 if !constraint.is_valid_for_non_fungible_use() {
593 panic!("Bounds are invalid for non-fungible use");
594 }
595
596 constraint
597 }
598
599 pub fn is_valid_for_fungible_use(&self) -> bool {
600 self.required_ids.is_empty()
601 && self.lower_bound.is_valid_for_fungible_use()
602 && self.upper_bound.is_valid_for_fungible_use()
603 && self.allowed_ids.is_valid_for_fungible_use()
604 && self.is_valid_independent_of_resource_type()
605 }
606
607 pub fn is_valid_for_non_fungible_use(&self) -> bool {
608 self.lower_bound.is_valid_for_non_fungible_use()
609 && self.upper_bound.is_valid_for_non_fungible_use()
610 && self.is_valid_independent_of_resource_type()
611 }
612
613 pub fn validate_fungible(&self, amount: Decimal) -> Result<(), ResourceConstraintError> {
614 self.validate_amount(amount)?;
615 Ok(())
617 }
618
619 pub fn validate_non_fungible_ids(
620 &self,
621 ids: &IndexSet<NonFungibleLocalId>,
622 ) -> Result<(), ResourceConstraintError> {
623 self.validate_amount(Decimal::from(ids.len()))?;
624
625 if let Some(missing_id) = self.required_ids.difference(ids).next() {
626 return Err(ResourceConstraintError::NonFungibleMissing {
627 missing_id: missing_id.clone(),
628 });
629 }
630
631 self.allowed_ids.validate_ids(ids)?;
632
633 Ok(())
634 }
635
636 fn validate_amount(&self, amount: Decimal) -> Result<(), ResourceConstraintError> {
637 self.lower_bound.validate_amount(&amount)?;
638 self.upper_bound.validate_amount(&amount)?;
639 Ok(())
640 }
641
642 pub fn is_valid_independent_of_resource_type(&self) -> bool {
643 if self.lower_bound.equivalent_decimal() > self.upper_bound.equivalent_decimal() {
645 return false;
646 }
647
648 let required_ids_amount = Decimal::from(self.required_ids.len());
649
650 if required_ids_amount > self.upper_bound.equivalent_decimal() {
652 return false;
653 }
654
655 match &self.allowed_ids {
656 AllowedIds::Allowlist(allowlist) => {
657 let allowlist_ids_amount = Decimal::from(allowlist.len());
658
659 if self.lower_bound.equivalent_decimal() > allowlist_ids_amount {
661 return false;
662 }
663
664 if !self.required_ids.is_subset(allowlist) {
666 return false;
667 }
668 }
669 AllowedIds::Any => {}
670 }
671
672 true
673 }
674
675 pub fn normalize(&mut self) {
679 let required_ids_len = Decimal::from(self.required_ids.len());
680
681 if self.lower_bound.equivalent_decimal() < required_ids_len {
683 self.lower_bound = LowerBound::Inclusive(required_ids_len);
684 }
685 if let AllowedIds::Allowlist(allowlist) = &self.allowed_ids {
686 let allowlist_len = Decimal::from(allowlist.len());
687 if allowlist_len < self.upper_bound.equivalent_decimal() {
688 self.upper_bound = UpperBound::Inclusive(allowlist_len);
689 }
690 }
691
692 if self.allowed_ids.allowlist_equivalent_length() > self.required_ids.len() {
695 if required_ids_len == self.upper_bound.equivalent_decimal() {
696 self.allowed_ids = AllowedIds::Allowlist(self.required_ids.clone());
699 } else if let AllowedIds::Allowlist(allowlist) = &self.allowed_ids {
700 if Decimal::from(allowlist.len()) == self.lower_bound.equivalent_decimal() {
701 self.required_ids = allowlist.clone();
702 }
703 }
704 }
705 }
706}
707
708#[derive(Debug, Clone, Copy, PartialEq, Eq, ManifestSbor, ScryptoSbor)]
724pub enum LowerBound {
725 NonZero,
726 Inclusive(Decimal),
727}
728
729impl PartialOrd for LowerBound {
730 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
731 Some(self.cmp(other))
732 }
733}
734
735impl Ord for LowerBound {
736 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
737 match (self, other) {
738 (
739 LowerBound::Inclusive(self_lower_inclusive),
740 LowerBound::Inclusive(other_lower_inclusive),
741 ) => self_lower_inclusive.cmp(other_lower_inclusive),
742 (LowerBound::Inclusive(self_lower_inclusive), LowerBound::NonZero) => {
743 if self_lower_inclusive.is_positive() {
744 core::cmp::Ordering::Greater
745 } else {
746 core::cmp::Ordering::Less
747 }
748 }
749 (LowerBound::NonZero, LowerBound::Inclusive(other_lower_inclusive)) => {
750 if other_lower_inclusive.is_positive() {
751 core::cmp::Ordering::Less
752 } else {
753 core::cmp::Ordering::Greater
754 }
755 }
756 (LowerBound::NonZero, LowerBound::NonZero) => core::cmp::Ordering::Equal,
757 }
758 }
759}
760
761impl LowerBound {
762 pub const fn zero() -> Self {
763 Self::Inclusive(Decimal::ZERO)
764 }
765
766 pub const fn non_zero() -> Self {
767 Self::NonZero
768 }
769
770 pub fn at_least(decimal: Decimal) -> Self {
773 if decimal.is_negative() {
774 panic!("An at_least bound is negative");
775 }
776 Self::Inclusive(decimal)
777 }
778
779 pub fn of(lower_bound: impl Resolve<LowerBound>) -> Self {
780 lower_bound.resolve()
781 }
782
783 pub fn validate_amount(&self, amount: &Decimal) -> Result<(), ResourceConstraintError> {
784 match self {
785 LowerBound::NonZero => {
786 if amount.is_zero() {
787 return Err(ResourceConstraintError::ExpectedNonZeroAmount);
788 }
789 }
790 LowerBound::Inclusive(inclusive) => {
791 if amount < inclusive {
792 return Err(ResourceConstraintError::ExpectedAtLeastAmount {
793 expected_at_least_amount: *inclusive,
794 actual_amount: *amount,
795 });
796 }
797 }
798 }
799 Ok(())
800 }
801
802 pub fn is_valid_for_fungible_use(&self) -> bool {
803 match self {
804 LowerBound::NonZero => true,
805 LowerBound::Inclusive(amount) => !amount.is_negative(),
806 }
807 }
808
809 pub fn is_valid_for_non_fungible_use(&self) -> bool {
810 match self {
811 LowerBound::NonZero => true,
812 LowerBound::Inclusive(amount) => {
813 !amount.is_negative() && amount.checked_floor() == Some(*amount)
814 }
815 }
816 }
817
818 pub fn cmp_upper(&self, other: &UpperBound) -> core::cmp::Ordering {
819 match (self, other) {
820 (
821 LowerBound::Inclusive(lower_bound_inclusive),
822 UpperBound::Inclusive(upper_bound_inclusive),
823 ) => lower_bound_inclusive.cmp(upper_bound_inclusive),
824 (_, UpperBound::Unbounded) => core::cmp::Ordering::Less,
825 (LowerBound::NonZero, UpperBound::Inclusive(upper_bound_inclusive)) => {
826 if upper_bound_inclusive.is_zero() {
827 core::cmp::Ordering::Greater
828 } else {
829 core::cmp::Ordering::Less
830 }
831 }
832 }
833 }
834
835 pub fn is_zero(&self) -> bool {
836 self.eq(&Self::zero())
837 }
838
839 pub fn is_positive(&self) -> bool {
840 !self.is_zero()
841 }
842
843 pub fn add_from(&mut self, other: Self) -> Result<(), BoundAdjustmentError> {
844 let new_bound = match (*self, other) {
845 (LowerBound::Inclusive(self_lower_bound), LowerBound::Inclusive(other_lower_bound)) => {
846 let lower_bound_inclusive = self_lower_bound
847 .checked_add(other_lower_bound)
848 .ok_or(BoundAdjustmentError::DecimalOverflow)?;
849 LowerBound::Inclusive(lower_bound_inclusive)
850 }
851 (LowerBound::Inclusive(amount), LowerBound::NonZero)
852 | (LowerBound::NonZero, LowerBound::Inclusive(amount)) => {
853 if amount.is_zero() {
854 LowerBound::NonZero
855 } else {
856 LowerBound::Inclusive(amount)
857 }
858 }
859 (LowerBound::NonZero, LowerBound::NonZero) => LowerBound::NonZero,
860 };
861
862 *self = new_bound;
863 Ok(())
864 }
865
866 pub fn take_amount(&mut self, take_amount: Decimal) {
868 let new_bound = match *self {
869 LowerBound::Inclusive(lower_bound_inclusive) => {
870 if take_amount > lower_bound_inclusive {
871 Self::zero()
872 } else {
873 LowerBound::Inclusive(lower_bound_inclusive - take_amount)
874 }
875 }
876 LowerBound::NonZero => {
877 if take_amount.is_zero() {
878 LowerBound::NonZero
879 } else {
880 Self::zero()
881 }
882 }
883 };
884
885 *self = new_bound;
886 }
887
888 pub fn constrain_to(&mut self, other_bound: LowerBound) {
889 let new_bound = (*self).max(other_bound);
890 *self = new_bound;
891 }
892
893 pub fn equivalent_decimal(&self) -> Decimal {
894 match self {
895 LowerBound::Inclusive(decimal) => *decimal,
896 LowerBound::NonZero => Decimal::from_attos(I192::ONE),
897 }
898 }
899
900 pub fn is_satisfied_by(&self, amount: Decimal) -> bool {
901 match self {
902 LowerBound::NonZero => amount.is_positive(),
903 LowerBound::Inclusive(inclusive_lower_bound) => *inclusive_lower_bound <= amount,
904 }
905 }
906}
907
908#[derive(Debug, Clone, Copy, PartialEq, Eq, ManifestSbor, ScryptoSbor)]
923pub enum UpperBound {
924 Inclusive(Decimal),
925 Unbounded,
926}
927
928impl PartialOrd for UpperBound {
929 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
930 Some(self.cmp(other))
931 }
932}
933
934impl Ord for UpperBound {
935 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
936 match (self, other) {
937 (
938 UpperBound::Inclusive(upper_bound_inclusive),
939 UpperBound::Inclusive(other_upper_bound_inclusive),
940 ) => upper_bound_inclusive.cmp(other_upper_bound_inclusive),
941 (UpperBound::Inclusive(_), UpperBound::Unbounded) => core::cmp::Ordering::Less,
942 (UpperBound::Unbounded, UpperBound::Inclusive(_)) => core::cmp::Ordering::Greater,
943 (UpperBound::Unbounded, UpperBound::Unbounded) => core::cmp::Ordering::Equal,
944 }
945 }
946}
947
948impl UpperBound {
949 pub const fn unbounded() -> Self {
950 Self::Unbounded
951 }
952
953 pub const fn zero() -> Self {
954 Self::Inclusive(Decimal::ZERO)
955 }
956
957 pub fn at_most(decimal: Decimal) -> Self {
960 if decimal.is_negative() {
961 panic!("An at_most bound is negative");
962 }
963 Self::Inclusive(decimal)
964 }
965
966 pub fn of(upper_bound: impl Resolve<UpperBound>) -> Self {
967 upper_bound.resolve()
968 }
969
970 pub fn validate_amount(&self, amount: &Decimal) -> Result<(), ResourceConstraintError> {
971 match self {
972 UpperBound::Inclusive(inclusive) => {
973 if amount > inclusive {
974 return Err(ResourceConstraintError::ExpectedAtMostAmount {
975 expected_at_most_amount: *inclusive,
976 actual_amount: *amount,
977 });
978 }
979 }
980 UpperBound::Unbounded => {}
981 }
982 Ok(())
983 }
984
985 pub fn is_valid_for_fungible_use(&self) -> bool {
986 match self {
987 UpperBound::Inclusive(amount) => !amount.is_negative(),
988 UpperBound::Unbounded => true,
989 }
990 }
991
992 pub fn is_valid_for_non_fungible_use(&self) -> bool {
993 match self {
994 UpperBound::Inclusive(amount) => {
995 !amount.is_negative() && amount.checked_floor() == Some(*amount)
996 }
997 UpperBound::Unbounded => true,
998 }
999 }
1000
1001 pub fn add_from(&mut self, other: Self) -> Result<(), BoundAdjustmentError> {
1002 let new_bound = match (*self, other) {
1003 (
1004 UpperBound::Inclusive(self_upper_bound_inclusive),
1005 UpperBound::Inclusive(other_upper_bound_inclusive),
1006 ) => {
1007 let upper_bound_inclusive = self_upper_bound_inclusive
1008 .checked_add(other_upper_bound_inclusive)
1009 .ok_or(BoundAdjustmentError::DecimalOverflow)?;
1010 UpperBound::Inclusive(upper_bound_inclusive)
1011 }
1012 (_, UpperBound::Unbounded) | (UpperBound::Unbounded, _) => UpperBound::Unbounded,
1013 };
1014
1015 *self = new_bound;
1016 Ok(())
1017 }
1018
1019 pub fn take_amount(&mut self, take_amount: Decimal) -> Result<(), BoundAdjustmentError> {
1021 let new_bound = match *self {
1022 UpperBound::Inclusive(upper_bound_inclusive) => {
1023 if take_amount > upper_bound_inclusive {
1024 return Err(BoundAdjustmentError::TakeCannotBeSatisfied);
1025 }
1026 UpperBound::Inclusive(upper_bound_inclusive - take_amount)
1027 }
1028 UpperBound::Unbounded => UpperBound::Unbounded,
1029 };
1030
1031 *self = new_bound;
1032
1033 Ok(())
1034 }
1035
1036 pub fn constrain_to(&mut self, other_bound: UpperBound) {
1037 let new_bound = (*self).min(other_bound);
1038 *self = new_bound;
1039 }
1040
1041 pub fn equivalent_decimal(&self) -> Decimal {
1042 match self {
1043 UpperBound::Inclusive(decimal) => *decimal,
1044 UpperBound::Unbounded => Decimal::MAX,
1045 }
1046 }
1047}
1048
1049#[derive(Debug, Clone, PartialEq, Eq, ManifestSbor, ScryptoSbor)]
1058pub enum AllowedIds {
1059 Allowlist(IndexSet<NonFungibleLocalId>),
1060 Any,
1061}
1062
1063impl AllowedIds {
1064 pub fn none() -> Self {
1065 Self::Allowlist(Default::default())
1066 }
1067
1068 pub fn allowlist(allowlist: impl IntoIterator<Item = NonFungibleLocalId>) -> Self {
1069 Self::Allowlist(allowlist.into_iter().collect())
1070 }
1071
1072 pub fn any() -> Self {
1073 Self::Any
1074 }
1075
1076 pub fn validate_ids(
1077 &self,
1078 ids: &IndexSet<NonFungibleLocalId>,
1079 ) -> Result<(), ResourceConstraintError> {
1080 match self {
1081 AllowedIds::Allowlist(allowed) => {
1082 for id in ids {
1083 if !allowed.contains(id) {
1084 return Err(ResourceConstraintError::NonFungibleNotAllowed {
1085 disallowed_id: id.clone(),
1086 });
1087 }
1088 }
1089 }
1090 AllowedIds::Any => {}
1091 }
1092 Ok(())
1093 }
1094
1095 pub fn allowlist_equivalent_length(&self) -> usize {
1096 match self {
1097 Self::Allowlist(allowlist) => allowlist.len(),
1098 Self::Any => usize::MAX,
1099 }
1100 }
1101
1102 pub fn is_valid_for_fungible_use(&self) -> bool {
1103 match self {
1104 AllowedIds::Allowlist(allowlist) => allowlist.is_empty(),
1105 AllowedIds::Any => true,
1106 }
1107 }
1108
1109 pub fn is_allow_list_and(
1110 &self,
1111 callback: impl FnOnce(&IndexSet<NonFungibleLocalId>) -> bool,
1112 ) -> bool {
1113 match self {
1114 AllowedIds::Allowlist(index_set) => callback(index_set),
1115 AllowedIds::Any => false,
1116 }
1117 }
1118}
1119
1120pub enum BoundAdjustmentError {
1121 DecimalOverflow,
1122 TakeCannotBeSatisfied,
1123}
1124
1125resolvable_with_identity_impl!(LowerBound);
1126
1127impl<T: Resolve<Decimal>> ResolveFrom<T> for LowerBound {
1128 fn resolve_from(value: T) -> Self {
1129 LowerBound::Inclusive(value.resolve())
1130 }
1131}
1132
1133resolvable_with_identity_impl!(UpperBound);
1134
1135impl<T: Resolve<Decimal>> ResolveFrom<T> for UpperBound {
1136 fn resolve_from(value: T) -> Self {
1137 UpperBound::Inclusive(value.resolve())
1138 }
1139}