mpl_hydra/utils/validation/
mod.rs1use crate::{
2 error::HydraError,
3 state::{Fanout, MembershipModel},
4};
5use anchor_lang::{
6 prelude::*,
7 solana_program::{instruction::Instruction, program_memory::sol_memcmp, pubkey::PUBKEY_BYTES},
8};
9use anchor_spl::token::TokenAccount;
10use mpl_token_metadata::state::{Metadata, TokenMetadataAccount};
11
12pub fn cmp_pubkeys(a: &Pubkey, b: &Pubkey) -> bool {
13 sol_memcmp(a.as_ref(), b.as_ref(), PUBKEY_BYTES) == 0
14}
15
16pub fn assert_derivation(
17 program_id: &Pubkey,
18 account: &AccountInfo,
19 path: &[&[u8]],
20 error: Option<error::Error>,
21) -> Result<u8> {
22 let (key, bump) = Pubkey::find_program_address(path, program_id);
23 if !cmp_pubkeys(&key, account.key) {
24 if let Some(err) = error {
25 msg!("Derivation {:?}", err);
26 return Err(err);
27 }
28 msg!("DerivedKeyInvalid");
29 return Err(HydraError::DerivedKeyInvalid.into());
30 }
31 Ok(bump)
32}
33
34pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> Result<()> {
35 if !cmp_pubkeys(account.owner, owner) {
36 Err(HydraError::IncorrectOwner.into())
37 } else {
38 Ok(())
39 }
40}
41
42pub fn assert_membership_model(fanout: &Account<Fanout>, model: MembershipModel) -> Result<()> {
43 if fanout.membership_model != model {
44 return Err(HydraError::InvalidMembershipModel.into());
45 }
46 Ok(())
47}
48
49pub fn assert_ata(
50 account: &AccountInfo,
51 target: &Pubkey,
52 mint: &Pubkey,
53 err: Option<error::Error>,
54) -> Result<u8> {
55 assert_derivation(
56 &anchor_spl::associated_token::ID,
57 &account.to_account_info(),
58 &[
59 target.as_ref(),
60 anchor_spl::token::ID.as_ref(),
61 mint.as_ref(),
62 ],
63 err,
64 )
65}
66
67pub fn assert_shares_distributed(fanout: &Account<Fanout>) -> Result<()> {
68 if fanout.total_available_shares != 0 {
69 return Err(HydraError::SharesArentAtMax.into());
70 }
71 Ok(())
72}
73
74pub fn assert_holding(
75 owner: &AccountInfo,
76 token_account: &Account<TokenAccount>,
77 mint_info: &AccountInfo,
78) -> Result<()> {
79 assert_owned_by(mint_info, &spl_token::id())?;
80 let token_account_info = token_account.to_account_info();
81 assert_owned_by(&token_account_info, &spl_token::id())?;
82 if !cmp_pubkeys(&token_account.owner, owner.key) {
83 return Err(HydraError::IncorrectOwner.into());
84 }
85 if token_account.amount < 1 {
86 return Err(HydraError::WalletDoesNotOwnMembershipToken.into());
87 }
88 if !cmp_pubkeys(&token_account.mint, &mint_info.key()) {
89 return Err(HydraError::MintDoesNotMatch.into());
90 }
91 Ok(())
92}
93
94pub fn assert_distributed(
95 ix: Instruction,
96 subject: &Pubkey,
97 membership_model: MembershipModel,
98) -> Result<()> {
99 if !cmp_pubkeys(&ix.program_id, &crate::id()) {
100 return Err(HydraError::MustDistribute.into());
101 }
102 let instruction_id = match membership_model {
103 MembershipModel::Wallet => [252, 168, 167, 66, 40, 201, 182, 163],
104 MembershipModel::NFT => [108, 240, 68, 81, 144, 83, 58, 153],
105 MembershipModel::Token => [126, 105, 46, 135, 28, 36, 117, 212],
106 };
107 if sol_memcmp(instruction_id.as_ref(), ix.data[0..8].as_ref(), 8) != 0 {
108 return Err(HydraError::MustDistribute.into());
109 }
110 if !cmp_pubkeys(subject, &ix.accounts[1].pubkey) {
111 return Err(HydraError::MustDistribute.into());
112 }
113 Ok(())
114}
115
116pub fn assert_valid_metadata(
117 metadata_account: &AccountInfo,
118 mint: &AccountInfo,
119) -> Result<Metadata> {
120 let meta = Metadata::from_account_info(metadata_account)?;
121 if !cmp_pubkeys(&meta.mint, mint.key) {
122 return Err(HydraError::InvalidMetadata.into());
123 }
124 Ok(meta)
125}
126
127pub fn assert_owned_by_one(account: &AccountInfo, owners: Vec<&Pubkey>) -> Result<()> {
128 for o in owners {
129 let res = assert_owned_by(account, o);
130 if res.is_ok() {
131 return res;
132 }
133 }
134 Err(HydraError::IncorrectOwner.into())
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
141
142 #[test]
143 fn test_multi_owner_check() {
144 let owner = Pubkey::new_unique();
145 let owner1 = Pubkey::new_unique();
146 let owner2 = Pubkey::new_unique();
147 let ad = Pubkey::new_unique();
148 let actual_owner = Pubkey::new_unique();
149 let lam = &mut 10000;
150 let a = AccountInfo::new(&ad, false, false, lam, &mut [0; 0], &actual_owner, false, 0);
151
152 let e = assert_owned_by_one(&a, vec![&owner, &owner2, &owner1]);
153
154 assert!(e.is_err());
155
156 let e = assert_owned_by_one(&a, vec![&owner, &actual_owner, &owner1]);
157
158 assert!(e.is_ok());
159 }
160}