mpl_token_auth_rules/state/rules.rs
1use crate::{
2 error::RuleSetError,
3 payload::Payload,
4 types::Assertable,
5 // TODO: Uncomment this after on-curve sycall available.
6 // utils::is_on_curve,
7 utils::{assert_derivation, compute_merkle_root, is_zeroed},
8};
9use serde::{Deserialize, Serialize};
10#[cfg(feature = "serde-with-feature")]
11use serde_with::{As, DisplayFromStr};
12use solana_program::{
13 account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError,
14 pubkey::Pubkey, system_program,
15};
16use std::collections::{HashMap, HashSet};
17
18#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
19/// Operators that can be used to compare against an `Amount` rule.
20pub enum CompareOp {
21 /// Less Than
22 Lt,
23 /// Less Than or Equal To
24 LtEq,
25 /// Equal To
26 Eq,
27 /// Greater Than or Equal To
28 GtEq,
29 /// Greater Than
30 Gt,
31}
32
33/// Enum representation of Rule failure conditions
34pub enum RuleResult {
35 /// The rule succeeded.
36 Success(ProgramError),
37 /// The rule failed.
38 Failure(ProgramError),
39 /// The program failed to execute the rule.
40 Error(ProgramError),
41}
42
43use RuleResult::*;
44
45#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
46/// The struct containing every type of Rule and its associated data.
47pub enum Rule {
48 /// Group AND, where every rule contained must pass.
49 All {
50 /// The vector of Rules contained under All.
51 rules: Vec<Rule>,
52 },
53 /// Group OR, where at least one rule contained must pass.
54 Any {
55 /// The vector of Rules contained under Any.
56 rules: Vec<Rule>,
57 },
58 /// Negation, where the contained rule must fail.
59 Not {
60 /// The Rule contained under Not.
61 rule: Box<Rule>,
62 },
63 /// An additional signer must be present. When the `Validate` instruction is called, this rule
64 /// does not require any `Payload` values, but the additional signer account must be provided
65 /// to `Validate` via the `additional_rule_accounts` argument so that whether it is a signer
66 /// can be retrieved from its `AccountInfo` struct.
67 AdditionalSigner {
68 /// The public key that must have also signed the transaction.
69 #[cfg_attr(feature = "serde-with-feature", serde(with = "As::<DisplayFromStr>"))]
70 account: Pubkey,
71 },
72 /// Direct comparison between `Pubkey`s. When the `Validate` instruction is called, this rule
73 /// requires a `PayloadType` value of `PayloadType::Pubkey`. The `field` value in the rule is
74 /// used to locate the `Pubkey` in the payload to compare to the `Pubkey` in the rule.
75 PubkeyMatch {
76 /// The public key to be compared against.
77 #[cfg_attr(feature = "serde-with-feature", serde(with = "As::<DisplayFromStr>"))]
78 pubkey: Pubkey,
79 /// The field in the `Payload` to be compared.
80 field: String,
81 },
82 /// The comparing `Pubkey` must be in the list of `Pubkey`s. When the `Validate` instruction
83 /// is called, this rule requires a `PayloadType` value of `PayloadType::Pubkey`. The `field`
84 /// value in the Rule is used to locate the `Pubkey` in the payload to compare to the `Pubkey`
85 /// list in the rule.
86 PubkeyListMatch {
87 /// The list of public keys to be compared against.
88 pubkeys: Vec<Pubkey>,
89 /// The field in the `Payload` to be compared.
90 field: String,
91 },
92 /// The comparing `Pubkey` must be a member of the Merkle tree in the rule. When the
93 /// `Validate` instruction is called, this rule requires `PayloadType` values of
94 /// `PayloadType::Pubkey` and `PayloadType::MerkleProof`. The `field` values in the Rule are
95 /// used to locate them in the `Payload`. The `Pubkey` and the proof are used to calculate
96 /// a Merkle root which is compared against the root stored in the rule.
97 PubkeyTreeMatch {
98 /// The root of the Merkle tree.
99 root: [u8; 32],
100 /// The field in the `Payload` to be compared
101 /// when looking for the `Pubkey`.
102 pubkey_field: String,
103 /// The field in the `Payload` to be compared
104 /// when looking for the Merkle proof.
105 proof_field: String,
106 },
107 /// A resulting PDA derivation of seeds must prove the account is a PDA. When the `Validate`
108 /// instruction is called, this rule requires `PayloadType` values of `PayloadType::Seeds`.
109 /// The `field` values in the Rule are used to locate them in the `Payload`. The seeds in the
110 /// `Payload` and the program ID stored in the Rule are used to derive the PDA from the
111 /// `Payload`.
112 PDAMatch {
113 /// The program used for the PDA derivation. If
114 /// `None` then the account owner is used.
115 program: Option<Pubkey>,
116 /// The field in the `Payload` to be compared
117 /// when looking for the PDA.
118 pda_field: String,
119 /// The field in the `Payload` to be compared
120 /// when looking for the seeds.
121 seeds_field: String,
122 },
123 /// The `Pubkey` must be owned by a given program. When the `Validate` instruction is called,
124 /// this rule requires a `PayloadType` value of `PayloadType::Pubkey`. The `field` value in
125 /// the rule is used to locate the `Pubkey` in the payload for which the owner must be the
126 /// program in the rule. Note this same `Pubkey` account must also be provided to `Validate`
127 /// via the `additional_rule_accounts` argument. This is so that the `Pubkey`'s owner can be
128 /// found from its `AccountInfo` struct.
129 ProgramOwned {
130 /// The program that must own the `Pubkey`.
131 #[cfg_attr(feature = "serde-with-feature", serde(with = "As::<DisplayFromStr>"))]
132 program: Pubkey,
133 /// The field in the `Payload` to be compared.
134 field: String,
135 },
136 /// The `Pubkey` must be owned by a program in the list of `Pubkey`s. When the `Validate`
137 /// instruction is called, this rule requires a `PayloadType` value of `PayloadType::Pubkey`.
138 /// The `field` value in the rule is used to locate the `Pubkey` in the payload for which the
139 /// owner must be a program in the list in the rule. Note this same `Pubkey` account must also
140 /// be provided to `Validate` via the `additional_rule_accounts` argument. This is so that the
141 /// `Pubkey`'s owner can be found from its `AccountInfo` struct.
142 ProgramOwnedList {
143 /// The program that must own the `Pubkey`.
144 programs: Vec<Pubkey>,
145 /// The field in the `Payload` to be compared.
146 field: String,
147 },
148 /// The `Pubkey` must be owned by a member of the Merkle tree in the rule. When the `Validate`
149 /// instruction is called, this rule requires `PayloadType` values of `PayloadType::Pubkey` and
150 /// `PayloadType::MerkleProof`. The `field` values in the Rule are used to locate them in the
151 /// `Payload`. Note this same `Pubkey` account must also be provided to `Validate` via the
152 /// `additional_rule_accounts` argument. This is so that the `Pubkey`'s owner can be found
153 /// from its `AccountInfo` struct. The owner and the proof are then used to calculate a Merkle
154 /// root, which is compared against the root stored in the rule.
155 ProgramOwnedTree {
156 /// The root of the Merkle tree.
157 root: [u8; 32],
158 /// The field in the `Payload` to be compared
159 /// when looking for the `Pubkey`.
160 pubkey_field: String,
161 /// The field in the `Payload` to be compared
162 /// when looking for the Merkle proof.
163 proof_field: String,
164 },
165 /// Comparison against the amount of tokens being transferred. When the `Validate`
166 /// instruction is called, this rule requires a `PayloadType` value of `PayloadType::Amount`.
167 /// The `field` value in the Rule is used to locate the numerical amount in the payload to
168 /// compare to the amount stored in the rule, using the comparison operator stored in the rule.
169 Amount {
170 /// The amount to be compared against.
171 amount: u64,
172 /// The operator to be used in the comparison.
173 operator: CompareOp,
174 /// The field the amount is stored in.
175 field: String,
176 },
177 /// Comparison based on time between operations. Currently not implemented. This rule
178 /// is planned check to ensure a certain amount of time has passed. This rule will make use
179 /// of the `rule_set_state_pda` optional account passed into `Validate`, and will require
180 /// the optional `rule_authority` account to sign.
181 Frequency {
182 /// The authority of the frequency account.
183 #[cfg_attr(feature = "serde-with-feature", serde(with = "As::<DisplayFromStr>"))]
184 authority: Pubkey,
185 },
186 /// The true test if a pubkey can be signed from a client and therefore is a true wallet account.
187 /// The details of this rule are as follows: a wallet is defined as being both owned by the
188 /// System Program and the address is on-curve. The `field` value in the rule is used to
189 /// locate the `Pubkey` in the payload that must be on-curve and for which the owner must be
190 /// the System Program. Note this same `Pubkey` account must also be provided to `Validate`
191 /// via the `additional_rule_accounts` argument. This is so that the `Pubkey`'s owner can be
192 /// found from its `AccountInfo` struct.
193 IsWallet {
194 /// The field in the `Payload` to be checked.
195 field: String,
196 },
197 /// An operation that always succeeds.
198 Pass,
199 /// The `Pubkey` must be owned by a program in the set of `Pubkey`s. When the `Validate`
200 /// instruction is called, this rule requires a `PayloadType` value of `PayloadType::Pubkey`.
201 /// The `field` value in the rule is used to locate the `Pubkey` in the payload for which the
202 /// owner must be a program in the set in the rule. Note this same `Pubkey` account must also
203 /// be provided to `Validate` via the `additional_rule_accounts` argument. This is so that the
204 /// `Pubkey`'s owner can be found from its `AccountInfo` struct.
205 ProgramOwnedSet {
206 /// The program that must own the `Pubkey`.
207 programs: HashSet<Pubkey>,
208 /// The field in the `Payload` to be compared.
209 field: String,
210 },
211 /// A rule that tells the operation finder to use the default namespace rule.
212 Namespace,
213}
214
215impl<'a> Assertable<'a> for Rule {
216 /// The top level validation function which parses an entire rule tree.
217 fn validate(
218 &self,
219 accounts: &HashMap<Pubkey, &AccountInfo>,
220 payload: &Payload,
221 update_rule_state: bool,
222 rule_set_state_pda: &Option<&AccountInfo>,
223 rule_authority: &Option<&AccountInfo>,
224 ) -> ProgramResult {
225 let result = self.low_level_validate(
226 accounts,
227 payload,
228 update_rule_state,
229 rule_set_state_pda,
230 rule_authority,
231 );
232
233 match result {
234 Success(_) => Ok(()),
235 Failure(err) => Err(err),
236 Error(err) => Err(err),
237 }
238 }
239}
240
241impl Rule {
242 /// Lower level validation function which iterates through a rule tree and applies boolean logic to rule results.
243 pub fn low_level_validate(
244 &self,
245 accounts: &HashMap<Pubkey, &AccountInfo>,
246 payload: &Payload,
247 _update_rule_state: bool,
248 _rule_set_state_pda: &Option<&AccountInfo>,
249 rule_authority: &Option<&AccountInfo>,
250 ) -> RuleResult {
251 match self {
252 Rule::All { rules } => {
253 msg!("Validating All");
254 let mut last: Option<ProgramError> = None;
255 for rule in rules {
256 let result = rule.low_level_validate(
257 accounts,
258 payload,
259 _update_rule_state,
260 _rule_set_state_pda,
261 rule_authority,
262 );
263 // Return failure on the first failing rule.
264 match result {
265 Success(err) => last = Some(err),
266 _ => return result,
267 }
268 }
269
270 // Return pass if and only if all rules passed.
271 Success(last.unwrap_or_else(|| RuleSetError::UnexpectedRuleSetFailure.into()))
272 }
273 Rule::Any { rules } => {
274 msg!("Validating Any");
275 let mut last_failure: Option<ProgramError> = None;
276 let mut last_error: Option<ProgramError> = None;
277 for rule in rules {
278 let result = rule.low_level_validate(
279 accounts,
280 payload,
281 _update_rule_state,
282 _rule_set_state_pda,
283 rule_authority,
284 );
285 match result {
286 Success(_) => return result,
287 Failure(err) => last_failure = Some(err),
288 Error(err) => last_error = Some(err),
289 }
290 }
291
292 // Return the last failure if and only if no rules passed and there was at least one failure,
293 // otherwise return the last error
294
295 if let Some(err) = last_failure {
296 Failure(err)
297 } else if let Some(err) = last_error {
298 Error(err)
299 } else {
300 Error(RuleSetError::UnexpectedRuleSetFailure.into())
301 }
302 }
303 Rule::Not { rule } => {
304 msg!("Validating Not");
305 let result = rule.low_level_validate(
306 accounts,
307 payload,
308 _update_rule_state,
309 _rule_set_state_pda,
310 rule_authority,
311 );
312
313 // Negate the result.
314 match result {
315 Success(err) => Failure(err),
316 Failure(err) => Success(err),
317 Error(err) => Error(err),
318 }
319 }
320 Rule::AdditionalSigner { account } => {
321 msg!("Validating AdditionalSigner");
322 if let Some(signer) = accounts.get(account) {
323 if signer.is_signer {
324 Success(self.to_error())
325 } else {
326 Failure(self.to_error())
327 }
328 } else {
329 Error(RuleSetError::MissingAccount.into())
330 }
331 }
332 Rule::PubkeyMatch { pubkey, field } => {
333 msg!("Validating PubkeyMatch");
334
335 let key = match payload.get_pubkey(field) {
336 Some(pubkey) => pubkey,
337 _ => return Error(RuleSetError::MissingPayloadValue.into()),
338 };
339
340 if key == pubkey {
341 Success(self.to_error())
342 } else {
343 Failure(self.to_error())
344 }
345 }
346 Rule::PubkeyListMatch { pubkeys, field } => {
347 msg!("Validating PubkeyListMatch");
348
349 let fields = field.split('|').collect::<Vec<&str>>();
350
351 if fields.len() > 1 {
352 let new_rule = Rule::Any {
353 rules: fields
354 .iter()
355 .map(|field| Rule::ProgramOwnedList {
356 programs: pubkeys.clone(),
357 field: field.to_string(),
358 })
359 .collect(),
360 };
361
362 return new_rule.low_level_validate(
363 accounts,
364 payload,
365 _update_rule_state,
366 _rule_set_state_pda,
367 rule_authority,
368 );
369 } else {
370 let key = match payload.get_pubkey(&field.to_owned()) {
371 Some(pubkey) => pubkey,
372 _ => return Error(RuleSetError::MissingPayloadValue.into()),
373 };
374
375 if pubkeys.iter().any(|pubkey| pubkey == key) {
376 return Success(self.to_error());
377 }
378 }
379
380 Failure(self.to_error())
381 }
382 Rule::PubkeyTreeMatch {
383 root,
384 pubkey_field,
385 proof_field,
386 } => {
387 msg!("Validating PubkeyTreeMatch");
388
389 // Get the `Pubkey` we are checking from the payload.
390 let leaf = match payload.get_pubkey(pubkey_field) {
391 Some(pubkey) => pubkey,
392 _ => return Error(RuleSetError::MissingPayloadValue.into()),
393 };
394
395 // Get the Merkle proof from the payload.
396 let merkle_proof = match payload.get_merkle_proof(proof_field) {
397 Some(merkle_proof) => merkle_proof,
398 _ => return Error(RuleSetError::MissingPayloadValue.into()),
399 };
400
401 // Check if the computed hash (root) is equal to the root in the rule.
402 let computed_root = compute_merkle_root(leaf, merkle_proof);
403 if computed_root == *root {
404 Success(self.to_error())
405 } else {
406 Failure(self.to_error())
407 }
408 }
409 Rule::PDAMatch {
410 program,
411 pda_field,
412 seeds_field,
413 } => {
414 msg!("Validating PDAMatch");
415
416 // Get the PDA from the payload.
417 let account = match payload.get_pubkey(pda_field) {
418 Some(pubkey) => pubkey,
419 _ => return Error(RuleSetError::MissingPayloadValue.into()),
420 };
421
422 // Get the derivation seeds from the payload.
423 let seeds = match payload.get_seeds(seeds_field) {
424 Some(seeds) => seeds,
425 _ => return Error(RuleSetError::MissingPayloadValue.into()),
426 };
427
428 // Get the program ID to use for the PDA derivation from the Rule.
429 let program = match program {
430 // If the Pubkey is stored in the rule, use that value.
431 Some(program) => program,
432 None => {
433 // If one is not stored, then assume the program ID is the account owner.
434 match accounts.get(account) {
435 Some(account) => account.owner,
436 _ => return Error(RuleSetError::MissingAccount.into()),
437 }
438 }
439 };
440
441 // Convert the Vec of Vec into Vec of u8 slices.
442 let vec_of_slices = seeds
443 .seeds
444 .iter()
445 .map(Vec::as_slice)
446 .collect::<Vec<&[u8]>>();
447
448 if let Ok(_bump) = assert_derivation(program, account, &vec_of_slices) {
449 Success(self.to_error())
450 } else {
451 Failure(self.to_error())
452 }
453 }
454 Rule::ProgramOwned { program, field } => {
455 msg!("Validating ProgramOwned");
456
457 let key = match payload.get_pubkey(field) {
458 Some(pubkey) => pubkey,
459 _ => return Error(RuleSetError::MissingPayloadValue.into()),
460 };
461
462 if let Some(account) = accounts.get(key) {
463 let data = match account.data.try_borrow() {
464 Ok(data) => data,
465 Err(_) => return Error(ProgramError::AccountBorrowFailed),
466 };
467
468 if is_zeroed(&data) {
469 // Print helpful errors.
470 if data.len() == 0 {
471 msg!("Account data is empty");
472 } else {
473 msg!("Account data is zeroed");
474 }
475
476 // Account must have nonzero data to count as program-owned.
477 return Error(self.to_error());
478 } else if *account.owner == *program {
479 return Success(self.to_error());
480 }
481 } else {
482 return Error(RuleSetError::MissingAccount.into());
483 }
484
485 Failure(self.to_error())
486 }
487 Rule::ProgramOwnedList { programs, field } => {
488 msg!("Validating ProgramOwnedList");
489
490 let fields = field.split('|').collect::<Vec<&str>>();
491
492 if fields.len() > 1 {
493 let new_rule = Rule::Any {
494 rules: fields
495 .iter()
496 .map(|field| Rule::ProgramOwnedList {
497 programs: programs.clone(),
498 field: field.to_string(),
499 })
500 .collect(),
501 };
502
503 return new_rule.low_level_validate(
504 accounts,
505 payload,
506 _update_rule_state,
507 _rule_set_state_pda,
508 rule_authority,
509 );
510 } else {
511 let key = match payload.get_pubkey(&field.to_string()) {
512 Some(pubkey) => pubkey,
513 _ => return Error(RuleSetError::MissingPayloadValue.into()),
514 };
515
516 let account = match accounts.get(key) {
517 Some(account) => account,
518 _ => return Error(RuleSetError::MissingAccount.into()),
519 };
520
521 let data = match account.data.try_borrow() {
522 Ok(data) => data,
523 Err(_) => return Error(ProgramError::AccountBorrowFailed),
524 };
525
526 if is_zeroed(&data) {
527 // Print helpful errors.
528 if data.len() == 0 {
529 msg!("Account data is empty");
530 } else {
531 msg!("Account data is zeroed");
532 }
533
534 return Error(RuleSetError::DataIsEmpty.into());
535 } else if programs.contains(account.owner) {
536 // Account owner must be in the set.
537 return Success(self.to_error());
538 }
539 }
540
541 Failure(self.to_error())
542 }
543 Rule::ProgramOwnedTree {
544 root,
545 pubkey_field,
546 proof_field,
547 } => {
548 msg!("Validating ProgramOwnedTree");
549
550 // Get the `Pubkey` we are checking from the payload.
551 let key = match payload.get_pubkey(pubkey_field) {
552 Some(pubkey) => pubkey,
553 _ => return Error(RuleSetError::MissingPayloadValue.into()),
554 };
555
556 // Get the `AccountInfo` struct for the `Pubkey`.
557 let account = match accounts.get(key) {
558 Some(account) => account,
559 _ => return Error(RuleSetError::MissingAccount.into()),
560 };
561
562 let data = match account.data.try_borrow() {
563 Ok(data) => data,
564 Err(_) => return Error(ProgramError::AccountBorrowFailed),
565 };
566
567 // Account must have nonzero data to count as program-owned.
568 if is_zeroed(&data) {
569 // Print helpful errors.
570 if data.len() == 0 {
571 msg!("Account data is empty");
572 } else {
573 msg!("Account data is zeroed");
574 }
575
576 return Error(RuleSetError::DataIsEmpty.into());
577 }
578
579 // The account owner is the leaf.
580 let leaf = account.owner;
581
582 // Get the Merkle proof from the payload.
583 let merkle_proof = match payload.get_merkle_proof(proof_field) {
584 Some(merkle_proof) => merkle_proof,
585 _ => return Error(RuleSetError::MissingPayloadValue.into()),
586 };
587
588 // Check if the computed hash (root) is equal to the root in the rule.
589 let computed_root = compute_merkle_root(leaf, merkle_proof);
590 if computed_root == *root {
591 Success(self.to_error())
592 } else {
593 Failure(self.to_error())
594 }
595 }
596 Rule::Amount {
597 amount: rule_amount,
598 operator,
599 field,
600 } => {
601 msg!("Validating Amount");
602 if let Some(payload_amount) = &payload.get_amount(field) {
603 let operator_fn = match operator {
604 CompareOp::Lt => PartialOrd::lt,
605 CompareOp::LtEq => PartialOrd::le,
606 CompareOp::Eq => PartialEq::eq,
607 CompareOp::Gt => PartialOrd::gt,
608 CompareOp::GtEq => PartialOrd::ge,
609 };
610
611 if operator_fn(payload_amount, rule_amount) {
612 Success(self.to_error())
613 } else {
614 Failure(self.to_error())
615 }
616 } else {
617 Error(RuleSetError::MissingPayloadValue.into())
618 }
619 }
620 Rule::Frequency { authority } => {
621 msg!("Validating Frequency");
622
623 if let Some(rule_authority) = rule_authority {
624 // TODO: If it's the wrong account (first condition) the `IsNotASigner`
625 // is misleading. Should be improved, perhaps with a `Mismatch` error.
626 if authority != rule_authority.key || !rule_authority.is_signer {
627 return Error(RuleSetError::RuleAuthorityIsNotSigner.into());
628 }
629 } else {
630 return Error(RuleSetError::MissingAccount.into());
631 }
632
633 Error(RuleSetError::NotImplemented.into())
634 }
635 Rule::Pass => {
636 msg!("Validating Pass");
637 Success(self.to_error())
638 }
639 Rule::IsWallet { field } => {
640 msg!("Validating IsWallet");
641
642 // Get the `Pubkey` we are checking from the payload.
643 let key = match payload.get_pubkey(field) {
644 Some(pubkey) => pubkey,
645 _ => return Error(RuleSetError::MissingPayloadValue.into()),
646 };
647
648 // Get the `AccountInfo` struct for the `Pubkey` and verify that
649 // its owner is the System Program.
650 if let Some(account) = accounts.get(key) {
651 if *account.owner != system_program::ID {
652 // TODO: Change error return to commented line after on-curve syscall
653 // available.
654 return Error(RuleSetError::NotImplemented.into());
655 //return (false, self.to_error());
656 }
657 } else {
658 return Error(RuleSetError::MissingAccount.into());
659 }
660
661 // TODO: Uncomment call to `is_on_curve()` after on-curve sycall available.
662 Error(RuleSetError::NotImplemented.into())
663 //(is_on_curve(key), self.to_error())
664 }
665 Rule::ProgramOwnedSet { programs, field } => {
666 msg!("Validating ProgramOwnedSet");
667
668 let fields = field.split('|').collect::<Vec<&str>>();
669
670 if fields.len() > 1 {
671 let new_rule = Rule::Any {
672 rules: fields
673 .iter()
674 .map(|field| Rule::ProgramOwnedSet {
675 programs: programs.clone(),
676 field: field.to_string(),
677 })
678 .collect(),
679 };
680
681 return new_rule.low_level_validate(
682 accounts,
683 payload,
684 _update_rule_state,
685 _rule_set_state_pda,
686 rule_authority,
687 );
688 } else {
689 let key = match payload.get_pubkey(&field.to_string()) {
690 Some(pubkey) => pubkey,
691 _ => return Error(RuleSetError::MissingPayloadValue.into()),
692 };
693
694 let account = match accounts.get(key) {
695 Some(account) => account,
696 _ => return Error(RuleSetError::MissingAccount.into()),
697 };
698
699 let data = match account.data.try_borrow() {
700 Ok(data) => data,
701 Err(_) => return Error(ProgramError::AccountBorrowFailed),
702 };
703
704 if is_zeroed(&data) {
705 // Print helpful errors.
706 if data.len() == 0 {
707 msg!("Account data is empty");
708 } else {
709 msg!("Account data is zeroed");
710 }
711
712 return Error(RuleSetError::DataIsEmpty.into());
713 } else if programs.contains(account.owner) {
714 // Account owner must be in the set.
715 return Success(self.to_error());
716 }
717 }
718
719 Failure(self.to_error())
720 }
721 Rule::Namespace => {
722 msg!("Validating Namespace");
723 Failure(self.to_error())
724 }
725 }
726 }
727
728 /// Convert the rule to a corresponding error resulting from the rule failure.
729 pub fn to_error(&self) -> ProgramError {
730 match self {
731 Rule::All { .. }
732 | Rule::Any { .. }
733 | Rule::Not { .. }
734 | Rule::Pass
735 | Rule::Namespace => RuleSetError::UnexpectedRuleSetFailure.into(),
736 Rule::AdditionalSigner { .. } => RuleSetError::AdditionalSignerCheckFailed.into(),
737 Rule::PubkeyMatch { .. } => RuleSetError::PubkeyMatchCheckFailed.into(),
738 Rule::PubkeyListMatch { .. } => RuleSetError::PubkeyListMatchCheckFailed.into(),
739 Rule::PubkeyTreeMatch { .. } => RuleSetError::PubkeyTreeMatchCheckFailed.into(),
740 Rule::PDAMatch { .. } => RuleSetError::PDAMatchCheckFailed.into(),
741 Rule::ProgramOwned { .. } => RuleSetError::ProgramOwnedCheckFailed.into(),
742 Rule::ProgramOwnedList { .. } => RuleSetError::ProgramOwnedListCheckFailed.into(),
743 Rule::ProgramOwnedTree { .. } => RuleSetError::ProgramOwnedTreeCheckFailed.into(),
744 Rule::Amount { .. } => RuleSetError::AmountCheckFailed.into(),
745 Rule::Frequency { .. } => RuleSetError::FrequencyCheckFailed.into(),
746 Rule::IsWallet { .. } => RuleSetError::IsWalletCheckFailed.into(),
747 Rule::ProgramOwnedSet { .. } => RuleSetError::ProgramOwnedSetCheckFailed.into(),
748 }
749 }
750}