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//! Native SOL has a buffer of its own at [`NATIVE_SOL_BUFFER_PDA`]: an empty
19//! account owned by the settlement program, whose lamports on top of rent pay
20//! out orders buying native SOL. It isn't keyed by a mint, so its seeds follow
21//! a pattern of their own.
22//!
23//! Unlike the order PDA, which stores its own bump (see
24//! [`crate::data::order::OrderAccount`]), a buffer's layout belongs entirely to
25//! the token program, leaving no room for one.
26
27use solana_address::Address;
28use solana_program_error::ProgramError;
29use solana_pubkey::Pubkey;
30
31use crate::data::intent::ENCODED_NATIVE_SOL_TRANSFER;
32use crate::pda::{is_pda_with_signer_seeds, SETTLEMENT_SEED};
33use crate::SettlementError;
34
35mod known_mints;
36pub use known_mints::KNOWN_MINTS;
37
38/// Trailing seed identifying the buffer PDAs.
39pub const BUFFER_SEED: &[u8] = b"buffer";
40
41/// Canonical seed components for the buffer PDA holding the specified `mint`
42/// token.
43///
44/// `mint` is the raw 32-byte token mint address, so the same helper serves
45/// both the off-chain builder and the on-chain handler (which holds the mint
46/// as an `Address`).
47pub const fn buffer_pda_seeds(mint: &[u8; 32]) -> [&[u8]; 3] {
48    [SETTLEMENT_SEED, mint, BUFFER_SEED]
49}
50
51/// Canonical seeds for re-deriving the buffer PDA for `mint` with `bump`.
52pub fn buffer_pda_signer_seeds<'a>(mint: &'a [u8; 32], bump: &'a [u8; 1]) -> [&'a [u8]; 4] {
53    let [s0, s1, s2] = buffer_pda_seeds(mint);
54    [s0, s1, s2, bump]
55}
56
57/// Derive the canonical buffer PDA address (and bump) for the token `mint`.
58pub fn find_buffer_pda(program_id: &Pubkey, mint: &Pubkey) -> (Pubkey, u8) {
59    Pubkey::find_program_address(&buffer_pda_seeds(mint.as_array()), program_id)
60}
61
62/// Trailing seed identifying the native SOL buffer PDA.
63pub const NATIVE_SOL_BUFFER_SEED: &[u8] = b"buffer native sol";
64
65/// Canonical seed components for the native SOL buffer PDA.
66pub const NATIVE_SOL_BUFFER_PDA_SEEDS: [&[u8]; 2] = [SETTLEMENT_SEED, NATIVE_SOL_BUFFER_SEED];
67
68pub const NATIVE_SOL_BUFFER_PDA_AND_BUMP: ([u8; 32], u8) =
69    const_crypto::ed25519::derive_program_address(
70        &NATIVE_SOL_BUFFER_PDA_SEEDS,
71        crate::ID.as_array(),
72    );
73
74/// The buffer holding the lamports paid out to orders buying native SOL, under
75/// [`crate::ID`], derived at compile time.
76pub const NATIVE_SOL_BUFFER_PDA: Address =
77    Address::new_from_array(NATIVE_SOL_BUFFER_PDA_AND_BUMP.0);
78
79/// Derive the canonical native SOL buffer PDA address (and bump).
80pub fn find_native_sol_buffer_pda(program_id: &Pubkey) -> (Pubkey, u8) {
81    Pubkey::find_program_address(&NATIVE_SOL_BUFFER_PDA_SEEDS, program_id)
82}
83
84/// A buffer PDA under [`crate::ID`] derived at compile time.
85struct KnownBuffer {
86    mint: [u8; 32],
87    address: [u8; 32],
88}
89
90/// The buffers corresponding [`KNOWN_MINTS`], followed by the native
91/// SOL buffer keyed by [`ENCODED_NATIVE_SOL_TRANSFER`].
92#[allow(long_running_const_eval)]
93const KNOWN_BUFFERS: [KnownBuffer; KNOWN_MINTS.len() + 1] = {
94    let mut buffers = [const {
95        KnownBuffer {
96            mint: [0; 32],
97            address: [0; 32],
98        }
99    }; KNOWN_MINTS.len() + 1];
100    let mut i = 0;
101    while i < KNOWN_MINTS.len() {
102        let mint = const_crypto::bs58::decode_pubkey(KNOWN_MINTS[i]);
103        let (address, _) = const_crypto::ed25519::derive_program_address(
104            &buffer_pda_seeds(&mint),
105            crate::ID.as_array(),
106        );
107        buffers[i] = KnownBuffer { mint, address };
108        i += 1;
109    }
110    buffers[i] = KnownBuffer {
111        mint: ENCODED_NATIVE_SOL_TRANSFER.to_bytes(),
112        address: *NATIVE_SOL_BUFFER_PDA.as_array(),
113    };
114    assert!(i + 1 == buffers.len());
115    buffers
116};
117
118/// [`KNOWN_BUFFERS`] without the native SOL buffer: one per [`KNOWN_MINTS`].
119#[cfg(test)]
120const KNOWN_MINT_BUFFERS: &[KnownBuffer; KNOWN_MINTS.len()] = KNOWN_BUFFERS
121    .first_chunk()
122    .expect("KNOWN_BUFFERS ends with the known mint buffers");
123
124#[cfg(test)]
125const KNOWN_NATIVE_MINT_BUFFER: &KnownBuffer = &KNOWN_BUFFERS[KNOWN_MINTS.len()];
126
127/// Bits of the hash that pick a slot: 512 slots keep collisions among 64 mints
128/// The number of bits representing each input slot of `KNOWN_BUFFER_SLOTS`.
129/// More bits make `KNOWN_BUFFER_SLOTS` exponentially larger, and therefore
130/// increase the size of the program. However, more bits also make it easier to
131/// find a multiplier with no collisions, and therefore reduce compilation
132/// times.
133///
134/// The problem of avoiding collision is the same as the birthday paradox.
135/// As a rule of thumb, finding a multiplier requires an expected `e^(n²/(2m))`
136/// iterations, where `n` is the number of known mints and m is 2^SLOT_BITS.
137const SLOT_BITS: u32 = 9;
138
139/// Gives every one of [`KNOWN_BUFFERS`] its own slot in [`KNOWN_BUFFER_SLOTS`].
140const KNOWN_BUFFER_MULTIPLIER: u64 = find_slot_multiplier(&KNOWN_BUFFERS);
141
142/// The index into [`KNOWN_BUFFERS`] of the mint in each slot, or `u8::MAX` for
143/// an empty slot, which [`known_buffer`] then finds no buffer at.
144const KNOWN_BUFFER_SLOTS: [u8; 1 << SLOT_BITS] = {
145    // Strictly less than MAX because the value is reserved for no buffer.
146    assert!(
147        KNOWN_BUFFERS.len() < u8::MAX as usize,
148        "too many known mints for u8 indices"
149    );
150    let mut slots = [u8::MAX; 1 << SLOT_BITS];
151    let mut i = 0;
152    while i < KNOWN_BUFFERS.len() {
153        slots[slot(&KNOWN_BUFFERS[i].mint, KNOWN_BUFFER_MULTIPLIER)] = i as u8;
154        i += 1;
155    }
156    slots
157};
158
159/// Multiplicative hash of the leading 8 bytes of `mint`, keeping the top
160/// [`SLOT_BITS`] of the product.
161const fn slot(mint: &[u8; 32], multiplier: u64) -> usize {
162    let key = u64::from_le_bytes(*mint.first_chunk().expect("a mint is longer than 8 bytes"));
163    (key.wrapping_mul(multiplier) >> (u64::BITS - SLOT_BITS)) as usize
164}
165
166/// Try the multipliers `SHA-256(0)`, `SHA-256(1)`, ... (each truncated to its
167/// first 8 bytes) until one sends each of `buffers` to a distinct [`slot`].
168///
169/// SHA-256 is utilized to provide a pseudorandom source of multipliers. Normally
170/// this shouldn't be necessary as the addresses themselves should already be effectively
171/// random, but its possible some of the KNOWN_MINTS use vanity addresses.
172///
173/// Panics (at compile time, for [`KNOWN_BUFFER_MULTIPLIER`]) if none does,
174/// which is certain if two mints share their leading 8 bytes.
175const fn find_slot_multiplier(buffers: &[KnownBuffer]) -> u64 {
176    const ATTEMPTS: u32 = 100_000;
177    let mut attempt: u32 = 0;
178    while attempt < ATTEMPTS {
179        let digest = const_crypto::sha2::Sha256::new()
180            .update(&attempt.to_le_bytes())
181            .finalize();
182        let multiplier = u64::from_le_bytes(
183            *digest
184                .first_chunk()
185                .expect("a digest is longer than 8 bytes"),
186        );
187        if has_distinct_slots(buffers, multiplier) {
188            return multiplier;
189        }
190        attempt = attempt
191            .checked_add(1)
192            .expect("attempt stays below ATTEMPTS");
193    }
194    panic!("no multiplier gives every known mint its own slot");
195}
196
197/// Whether `multiplier` sends each of `buffers` to a different [`slot`].
198const fn has_distinct_slots(buffers: &[KnownBuffer], multiplier: u64) -> bool {
199    let mut taken = [false; 1 << SLOT_BITS];
200    let mut i = 0;
201    while i < buffers.len() {
202        let slot = slot(&buffers[i].mint, multiplier);
203        if taken[slot] {
204            return false;
205        }
206        taken[slot] = true;
207        i = i.checked_add(1).expect("i stays below the buffer count");
208    }
209    true
210}
211
212/// The compile-time buffer for `mint`, if it's one of the known mints.
213fn known_buffer(mint: &[u8; 32]) -> Option<&'static KnownBuffer> {
214    let index = KNOWN_BUFFER_SLOTS[slot(mint, KNOWN_BUFFER_MULTIPLIER)];
215    KNOWN_BUFFERS
216        .get(usize::from(index))
217        .filter(|known| &known.mint == mint)
218}
219
220/// Confirm `buffer` matches the derived buffer PDA for the mint bytes `mint`
221/// and `bump`, or is [`NATIVE_SOL_BUFFER_PDA`] if `mint` is
222/// [`ENCODED_NATIVE_SOL_TRANSFER`].
223///
224/// A known mint is checked against its compile-time buffer alone, ignoring
225/// `bump`: nothing signs with a buffer's seeds, so the bump only matters for
226/// re-deriving the address. That buffer is derived under [`crate::ID`] whatever
227/// `program_id` is, which is sound because the program only works there (see
228/// [`crate::pda::state::STATE_PDA`]).
229#[inline]
230#[must_use = "ignoring the output means ignoring the validation result"]
231pub fn validate_buffer_pda(
232    buffer: &Address,
233    mint: &[u8; 32],
234    bump: u8,
235) -> Result<(), ProgramError> {
236    let is_buffer = match known_buffer(mint) {
237        Some(known) => buffer.as_array() == &known.address,
238        None => {
239            is_pda_with_signer_seeds(buffer, &crate::ID, buffer_pda_signer_seeds(mint, &[bump]))
240        }
241    };
242    is_buffer
243        .then_some(())
244        .ok_or(SettlementError::PushSourceNotBuffer.into())
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250    use crate::pda::tests::assert_distinct_versions_yield_distinct_pdas;
251
252    #[test]
253    fn find_buffer_pda_uses_canonical_seeds() {
254        let token = Pubkey::new_unique();
255
256        crate::pda::tests::assert_canonical_bump(
257            |program_id| find_buffer_pda(program_id, &token),
258            buffer_pda_seeds(token.as_array()),
259        );
260    }
261
262    #[test]
263    fn distinct_versions_yield_distinct_buffer_pdas() {
264        let mint = Pubkey::new_unique();
265        let (pda, _) = find_buffer_pda(&crate::ID, &mint);
266
267        assert_distinct_versions_yield_distinct_pdas(&pda, &[mint.as_array(), BUFFER_SEED]);
268    }
269
270    #[test]
271    fn accepts_a_valid_address() {
272        let mint = Pubkey::new_unique();
273        let (pda, bump) = find_buffer_pda(&crate::ID, &mint);
274
275        let buffer = crate::instruction::fixtures::fake_account(pda);
276        validate_buffer_pda(buffer.address(), mint.as_array(), bump)
277            .expect("the canonical buffer PDA must be accepted");
278    }
279
280    #[test]
281    fn rejects_an_invalid_address() {
282        let mint = Pubkey::new_unique();
283        let (_, bump) = find_buffer_pda(&crate::ID, &mint);
284
285        // An account sitting at some other address is not the buffer.
286        let buffer = crate::instruction::fixtures::fake_account(Pubkey::new_unique());
287        let err = validate_buffer_pda(buffer.address(), mint.as_array(), bump)
288            .expect_err("a non-canonical address must be rejected");
289        assert_eq!(err, SettlementError::PushSourceNotBuffer.into());
290    }
291
292    #[test]
293    fn rejects_a_wrong_bump() {
294        let mint = Pubkey::new_unique();
295        let (pda, bump) = find_buffer_pda(&crate::ID, &mint);
296
297        // The address is canonical but the carried bump doesn't derive it.
298        let buffer = crate::instruction::fixtures::fake_account(pda);
299        let err = validate_buffer_pda(buffer.address(), mint.as_array(), bump ^ 1)
300            .expect_err("a wrong bump must be rejected");
301        assert_eq!(err, SettlementError::PushSourceNotBuffer.into());
302    }
303
304    const A_KNOWN_MINT: Pubkey = Pubkey::from_str_const(KNOWN_MINTS[0]);
305
306    #[test]
307    fn pinned_native_sol_buffer_pda_is_canonical() {
308        let (pda, bump) = find_native_sol_buffer_pda(&crate::ID);
309        assert_eq!(NATIVE_SOL_BUFFER_PDA_AND_BUMP, (*pda.as_array(), bump));
310        crate::pda::tests::assert_canonical_bump(
311            find_native_sol_buffer_pda,
312            NATIVE_SOL_BUFFER_PDA_SEEDS,
313        );
314    }
315
316    #[test]
317    fn distinct_versions_yield_distinct_native_sol_buffer_pdas() {
318        assert_distinct_versions_yield_distinct_pdas(
319            &NATIVE_SOL_BUFFER_PDA,
320            &[NATIVE_SOL_BUFFER_SEED],
321        );
322    }
323
324    #[test]
325    fn native_sol_buffer_is_not_the_native_markers_mint_buffer() {
326        let (marker_buffer, _) = find_buffer_pda(&crate::ID, &ENCODED_NATIVE_SOL_TRANSFER);
327        assert_ne!(NATIVE_SOL_BUFFER_PDA, marker_buffer);
328    }
329
330    #[test]
331    fn known_buffers_stores_canonical_native_sol_transfer() {
332        assert_eq!(
333            KNOWN_NATIVE_MINT_BUFFER.mint,
334            ENCODED_NATIVE_SOL_TRANSFER.to_bytes()
335        );
336        assert_eq!(
337            KNOWN_NATIVE_MINT_BUFFER.address,
338            NATIVE_SOL_BUFFER_PDA.to_bytes()
339        );
340    }
341
342    #[test]
343    fn known_mint_buffers_are_canonical() {
344        for known in KNOWN_MINT_BUFFERS {
345            let (pda, _) = find_buffer_pda(&crate::ID, &Pubkey::new_from_array(known.mint));
346            assert_eq!(known.address, *pda.as_array());
347        }
348    }
349
350    #[test]
351    fn every_known_mint_looks_up_its_own_buffer() {
352        for mint in KNOWN_MINTS {
353            let mint = Pubkey::from_str_const(mint);
354            let known = known_buffer(mint.as_array())
355                .unwrap_or_else(|| panic!("{mint} must have a known buffer"));
356            assert_eq!(known.mint, *mint.as_array());
357        }
358    }
359
360    #[test]
361    fn unknown_mint_sharing_a_slot_has_no_known_buffer() {
362        let mut mint = *A_KNOWN_MINT.as_array();
363        mint[31] ^= 1;
364
365        // ensure that the slot we will hit is still resolving to the original mint
366        assert_ne!(
367            KNOWN_BUFFER_SLOTS[slot(&mint, KNOWN_BUFFER_MULTIPLIER)],
368            u8::MAX
369        );
370
371        // even so it should resolve to none
372        assert!(known_buffer(&mint).is_none());
373    }
374
375    #[test]
376    fn unknown_mint_in_an_empty_slot_has_no_known_buffer() {
377        let mint = (0u64..)
378            .map(|seed| *crate::fixtures::pubkey_from_seed(&seed.to_string()).as_array())
379            .find(|mint| KNOWN_BUFFER_SLOTS[slot(mint, KNOWN_BUFFER_MULTIPLIER)] == u8::MAX)
380            .expect("most slots are empty");
381        assert!(known_buffer(&mint).is_none());
382    }
383
384    /// `find_slot_multiplier` runs in a const context, where this panic is a
385    /// compile error; calling it at runtime is the only way to observe it.
386    #[test]
387    #[should_panic(expected = "no multiplier gives every known mint its own slot")]
388    fn find_slot_multiplier_rejects_mints_sharing_their_leading_bytes() {
389        let mut other = *A_KNOWN_MINT.as_array();
390        other[31] ^= 1;
391        let _ = find_slot_multiplier(&[
392            KnownBuffer {
393                mint: *A_KNOWN_MINT.as_array(),
394                address: [0; 32],
395            },
396            KnownBuffer {
397                mint: other,
398                address: [0; 32],
399            },
400        ]);
401    }
402
403    #[test]
404    fn accepts_the_known_buffer() {
405        let (pda, bump) = find_buffer_pda(&crate::ID, &A_KNOWN_MINT);
406
407        validate_buffer_pda(&pda, A_KNOWN_MINT.as_array(), bump)
408            .expect("the known buffer PDA must be accepted");
409    }
410
411    #[test]
412    fn rejects_an_invalid_address_for_a_known_mint() {
413        let (_, bump) = find_buffer_pda(&crate::ID, &A_KNOWN_MINT);
414
415        let err = validate_buffer_pda(&Pubkey::new_unique(), A_KNOWN_MINT.as_array(), bump)
416            .expect_err("a non-canonical address must be rejected");
417        assert_eq!(err, SettlementError::PushSourceNotBuffer.into());
418    }
419
420    #[test]
421    fn accepts_the_native_sol_buffer() {
422        validate_buffer_pda(
423            &NATIVE_SOL_BUFFER_PDA,
424            ENCODED_NATIVE_SOL_TRANSFER.as_array(),
425            NATIVE_SOL_BUFFER_PDA_AND_BUMP.1,
426        )
427        .expect("the native SOL buffer must be accepted");
428    }
429
430    #[test]
431    fn rejects_the_native_markers_mint_buffer_as_the_native_sol_buffer() {
432        let (pda, bump) = find_buffer_pda(&crate::ID, &ENCODED_NATIVE_SOL_TRANSFER);
433
434        let err = validate_buffer_pda(&pda, ENCODED_NATIVE_SOL_TRANSFER.as_array(), bump)
435            .expect_err("only the native SOL buffer holds native SOL");
436        assert_eq!(err, SettlementError::PushSourceNotBuffer.into());
437    }
438
439    #[test]
440    fn ignores_the_bump_for_a_known_mint() {
441        let (pda, bump) = find_buffer_pda(&crate::ID, &A_KNOWN_MINT);
442
443        validate_buffer_pda(&pda, A_KNOWN_MINT.as_array(), bump ^ 1)
444            .expect("the known buffer PDA must be accepted whatever the bump");
445    }
446
447    mod proptest {
448        use ::proptest::prelude::*;
449
450        use super::*;
451
452        proptest! {
453            #[test]
454            fn distinct_tokens_yield_distinct_pdas(
455                program_id in any::<[u8; 32]>(),
456                token1 in any::<[u8; 32]>(),
457                token2 in any::<[u8; 32]>(),
458            ) {
459                prop_assume!(token1 != token2);
460                let program_id = Pubkey::new_from_array(program_id);
461                let (pda1, _) = find_buffer_pda(&program_id, &Pubkey::new_from_array(token1));
462                let (pda2, _) = find_buffer_pda(&program_id, &Pubkey::new_from_array(token2));
463                prop_assert_ne!(pda1, pda2);
464            }
465        }
466    }
467}