Skip to main content

cow_settlement_interface/pda/
buffer.rs

1//! Buffer PDA seed and address derivation.
2//!
3//! Each buffer is a per-token SPL token account that holds funds controlled
4//! by the settlement state PDA. It lives at a PDA keyed by the token mint, so
5//! there is exactly one buffer address per token.
6//!
7//! The token account stored at this address is initialized by the
8//! `CreateBuffer` instruction; its SPL `owner` (token authority) is the
9//! settlement state PDA (see [`crate::pda::state`]), the single authority
10//! controlling every buffer.
11//!
12//! The seeds start with [`SETTLEMENT_SEED`], which carries
13//! the cargo package major and minor version, so a version bump moves every buffer.
14//! Since only the state PDA can spend a buffer and that address moves too,
15//! buffers must be drained under the old version before a bump ships, or their
16//! contents are stranded.
17//!
18//! Unlike the order PDA, which stores its own bump (see
19//! [`crate::data::order::OrderAccount`]), a buffer is a fixed-size SPL token
20//! account with no room for one.
21
22use solana_account_view::AccountView;
23use solana_address::Address;
24use solana_program_error::ProgramError;
25use solana_pubkey::Pubkey;
26
27use crate::pda::SETTLEMENT_SEED;
28use crate::SettlementError;
29
30/// Trailing seed identifying the buffer PDAs.
31pub const BUFFER_SEED: &[u8] = b"buffer";
32
33/// Canonical seed components for the buffer PDA holding the specified `mint`
34/// token.
35///
36/// `mint` is the raw 32-byte token mint address, so the same helper serves
37/// both the off-chain builder and the on-chain handler (which holds the mint
38/// as an `Address`).
39pub fn buffer_pda_seeds(mint: &[u8; 32]) -> [&[u8]; 3] {
40    [SETTLEMENT_SEED, mint, BUFFER_SEED]
41}
42
43/// Canonical seeds for re-deriving the buffer PDA for `mint` with `bump`.
44pub fn buffer_pda_signer_seeds<'a>(mint: &'a [u8; 32], bump: &'a [u8; 1]) -> [&'a [u8]; 4] {
45    let [s0, s1, s2] = buffer_pda_seeds(mint);
46    [s0, s1, s2, bump]
47}
48
49/// Derive the canonical buffer PDA address (and bump) for the token `mint`.
50pub fn find_buffer_pda(program_id: &Pubkey, mint: &Pubkey) -> (Pubkey, u8) {
51    Pubkey::find_program_address(&buffer_pda_seeds(mint.as_array()), program_id)
52}
53
54/// Confirm `buffer` matches the derived buffer PDA for `mint` and `bump`.
55#[must_use = "ignoring the output means ignoring the validation result"]
56pub fn validate_buffer_pda(
57    program_id: &Address,
58    buffer: &AccountView,
59    mint: &Address,
60    bump: u8,
61) -> Result<(), ProgramError> {
62    let derived = Address::create_program_address(
63        &buffer_pda_signer_seeds(&mint.to_bytes(), &[bump]),
64        program_id,
65    )
66    .map_err(|_| SettlementError::PushSourceNotBuffer)?;
67    if buffer.address() != &derived {
68        return Err(SettlementError::PushSourceNotBuffer.into());
69    }
70    Ok(())
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::pda::tests::assert_distinct_versions_yield_distinct_pdas;
77
78    #[test]
79    fn find_buffer_pda_uses_canonical_seeds() {
80        let token = Pubkey::new_unique();
81
82        crate::pda::tests::assert_canonical_bump(
83            |program_id| find_buffer_pda(program_id, &token),
84            buffer_pda_seeds(token.as_array()),
85        );
86    }
87
88    #[test]
89    fn distinct_versions_yield_distinct_buffer_pdas() {
90        let mint = Pubkey::new_unique();
91        let (pda, _) = find_buffer_pda(&crate::ID, &mint);
92
93        assert_distinct_versions_yield_distinct_pdas(&pda, &[mint.as_array(), BUFFER_SEED]);
94    }
95
96    #[test]
97    fn accepts_a_valid_pda() {
98        let program_id = Pubkey::new_unique();
99        let mint = Pubkey::new_unique();
100        let (pda, bump) = find_buffer_pda(&program_id, &mint);
101
102        let buffer = crate::instruction::fixtures::fake_account(pda);
103        validate_buffer_pda(&program_id, &buffer, &mint, bump)
104            .expect("the canonical buffer PDA must be accepted");
105    }
106
107    #[test]
108    fn rejects_an_invalid_address() {
109        let program_id = Pubkey::new_unique();
110        let mint = Pubkey::new_unique();
111        let (_, bump) = find_buffer_pda(&program_id, &mint);
112
113        // An account sitting at some other address is not the buffer.
114        let buffer = crate::instruction::fixtures::fake_account(Pubkey::new_unique());
115        let err = validate_buffer_pda(&program_id, &buffer, &mint, bump)
116            .expect_err("a non-canonical address must be rejected");
117        assert_eq!(err, SettlementError::PushSourceNotBuffer.into());
118    }
119
120    #[test]
121    fn rejects_a_wrong_bump() {
122        let program_id = Pubkey::new_unique();
123        let mint = Pubkey::new_unique();
124        let (pda, bump) = find_buffer_pda(&program_id, &mint);
125
126        // The address is canonical but the carried bump doesn't derive it.
127        let buffer = crate::instruction::fixtures::fake_account(pda);
128        let err = validate_buffer_pda(&program_id, &buffer, &mint, bump ^ 1)
129            .expect_err("a wrong bump must be rejected");
130        assert_eq!(err, SettlementError::PushSourceNotBuffer.into());
131    }
132
133    mod proptest {
134        use ::proptest::prelude::*;
135
136        use super::*;
137
138        proptest! {
139            #[test]
140            fn distinct_tokens_yield_distinct_pdas(
141                program_id in any::<[u8; 32]>(),
142                token1 in any::<[u8; 32]>(),
143                token2 in any::<[u8; 32]>(),
144            ) {
145                prop_assume!(token1 != token2);
146                let program_id = Pubkey::new_from_array(program_id);
147                let (pda1, _) = find_buffer_pda(&program_id, &Pubkey::new_from_array(token1));
148                let (pda2, _) = find_buffer_pda(&program_id, &Pubkey::new_from_array(token2));
149                prop_assert_ne!(pda1, pda2);
150            }
151        }
152    }
153}