mpl_token_auth_rules/state/v2/constraint/
amount.rs

1use solana_program::msg;
2
3use crate::{
4    error::RuleSetError,
5    state::{try_from_bytes, RuleResult},
6    state::{
7        v2::{Constraint, ConstraintType, Operator, Str32, HEADER_SECTION, U64_BYTES},
8        Header,
9    },
10};
11
12/// Constraint representing a comparison against the amount of tokens being transferred.
13///
14/// This constraint requires a `PayloadType` value of `PayloadType::Amount`. The `field`
15/// value in the Rule is used to locate the numerical amount in the payload to compare to
16/// the amount stored in the rule, using the comparison operator stored in the rule.
17pub struct Amount<'a> {
18    /// The amount to be compared against.
19    pub amount: &'a u64,
20    /// The operator to be used in the comparison.
21    pub operator: &'a u64,
22    /// The field the amount is stored in.
23    pub field: &'a Str32,
24}
25
26impl<'a> Amount<'a> {
27    /// Deserialize a constraint from a byte array.
28    pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, RuleSetError> {
29        // amount
30        let amount = try_from_bytes::<u64>(0, U64_BYTES, bytes)?;
31        let mut cursor = U64_BYTES;
32
33        // operator
34        let operator = try_from_bytes::<u64>(cursor, U64_BYTES, bytes)?;
35        cursor += U64_BYTES;
36
37        // field
38        let field = try_from_bytes::<Str32>(cursor, Str32::SIZE, bytes)?;
39
40        Ok(Self {
41            amount,
42            operator,
43            field,
44        })
45    }
46
47    /// Serialize a constraint into a byte array.
48    pub fn serialize(
49        field: String,
50        operator: Operator,
51        amount: u64,
52    ) -> Result<Vec<u8>, RuleSetError> {
53        // length of the assert
54        let length = (U64_BYTES + U64_BYTES + Str32::SIZE) as u32;
55        let mut data = Vec::with_capacity(HEADER_SECTION + length as usize);
56
57        // Header
58        Header::serialize(ConstraintType::Amount, length, &mut data);
59
60        // Constraint
61        // - amount
62        data.extend(u64::to_le_bytes(amount));
63        // - operator
64        data.extend(u64::to_le_bytes(operator as u64));
65        // - field
66        let mut field_bytes = [0u8; Str32::SIZE];
67        field_bytes[..field.len()].copy_from_slice(field.as_bytes());
68        data.extend(field_bytes);
69
70        Ok(data)
71    }
72}
73
74impl<'a> Constraint<'a> for Amount<'a> {
75    fn constraint_type(&self) -> ConstraintType {
76        ConstraintType::Amount
77    }
78
79    fn validate(
80        &self,
81        _accounts: &std::collections::HashMap<
82            solana_program::pubkey::Pubkey,
83            &solana_program::account_info::AccountInfo,
84        >,
85        payload: &crate::payload::Payload,
86        _update_rule_state: bool,
87        _rule_set_state_pda: &Option<&solana_program::account_info::AccountInfo>,
88        _rule_authority: &Option<&solana_program::account_info::AccountInfo>,
89    ) -> RuleResult {
90        msg!("Validating Amount");
91        let condition_type = self.constraint_type();
92
93        if let Some(payload_amount) = &payload.get_amount(&self.field.to_string()) {
94            let operator_fn = match Operator::try_from(*self.operator) {
95                Ok(Operator::Lt) => PartialOrd::lt,
96                Ok(Operator::LtEq) => PartialOrd::le,
97                Ok(Operator::Eq) => PartialEq::eq,
98                Ok(Operator::Gt) => PartialOrd::gt,
99                Ok(Operator::GtEq) => PartialOrd::ge,
100                // sanity check: the value is checked at creation
101                Err(_) => return RuleResult::Failure(condition_type.to_error()),
102            };
103
104            if operator_fn(payload_amount, self.amount) {
105                RuleResult::Success(condition_type.to_error())
106            } else {
107                RuleResult::Failure(condition_type.to_error())
108            }
109        } else {
110            RuleResult::Error(RuleSetError::MissingPayloadValue.into())
111        }
112    }
113}