use core::ops::AddAssign;
use crate::internal_prelude::*;
#[derive(Debug, Clone, PartialEq, Eq, Default, ManifestSbor, ScryptoSbor)]
#[sbor(transparent)]
pub struct ManifestResourceConstraints {
specified_resources: IndexMap<ResourceAddress, ManifestResourceConstraint>,
}
impl ManifestResourceConstraints {
pub fn new() -> Self {
Default::default()
}
pub fn with(
self,
resource_address: ResourceAddress,
constraint: ManifestResourceConstraint,
) -> Self {
if !constraint.is_valid_for(&resource_address) {
panic!("Constraint isn't valid for the resource address");
}
self.with_unchecked(resource_address, constraint)
}
pub fn with_unchecked(
mut self,
resource_address: ResourceAddress,
constraint: ManifestResourceConstraint,
) -> Self {
let replaced = self
.specified_resources
.insert(resource_address, constraint);
if replaced.is_some() {
panic!("A constraint has already been specified against the resource");
}
self
}
pub fn with_exact_amount(
self,
resource_address: ResourceAddress,
amount: impl Resolve<Decimal>,
) -> Self {
self.with(
resource_address,
ManifestResourceConstraint::ExactAmount(amount.resolve()),
)
}
pub fn with_at_least_amount(
self,
resource_address: ResourceAddress,
amount: impl Resolve<Decimal>,
) -> Self {
self.with(
resource_address,
ManifestResourceConstraint::AtLeastAmount(amount.resolve()),
)
}
pub fn with_amount_range(
self,
resource_address: ResourceAddress,
lower_bound: impl Resolve<LowerBound>,
upper_bound: impl Resolve<UpperBound>,
) -> Self {
self.with_general_constraint(
resource_address,
GeneralResourceConstraint {
required_ids: Default::default(),
lower_bound: lower_bound.resolve(),
upper_bound: upper_bound.resolve(),
allowed_ids: AllowedIds::Any,
},
)
}
pub fn with_exact_non_fungibles(
self,
resource_address: ResourceAddress,
non_fungible_ids: impl IntoIterator<Item = NonFungibleLocalId>,
) -> Self {
self.with(
resource_address,
ManifestResourceConstraint::ExactNonFungibles(non_fungible_ids.into_iter().collect()),
)
}
pub fn with_at_least_non_fungibles(
self,
resource_address: ResourceAddress,
non_fungible_ids: impl IntoIterator<Item = NonFungibleLocalId>,
) -> Self {
self.with(
resource_address,
ManifestResourceConstraint::AtLeastNonFungibles(non_fungible_ids.into_iter().collect()),
)
}
pub fn with_general_constraint(
self,
resource_address: ResourceAddress,
bounds: GeneralResourceConstraint,
) -> Self {
self.with(
resource_address,
ManifestResourceConstraint::General(bounds),
)
}
pub fn specified_resources(&self) -> &IndexMap<ResourceAddress, ManifestResourceConstraint> {
&self.specified_resources
}
pub fn iter(&self) -> impl Iterator<Item = (&ResourceAddress, &ManifestResourceConstraint)> {
self.specified_resources.iter()
}
pub fn len(&self) -> usize {
self.specified_resources().len()
}
pub fn is_valid(&self) -> bool {
for (resource_address, constraint) in self.iter() {
if !constraint.is_valid_for(resource_address) {
return false;
}
}
true
}
pub fn contains_specified_resource(&self, resource_address: &ResourceAddress) -> bool {
self.specified_resources.contains_key(resource_address)
}
pub fn validate(
self,
balances: AggregateResourceBalances,
prevent_unspecified_resource_balances: bool,
) -> Result<(), ResourceConstraintsError> {
let AggregateResourceBalances {
fungible_resources,
non_fungible_resources,
} = balances;
if prevent_unspecified_resource_balances {
for (resource_address, amount) in fungible_resources.iter() {
if !self.specified_resources.contains_key(resource_address) && amount.is_positive()
{
return Err(
ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource {
resource_address: *resource_address,
},
);
}
}
for (resource_address, ids) in non_fungible_resources.iter() {
if !self.specified_resources.contains_key(resource_address) && !ids.is_empty() {
return Err(
ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource {
resource_address: *resource_address,
},
);
}
}
}
let zero_balance = Decimal::ZERO;
let empty_ids: IndexSet<NonFungibleLocalId> = Default::default();
for (resource_address, constraint) in self.specified_resources {
if resource_address.is_fungible() {
let amount = fungible_resources
.get(&resource_address)
.unwrap_or(&zero_balance);
constraint.validate_fungible(*amount).map_err(|error| {
ResourceConstraintsError::ResourceConstraintFailed {
resource_address,
error,
}
})?;
} else {
let ids = non_fungible_resources
.get(&resource_address)
.unwrap_or(&empty_ids);
constraint.validate_non_fungible(ids).map_err(|error| {
ResourceConstraintsError::ResourceConstraintFailed {
resource_address,
error,
}
})?;
}
}
Ok(())
}
}
pub struct AggregateResourceBalances {
fungible_resources: IndexMap<ResourceAddress, Decimal>,
non_fungible_resources: IndexMap<ResourceAddress, IndexSet<NonFungibleLocalId>>,
}
impl AggregateResourceBalances {
pub fn new() -> Self {
Self {
fungible_resources: Default::default(),
non_fungible_resources: Default::default(),
}
}
pub fn add_fungible(&mut self, resource_address: ResourceAddress, amount: Decimal) {
if amount.is_positive() {
self.fungible_resources
.entry(resource_address)
.or_default()
.add_assign(amount);
}
}
pub fn add_non_fungible(
&mut self,
resource_address: ResourceAddress,
ids: IndexSet<NonFungibleLocalId>,
) {
if !ids.is_empty() {
self.non_fungible_resources
.entry(resource_address)
.or_default()
.extend(ids);
}
}
pub fn validate_only(
self,
constraints: ManifestResourceConstraints,
) -> Result<(), ResourceConstraintsError> {
constraints.validate(self, true)
}
pub fn validate_includes(
self,
constraints: ManifestResourceConstraints,
) -> Result<(), ResourceConstraintsError> {
constraints.validate(self, false)
}
}
impl IntoIterator for ManifestResourceConstraints {
type Item = (ResourceAddress, ManifestResourceConstraint);
type IntoIter =
<IndexMap<ResourceAddress, ManifestResourceConstraint> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.specified_resources.into_iter()
}
}
#[derive(Debug, Clone, PartialEq, Eq, ManifestSbor, ScryptoSbor)]
pub enum ManifestResourceConstraint {
NonZeroAmount,
ExactAmount(Decimal),
AtLeastAmount(Decimal),
ExactNonFungibles(IndexSet<NonFungibleLocalId>),
AtLeastNonFungibles(IndexSet<NonFungibleLocalId>),
General(GeneralResourceConstraint),
}
impl ManifestResourceConstraint {
pub fn is_valid_for(&self, resource_address: &ResourceAddress) -> bool {
if resource_address.is_fungible() {
self.is_valid_for_fungible_use()
} else {
self.is_valid_for_non_fungible_use()
}
}
pub fn is_valid_for_fungible_use(&self) -> bool {
match self {
ManifestResourceConstraint::NonZeroAmount => true,
ManifestResourceConstraint::ExactAmount(amount) => !amount.is_negative(),
ManifestResourceConstraint::AtLeastAmount(amount) => !amount.is_negative(),
ManifestResourceConstraint::ExactNonFungibles(_) => false,
ManifestResourceConstraint::AtLeastNonFungibles(_) => false,
ManifestResourceConstraint::General(general) => general.is_valid_for_fungible_use(),
}
}
pub fn is_valid_for_non_fungible_use(&self) -> bool {
match self {
ManifestResourceConstraint::NonZeroAmount => true,
ManifestResourceConstraint::ExactAmount(amount) => {
!amount.is_negative() && amount.checked_floor() == Some(*amount)
}
ManifestResourceConstraint::AtLeastAmount(amount) => {
!amount.is_negative() && amount.checked_floor() == Some(*amount)
}
ManifestResourceConstraint::ExactNonFungibles(_) => true,
ManifestResourceConstraint::AtLeastNonFungibles(_) => true,
ManifestResourceConstraint::General(general) => general.is_valid_for_non_fungible_use(),
}
}
pub fn validate_non_fungible(
self,
ids: &IndexSet<NonFungibleLocalId>,
) -> Result<(), ResourceConstraintError> {
let amount = Decimal::from(ids.len());
match self {
ManifestResourceConstraint::NonZeroAmount => {
if ids.is_empty() {
return Err(ResourceConstraintError::ExpectedNonZeroAmount);
}
}
ManifestResourceConstraint::ExactAmount(expected_exact_amount) => {
if amount.ne(&expected_exact_amount) {
return Err(ResourceConstraintError::ExpectedExactAmount {
actual_amount: amount,
expected_amount: expected_exact_amount,
});
}
}
ManifestResourceConstraint::AtLeastAmount(expected_at_least_amount) => {
if amount < expected_at_least_amount {
return Err(ResourceConstraintError::ExpectedAtLeastAmount {
expected_at_least_amount,
actual_amount: amount,
});
}
}
ManifestResourceConstraint::ExactNonFungibles(expected_exact_ids) => {
if let Some(missing_id) = expected_exact_ids.difference(ids).next() {
return Err(ResourceConstraintError::NonFungibleMissing {
missing_id: missing_id.clone(),
});
}
if let Some(disallowed_id) = ids.difference(&expected_exact_ids).next() {
return Err(ResourceConstraintError::NonFungibleNotAllowed {
disallowed_id: disallowed_id.clone(),
});
}
}
ManifestResourceConstraint::AtLeastNonFungibles(expected_at_least_ids) => {
if let Some(missing_id) = expected_at_least_ids.difference(ids).next() {
return Err(ResourceConstraintError::NonFungibleMissing {
missing_id: missing_id.clone(),
});
}
}
ManifestResourceConstraint::General(constraint) => {
constraint.validate_non_fungible_ids(ids)?;
}
}
Ok(())
}
pub fn validate_fungible(self, amount: Decimal) -> Result<(), ResourceConstraintError> {
match self {
ManifestResourceConstraint::NonZeroAmount => {
if amount.is_zero() {
return Err(ResourceConstraintError::ExpectedNonZeroAmount);
}
}
ManifestResourceConstraint::ExactAmount(expected_exact_amount) => {
if amount.ne(&expected_exact_amount) {
return Err(ResourceConstraintError::ExpectedExactAmount {
actual_amount: amount,
expected_amount: expected_exact_amount,
});
}
}
ManifestResourceConstraint::AtLeastAmount(expected_at_least_amount) => {
if amount < expected_at_least_amount {
return Err(ResourceConstraintError::ExpectedAtLeastAmount {
expected_at_least_amount,
actual_amount: amount,
});
}
}
ManifestResourceConstraint::ExactNonFungibles(..) => {
return Err(
ResourceConstraintError::NonFungibleConstraintNotValidForFungibleResource,
);
}
ManifestResourceConstraint::AtLeastNonFungibles(..) => {
return Err(
ResourceConstraintError::NonFungibleConstraintNotValidForFungibleResource,
);
}
ManifestResourceConstraint::General(constraint) => {
constraint.validate_fungible(amount)?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
pub enum ResourceConstraintsError {
UnexpectedNonZeroBalanceOfUnspecifiedResource {
resource_address: ResourceAddress,
},
ResourceConstraintFailed {
resource_address: ResourceAddress,
error: ResourceConstraintError,
},
}
#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
pub enum ResourceConstraintError {
NonFungibleConstraintNotValidForFungibleResource,
ExpectedNonZeroAmount,
ExpectedExactAmount {
expected_amount: Decimal,
actual_amount: Decimal,
},
ExpectedAtLeastAmount {
expected_at_least_amount: Decimal,
actual_amount: Decimal,
},
ExpectedAtMostAmount {
expected_at_most_amount: Decimal,
actual_amount: Decimal,
},
NonFungibleMissing {
missing_id: NonFungibleLocalId,
},
NonFungibleNotAllowed {
disallowed_id: NonFungibleLocalId,
},
}
#[derive(Debug, Clone, PartialEq, Eq, ManifestSbor, ScryptoSbor)]
pub struct GeneralResourceConstraint {
pub required_ids: IndexSet<NonFungibleLocalId>,
pub lower_bound: LowerBound,
pub upper_bound: UpperBound,
pub allowed_ids: AllowedIds,
}
impl GeneralResourceConstraint {
pub fn fungible(
lower_bound: impl Resolve<LowerBound>,
upper_bound: impl Resolve<UpperBound>,
) -> Self {
let constraint = Self {
required_ids: Default::default(),
lower_bound: lower_bound.resolve(),
upper_bound: upper_bound.resolve(),
allowed_ids: AllowedIds::Any,
};
if !constraint.is_valid_for_fungible_use() {
panic!("Bounds are invalid for fungible use");
}
constraint
}
pub fn non_fungible_no_allow_list(
required_ids: impl IntoIterator<Item = NonFungibleLocalId>,
lower_bound: impl Resolve<LowerBound>,
upper_bound: impl Resolve<UpperBound>,
) -> Self {
let constraint = Self {
required_ids: required_ids.into_iter().collect(),
lower_bound: lower_bound.resolve(),
upper_bound: upper_bound.resolve(),
allowed_ids: AllowedIds::Any,
};
if !constraint.is_valid_for_non_fungible_use() {
panic!("Bounds are invalid for non-fungible use");
}
constraint
}
pub fn non_fungible_with_allow_list(
required_ids: impl IntoIterator<Item = NonFungibleLocalId>,
lower_bound: impl Resolve<LowerBound>,
upper_bound: impl Resolve<UpperBound>,
allowed_ids: impl IntoIterator<Item = NonFungibleLocalId>,
) -> Self {
let constraint = Self {
required_ids: required_ids.into_iter().collect(),
lower_bound: lower_bound.resolve(),
upper_bound: upper_bound.resolve(),
allowed_ids: AllowedIds::allowlist(allowed_ids),
};
if !constraint.is_valid_for_non_fungible_use() {
panic!("Bounds are invalid for non-fungible use");
}
constraint
}
pub fn is_valid_for_fungible_use(&self) -> bool {
self.required_ids.is_empty()
&& self.lower_bound.is_valid_for_fungible_use()
&& self.upper_bound.is_valid_for_fungible_use()
&& self.allowed_ids.is_valid_for_fungible_use()
&& self.is_valid_independent_of_resource_type()
}
pub fn is_valid_for_non_fungible_use(&self) -> bool {
self.lower_bound.is_valid_for_non_fungible_use()
&& self.upper_bound.is_valid_for_non_fungible_use()
&& self.is_valid_independent_of_resource_type()
}
pub fn validate_fungible(&self, amount: Decimal) -> Result<(), ResourceConstraintError> {
self.validate_amount(amount)?;
Ok(())
}
pub fn validate_non_fungible_ids(
&self,
ids: &IndexSet<NonFungibleLocalId>,
) -> Result<(), ResourceConstraintError> {
self.validate_amount(Decimal::from(ids.len()))?;
if let Some(missing_id) = self.required_ids.difference(ids).next() {
return Err(ResourceConstraintError::NonFungibleMissing {
missing_id: missing_id.clone(),
});
}
self.allowed_ids.validate_ids(ids)?;
Ok(())
}
fn validate_amount(&self, amount: Decimal) -> Result<(), ResourceConstraintError> {
self.lower_bound.validate_amount(&amount)?;
self.upper_bound.validate_amount(&amount)?;
Ok(())
}
pub fn is_valid_independent_of_resource_type(&self) -> bool {
if self.lower_bound.equivalent_decimal() > self.upper_bound.equivalent_decimal() {
return false;
}
let required_ids_amount = Decimal::from(self.required_ids.len());
if required_ids_amount > self.upper_bound.equivalent_decimal() {
return false;
}
match &self.allowed_ids {
AllowedIds::Allowlist(allowlist) => {
let allowlist_ids_amount = Decimal::from(allowlist.len());
if self.lower_bound.equivalent_decimal() > allowlist_ids_amount {
return false;
}
if !self.required_ids.is_subset(allowlist) {
return false;
}
}
AllowedIds::Any => {}
}
true
}
pub fn normalize(&mut self) {
let required_ids_len = Decimal::from(self.required_ids.len());
if self.lower_bound.equivalent_decimal() < required_ids_len {
self.lower_bound = LowerBound::Inclusive(required_ids_len);
}
if let AllowedIds::Allowlist(allowlist) = &self.allowed_ids {
let allowlist_len = Decimal::from(allowlist.len());
if allowlist_len < self.upper_bound.equivalent_decimal() {
self.upper_bound = UpperBound::Inclusive(allowlist_len);
}
}
if self.allowed_ids.allowlist_equivalent_length() > self.required_ids.len() {
if required_ids_len == self.upper_bound.equivalent_decimal() {
self.allowed_ids = AllowedIds::Allowlist(self.required_ids.clone());
} else if let AllowedIds::Allowlist(allowlist) = &self.allowed_ids {
if Decimal::from(allowlist.len()) == self.lower_bound.equivalent_decimal() {
self.required_ids = allowlist.clone();
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ManifestSbor, ScryptoSbor)]
pub enum LowerBound {
NonZero,
Inclusive(Decimal),
}
impl PartialOrd for LowerBound {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for LowerBound {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match (self, other) {
(
LowerBound::Inclusive(self_lower_inclusive),
LowerBound::Inclusive(other_lower_inclusive),
) => self_lower_inclusive.cmp(other_lower_inclusive),
(LowerBound::Inclusive(self_lower_inclusive), LowerBound::NonZero) => {
if self_lower_inclusive.is_positive() {
core::cmp::Ordering::Greater
} else {
core::cmp::Ordering::Less
}
}
(LowerBound::NonZero, LowerBound::Inclusive(other_lower_inclusive)) => {
if other_lower_inclusive.is_positive() {
core::cmp::Ordering::Less
} else {
core::cmp::Ordering::Greater
}
}
(LowerBound::NonZero, LowerBound::NonZero) => core::cmp::Ordering::Equal,
}
}
}
impl LowerBound {
pub const fn zero() -> Self {
Self::Inclusive(Decimal::ZERO)
}
pub const fn non_zero() -> Self {
Self::NonZero
}
pub fn at_least(decimal: Decimal) -> Self {
if decimal.is_negative() {
panic!("An at_least bound is negative");
}
Self::Inclusive(decimal)
}
pub fn of(lower_bound: impl Resolve<LowerBound>) -> Self {
lower_bound.resolve()
}
pub fn validate_amount(&self, amount: &Decimal) -> Result<(), ResourceConstraintError> {
match self {
LowerBound::NonZero => {
if amount.is_zero() {
return Err(ResourceConstraintError::ExpectedNonZeroAmount);
}
}
LowerBound::Inclusive(inclusive) => {
if amount < inclusive {
return Err(ResourceConstraintError::ExpectedAtLeastAmount {
expected_at_least_amount: *inclusive,
actual_amount: *amount,
});
}
}
}
Ok(())
}
pub fn is_valid_for_fungible_use(&self) -> bool {
match self {
LowerBound::NonZero => true,
LowerBound::Inclusive(amount) => !amount.is_negative(),
}
}
pub fn is_valid_for_non_fungible_use(&self) -> bool {
match self {
LowerBound::NonZero => true,
LowerBound::Inclusive(amount) => {
!amount.is_negative() && amount.checked_floor() == Some(*amount)
}
}
}
pub fn cmp_upper(&self, other: &UpperBound) -> core::cmp::Ordering {
match (self, other) {
(
LowerBound::Inclusive(lower_bound_inclusive),
UpperBound::Inclusive(upper_bound_inclusive),
) => lower_bound_inclusive.cmp(upper_bound_inclusive),
(_, UpperBound::Unbounded) => core::cmp::Ordering::Less,
(LowerBound::NonZero, UpperBound::Inclusive(upper_bound_inclusive)) => {
if upper_bound_inclusive.is_zero() {
core::cmp::Ordering::Greater
} else {
core::cmp::Ordering::Less
}
}
}
}
pub fn is_zero(&self) -> bool {
self.eq(&Self::zero())
}
pub fn is_positive(&self) -> bool {
!self.is_zero()
}
pub fn add_from(&mut self, other: Self) -> Result<(), BoundAdjustmentError> {
let new_bound = match (*self, other) {
(LowerBound::Inclusive(self_lower_bound), LowerBound::Inclusive(other_lower_bound)) => {
let lower_bound_inclusive = self_lower_bound
.checked_add(other_lower_bound)
.ok_or(BoundAdjustmentError::DecimalOverflow)?;
LowerBound::Inclusive(lower_bound_inclusive)
}
(LowerBound::Inclusive(amount), LowerBound::NonZero)
| (LowerBound::NonZero, LowerBound::Inclusive(amount)) => {
if amount.is_zero() {
LowerBound::NonZero
} else {
LowerBound::Inclusive(amount)
}
}
(LowerBound::NonZero, LowerBound::NonZero) => LowerBound::NonZero,
};
*self = new_bound;
Ok(())
}
pub fn take_amount(&mut self, take_amount: Decimal) {
let new_bound = match *self {
LowerBound::Inclusive(lower_bound_inclusive) => {
if take_amount > lower_bound_inclusive {
Self::zero()
} else {
LowerBound::Inclusive(lower_bound_inclusive - take_amount)
}
}
LowerBound::NonZero => {
if take_amount.is_zero() {
LowerBound::NonZero
} else {
Self::zero()
}
}
};
*self = new_bound;
}
pub fn constrain_to(&mut self, other_bound: LowerBound) {
let new_bound = (*self).max(other_bound);
*self = new_bound;
}
pub fn equivalent_decimal(&self) -> Decimal {
match self {
LowerBound::Inclusive(decimal) => *decimal,
LowerBound::NonZero => Decimal::from_attos(I192::ONE),
}
}
pub fn is_satisfied_by(&self, amount: Decimal) -> bool {
match self {
LowerBound::NonZero => amount.is_positive(),
LowerBound::Inclusive(inclusive_lower_bound) => *inclusive_lower_bound <= amount,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ManifestSbor, ScryptoSbor)]
pub enum UpperBound {
Inclusive(Decimal),
Unbounded,
}
impl PartialOrd for UpperBound {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for UpperBound {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match (self, other) {
(
UpperBound::Inclusive(upper_bound_inclusive),
UpperBound::Inclusive(other_upper_bound_inclusive),
) => upper_bound_inclusive.cmp(other_upper_bound_inclusive),
(UpperBound::Inclusive(_), UpperBound::Unbounded) => core::cmp::Ordering::Less,
(UpperBound::Unbounded, UpperBound::Inclusive(_)) => core::cmp::Ordering::Greater,
(UpperBound::Unbounded, UpperBound::Unbounded) => core::cmp::Ordering::Equal,
}
}
}
impl UpperBound {
pub const fn unbounded() -> Self {
Self::Unbounded
}
pub const fn zero() -> Self {
Self::Inclusive(Decimal::ZERO)
}
pub fn at_most(decimal: Decimal) -> Self {
if decimal.is_negative() {
panic!("An at_most bound is negative");
}
Self::Inclusive(decimal)
}
pub fn of(upper_bound: impl Resolve<UpperBound>) -> Self {
upper_bound.resolve()
}
pub fn validate_amount(&self, amount: &Decimal) -> Result<(), ResourceConstraintError> {
match self {
UpperBound::Inclusive(inclusive) => {
if amount > inclusive {
return Err(ResourceConstraintError::ExpectedAtMostAmount {
expected_at_most_amount: *inclusive,
actual_amount: *amount,
});
}
}
UpperBound::Unbounded => {}
}
Ok(())
}
pub fn is_valid_for_fungible_use(&self) -> bool {
match self {
UpperBound::Inclusive(amount) => !amount.is_negative(),
UpperBound::Unbounded => true,
}
}
pub fn is_valid_for_non_fungible_use(&self) -> bool {
match self {
UpperBound::Inclusive(amount) => {
!amount.is_negative() && amount.checked_floor() == Some(*amount)
}
UpperBound::Unbounded => true,
}
}
pub fn add_from(&mut self, other: Self) -> Result<(), BoundAdjustmentError> {
let new_bound = match (*self, other) {
(
UpperBound::Inclusive(self_upper_bound_inclusive),
UpperBound::Inclusive(other_upper_bound_inclusive),
) => {
let upper_bound_inclusive = self_upper_bound_inclusive
.checked_add(other_upper_bound_inclusive)
.ok_or(BoundAdjustmentError::DecimalOverflow)?;
UpperBound::Inclusive(upper_bound_inclusive)
}
(_, UpperBound::Unbounded) | (UpperBound::Unbounded, _) => UpperBound::Unbounded,
};
*self = new_bound;
Ok(())
}
pub fn take_amount(&mut self, take_amount: Decimal) -> Result<(), BoundAdjustmentError> {
let new_bound = match *self {
UpperBound::Inclusive(upper_bound_inclusive) => {
if take_amount > upper_bound_inclusive {
return Err(BoundAdjustmentError::TakeCannotBeSatisfied);
}
UpperBound::Inclusive(upper_bound_inclusive - take_amount)
}
UpperBound::Unbounded => UpperBound::Unbounded,
};
*self = new_bound;
Ok(())
}
pub fn constrain_to(&mut self, other_bound: UpperBound) {
let new_bound = (*self).min(other_bound);
*self = new_bound;
}
pub fn equivalent_decimal(&self) -> Decimal {
match self {
UpperBound::Inclusive(decimal) => *decimal,
UpperBound::Unbounded => Decimal::MAX,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, ManifestSbor, ScryptoSbor)]
pub enum AllowedIds {
Allowlist(IndexSet<NonFungibleLocalId>),
Any,
}
impl AllowedIds {
pub fn none() -> Self {
Self::Allowlist(Default::default())
}
pub fn allowlist(allowlist: impl IntoIterator<Item = NonFungibleLocalId>) -> Self {
Self::Allowlist(allowlist.into_iter().collect())
}
pub fn any() -> Self {
Self::Any
}
pub fn validate_ids(
&self,
ids: &IndexSet<NonFungibleLocalId>,
) -> Result<(), ResourceConstraintError> {
match self {
AllowedIds::Allowlist(allowed) => {
for id in ids {
if !allowed.contains(id) {
return Err(ResourceConstraintError::NonFungibleNotAllowed {
disallowed_id: id.clone(),
});
}
}
}
AllowedIds::Any => {}
}
Ok(())
}
pub fn allowlist_equivalent_length(&self) -> usize {
match self {
Self::Allowlist(allowlist) => allowlist.len(),
Self::Any => usize::MAX,
}
}
pub fn is_valid_for_fungible_use(&self) -> bool {
match self {
AllowedIds::Allowlist(allowlist) => allowlist.is_empty(),
AllowedIds::Any => true,
}
}
pub fn is_allow_list_and(
&self,
callback: impl FnOnce(&IndexSet<NonFungibleLocalId>) -> bool,
) -> bool {
match self {
AllowedIds::Allowlist(index_set) => callback(index_set),
AllowedIds::Any => false,
}
}
}
pub enum BoundAdjustmentError {
DecimalOverflow,
TakeCannotBeSatisfied,
}
resolvable_with_identity_impl!(LowerBound);
impl<T: Resolve<Decimal>> ResolveFrom<T> for LowerBound {
fn resolve_from(value: T) -> Self {
LowerBound::Inclusive(value.resolve())
}
}
resolvable_with_identity_impl!(UpperBound);
impl<T: Resolve<Decimal>> ResolveFrom<T> for UpperBound {
fn resolve_from(value: T) -> Self {
UpperBound::Inclusive(value.resolve())
}
}