Skip to main content

cow_settlement_interface/data/
state.rs

1//! Settlement state PDA body and its canonical byte representation.
2//!
3//! The state PDA (see [`crate::pda::state`]) stores a single piece of
4//! protocol configuration: the `reclaim_authority` account (see
5//! [`StateAccount::reclaim_authority`]).
6
7use core::mem::size_of;
8
9use arrayref::{array_refs, mut_array_refs};
10use derive_more::Deref;
11use solana_program_error::ProgramError;
12use solana_pubkey::Pubkey;
13
14use crate::SettlementAccount;
15
16/// Idiomatic representation of the state PDA's body.
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct StateAccount {
19    /// The account authorized to reclaim rent for buffers. It must sign the
20    /// reclaim and chooses where the rent goes.
21    pub reclaim_authority: Pubkey,
22}
23
24/// Canonical 33-byte representation of a [`StateAccount`]: the discriminator
25/// byte followed by `reclaim_authority`'s bytes.
26///
27/// ```text
28///  ┌──── discriminator
29///  ┌┬───────────────────────────────┐
30///  ││       reclaim_authority       │
31///  └┴───────────────────────────────┘
32/// 0 1                               33
33/// ```
34#[derive(Clone, Debug, Deref, Eq, PartialEq)]
35pub struct EncodedStateAccount([u8; Self::SIZE]);
36
37impl EncodedStateAccount {
38    // Per-field widths, derived from the `StateAccount` field types.
39    const W_DISCRIMINATOR: usize = size_of::<u8>();
40    const W_RECLAIM_AUTHORITY: usize = size_of::<Pubkey>();
41
42    pub const SIZE: usize = 33;
43
44    /// Single-byte account discriminator. See [`crate::SettlementAccount`].
45    pub const DISCRIMINATOR: u8 = SettlementAccount::SettlementState.discriminator();
46}
47
48/// Writes the canonical [`EncodedStateAccount`] encoding of the given fields
49/// into `buffer`.
50pub fn write_account(buffer: &mut [u8; EncodedStateAccount::SIZE], reclaim_authority: &Pubkey) {
51    let (discriminator_slot, reclaim_authority_slot) = mut_array_refs![
52        buffer,
53        EncodedStateAccount::W_DISCRIMINATOR,
54        EncodedStateAccount::W_RECLAIM_AUTHORITY
55    ];
56    *discriminator_slot = [EncodedStateAccount::DISCRIMINATOR];
57    *reclaim_authority_slot = reclaim_authority.to_bytes();
58}
59
60impl From<EncodedStateAccount> for [u8; EncodedStateAccount::SIZE] {
61    fn from(encoded: EncodedStateAccount) -> Self {
62        encoded.0
63    }
64}
65
66impl From<StateAccount> for EncodedStateAccount {
67    fn from(account: StateAccount) -> Self {
68        let mut out = [0u8; Self::SIZE];
69        write_account(&mut out, &account.reclaim_authority);
70        Self(out)
71    }
72}
73
74impl TryFrom<[u8; EncodedStateAccount::SIZE]> for StateAccount {
75    type Error = ProgramError;
76
77    fn try_from(bytes: [u8; EncodedStateAccount::SIZE]) -> Result<Self, Self::Error> {
78        let (discriminator, reclaim_authority) = array_refs![
79            &bytes,
80            EncodedStateAccount::W_DISCRIMINATOR,
81            EncodedStateAccount::W_RECLAIM_AUTHORITY
82        ];
83
84        if *discriminator != [EncodedStateAccount::DISCRIMINATOR] {
85            return Err(ProgramError::InvalidAccountData);
86        }
87
88        Ok(StateAccount {
89            reclaim_authority: Pubkey::new_from_array(*reclaim_authority),
90        })
91    }
92}
93
94impl TryFrom<EncodedStateAccount> for StateAccount {
95    type Error = ProgramError;
96
97    fn try_from(encoded: EncodedStateAccount) -> Result<Self, Self::Error> {
98        StateAccount::try_from(encoded.0)
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    fn sample_account() -> StateAccount {
107        StateAccount {
108            reclaim_authority: Pubkey::new_from_array([0x42; 32]),
109        }
110    }
111
112    #[test]
113    fn decode_rejects_wrong_discriminator() {
114        let mut bytes: [u8; EncodedStateAccount::SIZE] =
115            EncodedStateAccount::from(sample_account()).into();
116        bytes[0] ^= 0xff;
117        let err = StateAccount::try_from(bytes).expect_err("wrong discriminator must be rejected");
118        assert_eq!(err, ProgramError::InvalidAccountData);
119    }
120
121    #[test]
122    fn direct_write_account_matches_state_account_encoding() {
123        let account = sample_account();
124        let mut buffer = [0u8; EncodedStateAccount::SIZE];
125        write_account(&mut buffer, &account.reclaim_authority);
126        let direct = EncodedStateAccount(buffer);
127        let via_state_account = EncodedStateAccount::from(account);
128        assert_eq!(direct, via_state_account);
129    }
130
131    mod proptest {
132        use ::proptest::prelude::*;
133
134        use super::*;
135
136        proptest! {
137            #[test]
138            fn account_encode_roundtrip(bytes in any::<[u8; 32]>()) {
139                let account = StateAccount { reclaim_authority: Pubkey::new_from_array(bytes) };
140                let encoded = EncodedStateAccount::from(account.clone());
141                let decoded = StateAccount::try_from(encoded).expect("should decode after encoding");
142                prop_assert_eq!(decoded, account);
143            }
144
145            #[test]
146            fn account_decode_roundtrip(bytes in any::<[u8; 32]>()) {
147                let mut encoded = [0u8; EncodedStateAccount::SIZE];
148                encoded[0] = EncodedStateAccount::DISCRIMINATOR;
149                encoded[1..].copy_from_slice(&bytes);
150                let encoded = EncodedStateAccount(encoded);
151
152                let decoded = StateAccount::try_from(encoded.clone()).expect("should decode from valid bytes");
153                let re_encoded = EncodedStateAccount::from(decoded);
154
155                prop_assert_eq!(re_encoded, encoded);
156            }
157        }
158    }
159}