zipper/
lib.rs

1use anchor_lang::prelude::*;
2use anchor_spl::token::TokenAccount;
3use solana_security_txt::security_txt;
4
5declare_id!("Z1PrGTgZp5Q1WKewjF4XaTW2nHvNxvbxs7qW8p9qz5U");
6
7security_txt! {
8    name: "Zipper",
9    project_url: "http://github.com/cavemanloverboy/zipper",
10    contacts: "caveycool@gmail.com",
11    policy: "https://github.com/cavemanloverboy/zipper/blob/main/SECURITY.md"
12}
13
14#[program]
15pub mod zipper {
16    use super::*;
17
18    pub fn verify(ctx: Context<VerifyAccounts>, balances: Vec<u64>) -> Result<()> {
19        // Check that the number of accounts provided is correct
20        require_eq!(
21            ctx.remaining_accounts.len(),
22            balances.len(),
23            ZipperError::InvalidNumberOfAccountsOrBalances
24        );
25
26        // Check that all accounts provided are token accounts
27        let actual_balances: Vec<(u64, String)> = ctx
28            .remaining_accounts
29            .into_iter()
30            .map(|acc| {
31                if let Ok(token_account) = TokenAccount::try_deserialize(&mut &**acc.data.borrow())
32                {
33                    // Attempt to deserialize spl token account and get balance + mint
34                    return Ok((
35                        token_account.amount,
36                        format!(
37                            "spl addr {}, mint {}",
38                            acc.key.to_string(),
39                            token_account.mint
40                        ),
41                    ));
42                } else if acc.owner == &System::id() {
43                    // If system program account just retrieve lamports
44                    Ok((acc.lamports(), format!("sol addr {}", acc.key.to_string())))
45                } else {
46                    // Neither SPL or System Program Account
47                    Err(ZipperError::NonSOLOrSPLAccountProvided.into())
48                }
49            })
50            .collect::<Result<Vec<(u64, String)>>>()
51            .map_err(|_| ZipperError::NonSOLOrSPLAccountProvided)?;
52
53        // Check Balances
54        for i in 0..actual_balances.len() {
55            if actual_balances[i].0 < balances[i] {
56                panic!(
57                    "expected {} >= {} for {}",
58                    actual_balances[i].0, balances[i], actual_balances[i].1,
59                );
60            } else {
61                msg!(
62                    "expected {} >= {} for {}",
63                    actual_balances[i].0,
64                    balances[i],
65                    actual_balances[i].1,
66                )
67            }
68        }
69        Ok(())
70    }
71}
72
73#[derive(Accounts)]
74pub struct VerifyAccounts {}
75
76pub struct AccountZipper;
77
78impl AccountZipper {
79    /// NOTE: SOL account comes first!
80    pub fn zip_accounts(keys: &[Pubkey]) -> Vec<AccountMeta> {
81        keys.into_iter()
82            .map(|&pubkey| AccountMeta {
83                pubkey,
84                is_signer: false,
85                is_writable: false,
86            })
87            .collect()
88    }
89}
90
91#[error_code]
92pub enum ZipperError {
93    #[msg("number of SOL + SPL accounts does not match the number of expected_balances provided")]
94    InvalidNumberOfAccountsOrBalances,
95    #[msg("one of the accounts has a lower-than-expected balance")]
96    InsufficientBalance,
97    #[msg("an account that is not an spl account was provided as an additional account")]
98    NonSOLOrSPLAccountProvided,
99}