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's layout belongs entirely to
20//! the token program, leaving no room for one.
21
22use solana_address::Address;
23use solana_program_error::ProgramError;
24use solana_pubkey::Pubkey;
25
26use crate::pda::{is_pda_with_signer_seeds, SETTLEMENT_SEED};
27use crate::SettlementError;
28
29/// Trailing seed identifying the buffer PDAs.
30pub const BUFFER_SEED: &[u8] = b"buffer";
31
32/// Canonical seed components for the buffer PDA holding the specified `mint`
33/// token.
34///
35/// `mint` is the raw 32-byte token mint address, so the same helper serves
36/// both the off-chain builder and the on-chain handler (which holds the mint
37/// as an `Address`).
38pub fn buffer_pda_seeds(mint: &[u8; 32]) -> [&[u8]; 3] {
39    [SETTLEMENT_SEED, mint, BUFFER_SEED]
40}
41
42/// Canonical seeds for re-deriving the buffer PDA for `mint` with `bump`.
43pub fn buffer_pda_signer_seeds<'a>(mint: &'a [u8; 32], bump: &'a [u8; 1]) -> [&'a [u8]; 4] {
44    let [s0, s1, s2] = buffer_pda_seeds(mint);
45    [s0, s1, s2, bump]
46}
47
48/// Derive the canonical buffer PDA address (and bump) for the token `mint`.
49pub fn find_buffer_pda(program_id: &Pubkey, mint: &Pubkey) -> (Pubkey, u8) {
50    Pubkey::find_program_address(&buffer_pda_seeds(mint.as_array()), program_id)
51}
52
53/// Confirm `buffer` matches the derived buffer PDA for the mint bytes `mint`
54/// and `bump`.
55#[inline]
56#[must_use = "ignoring the output means ignoring the validation result"]
57pub fn validate_buffer_pda(
58    program_id: &Address,
59    buffer: &Address,
60    mint: &[u8; 32],
61    bump: u8,
62) -> Result<(), ProgramError> {
63    is_pda_with_signer_seeds(buffer, program_id, buffer_pda_signer_seeds(mint, &[bump]))
64        .then_some(())
65        .ok_or(SettlementError::PushSourceNotBuffer.into())
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use crate::pda::tests::assert_distinct_versions_yield_distinct_pdas;
72
73    #[test]
74    fn find_buffer_pda_uses_canonical_seeds() {
75        let token = Pubkey::new_unique();
76
77        crate::pda::tests::assert_canonical_bump(
78            |program_id| find_buffer_pda(program_id, &token),
79            buffer_pda_seeds(token.as_array()),
80        );
81    }
82
83    #[test]
84    fn distinct_versions_yield_distinct_buffer_pdas() {
85        let mint = Pubkey::new_unique();
86        let (pda, _) = find_buffer_pda(&crate::ID, &mint);
87
88        assert_distinct_versions_yield_distinct_pdas(&pda, &[mint.as_array(), BUFFER_SEED]);
89    }
90
91    #[test]
92    fn accepts_a_valid_address() {
93        let program_id = Pubkey::new_unique();
94        let mint = Pubkey::new_unique();
95        let (pda, bump) = find_buffer_pda(&program_id, &mint);
96
97        let buffer = crate::instruction::fixtures::fake_account(pda);
98        validate_buffer_pda(&program_id, buffer.address(), mint.as_array(), bump)
99            .expect("the canonical buffer PDA must be accepted");
100    }
101
102    #[test]
103    fn rejects_an_invalid_address() {
104        let program_id = Pubkey::new_unique();
105        let mint = Pubkey::new_unique();
106        let (_, bump) = find_buffer_pda(&program_id, &mint);
107
108        // An account sitting at some other address is not the buffer.
109        let buffer = crate::instruction::fixtures::fake_account(Pubkey::new_unique());
110        let err = validate_buffer_pda(&program_id, buffer.address(), mint.as_array(), bump)
111            .expect_err("a non-canonical address must be rejected");
112        assert_eq!(err, SettlementError::PushSourceNotBuffer.into());
113    }
114
115    #[test]
116    fn rejects_a_wrong_bump() {
117        let program_id = Pubkey::new_unique();
118        let mint = Pubkey::new_unique();
119        let (pda, bump) = find_buffer_pda(&program_id, &mint);
120
121        // The address is canonical but the carried bump doesn't derive it.
122        let buffer = crate::instruction::fixtures::fake_account(pda);
123        let err = validate_buffer_pda(&program_id, buffer.address(), mint.as_array(), bump ^ 1)
124            .expect_err("a wrong bump must be rejected");
125        assert_eq!(err, SettlementError::PushSourceNotBuffer.into());
126    }
127
128    mod proptest {
129        use ::proptest::prelude::*;
130
131        use super::*;
132
133        proptest! {
134            #[test]
135            fn distinct_tokens_yield_distinct_pdas(
136                program_id in any::<[u8; 32]>(),
137                token1 in any::<[u8; 32]>(),
138                token2 in any::<[u8; 32]>(),
139            ) {
140                prop_assume!(token1 != token2);
141                let program_id = Pubkey::new_from_array(program_id);
142                let (pda1, _) = find_buffer_pda(&program_id, &Pubkey::new_from_array(token1));
143                let (pda2, _) = find_buffer_pda(&program_id, &Pubkey::new_from_array(token2));
144                prop_assert_ne!(pda1, pda2);
145            }
146        }
147    }
148}