hydra/utils/validation/
mod.rs

1use crate::error::HydraError;
2use crate::state::{Fanout, MembershipModel};
3use anchor_lang::prelude::*;
4use anchor_lang::solana_program::instruction::Instruction;
5use anchor_spl::token::TokenAccount;
6use mpl_token_metadata::state::Metadata;
7
8pub fn assert_derivation(
9    program_id: &Pubkey,
10    account: &AccountInfo,
11    path: &[&[u8]],
12    error: Option<error::Error>,
13) -> Result<u8> {
14    let (key, bump) = Pubkey::find_program_address(&path, program_id);
15    if key != *account.key {
16        if error.is_some() {
17            let err = error.unwrap();
18            msg!("Derivation {:?}", err);
19            return Err(err.into());
20        }
21        msg!("DerivedKeyInvalid");
22        return Err(HydraError::DerivedKeyInvalid.into());
23    }
24    Ok(bump)
25}
26
27pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> Result<()> {
28    if account.owner != owner {
29        Err(HydraError::IncorrectOwner.into())
30    } else {
31        Ok(())
32    }
33}
34
35pub fn assert_membership_model(
36    fanout: &Account<Fanout>,
37    model: MembershipModel,
38) -> Result<()> {
39    if fanout.membership_model != model {
40        return Err(HydraError::InvalidMembershipModel.into());
41    }
42    Ok(())
43}
44
45pub fn assert_ata(
46    account: &AccountInfo,
47    target: &Pubkey,
48    mint: &Pubkey,
49    err: Option<error::Error>,
50) -> Result<u8> {
51    assert_derivation(
52        &anchor_spl::associated_token::ID,
53        &account.to_account_info(),
54        &[
55            target.as_ref(),
56            anchor_spl::token::ID.as_ref(),
57            mint.as_ref(),
58        ],
59        err,
60    )
61}
62
63pub fn assert_shares_distributed(fanout: &Account<Fanout>) -> Result<()> {
64    if fanout.total_available_shares != 0 {
65        return Err(HydraError::SharesArentAtMax.into());
66    }
67    Ok(())
68}
69
70pub fn assert_holding(
71    owner: &AccountInfo,
72    token_account: &Account<TokenAccount>,
73    mint_info: &AccountInfo,
74) -> Result<()> {
75    assert_owned_by(mint_info, &spl_token::id())?;
76    let token_account_info = token_account.to_account_info();
77    assert_owned_by(&token_account_info, &spl_token::id())?;
78    if token_account.owner != *owner.key {
79        return Err(HydraError::IncorrectOwner.into());
80    }
81    if token_account.amount < 1 {
82        return Err(HydraError::WalletDoesNotOwnMembershipToken.into());
83    }
84    if token_account.mint != mint_info.key() {
85        return Err(HydraError::MintDoesNotMatch.into());
86    }
87    Ok(())
88}
89
90pub fn assert_distributed(
91    ix: Instruction,
92    subject: &Pubkey,
93    membership_model: MembershipModel,
94) -> Result<()> {
95    if ix.program_id != crate::id() {
96        return Err(HydraError::MustDistribute.into());
97    }
98    let instruction_id = match membership_model {
99        MembershipModel::Wallet => [252, 168, 167, 66, 40, 201, 182, 163],
100        MembershipModel::NFT => [108, 240, 68, 81, 144, 83, 58, 153],
101        MembershipModel::Token => [126, 105, 46, 135, 28, 36, 117, 212],
102    };
103    if instruction_id != ix.data[0..8] {
104        return Err(HydraError::MustDistribute.into());
105    }
106    if subject != &ix.accounts[1].pubkey {
107        return Err(HydraError::MustDistribute.into());
108    }
109    Ok(())
110}
111
112pub fn assert_valid_metadata(
113    metadata_account: &AccountInfo,
114    mint: &AccountInfo,
115) -> Result<Metadata> {
116    let meta = Metadata::from_account_info(metadata_account)?;
117    if meta.mint != *mint.key {
118        return Err(HydraError::InvalidMetadata.into());
119    }
120    Ok(meta)
121}