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 the protocol's authority
4//! configuration: the `manager` and the `reclaim_authority` accounts (see
5//! [`StateAccount`]).
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 add and remove solvers, as well as reassign
20    /// all the roles in the settlement program.
21    pub manager: Pubkey,
22    /// The account authorized to reclaim rent for buffers. It must sign the
23    /// reclaim and chooses where the rent goes.
24    pub reclaim_authority: Pubkey,
25}
26
27/// Canonical 65-byte representation of a [`StateAccount`]: the discriminator
28/// byte followed by `manager`'s and `reclaim_authority`'s bytes.
29///
30/// ```text
31///  ┌──── discriminator
32///  ┌┬───────────────────────────────┬───────────────────────────────┐
33///  ││            manager            │       reclaim_authority       │
34///  └┴───────────────────────────────┴───────────────────────────────┘
35/// 0 1                               33                              65
36/// ```
37#[derive(Clone, Debug, Deref, Eq, PartialEq)]
38pub struct EncodedStateAccount([u8; Self::SIZE]);
39
40impl EncodedStateAccount {
41    // Per-field widths, derived from the `StateAccount` field types.
42    const W_DISCRIMINATOR: usize = size_of::<u8>();
43    const W_MANAGER: usize = size_of::<Pubkey>();
44    const W_RECLAIM_AUTHORITY: usize = size_of::<Pubkey>();
45
46    pub const SIZE: usize = 65;
47
48    /// Single-byte account discriminator. See [`crate::SettlementAccount`].
49    pub const DISCRIMINATOR: u8 = SettlementAccount::SettlementState.discriminator();
50}
51
52/// Writes the canonical [`EncodedStateAccount`] encoding of `account` into
53/// `buffer`.
54pub fn write_account(buffer: &mut [u8; EncodedStateAccount::SIZE], account: &StateAccount) {
55    let StateAccount {
56        manager,
57        reclaim_authority,
58    } = account;
59    let (discriminator_slot, manager_slot, reclaim_authority_slot) = mut_array_refs![
60        buffer,
61        EncodedStateAccount::W_DISCRIMINATOR,
62        EncodedStateAccount::W_MANAGER,
63        EncodedStateAccount::W_RECLAIM_AUTHORITY
64    ];
65    *discriminator_slot = [EncodedStateAccount::DISCRIMINATOR];
66    *manager_slot = manager.to_bytes();
67    *reclaim_authority_slot = reclaim_authority.to_bytes();
68}
69
70impl From<EncodedStateAccount> for [u8; EncodedStateAccount::SIZE] {
71    fn from(encoded: EncodedStateAccount) -> Self {
72        encoded.0
73    }
74}
75
76impl From<StateAccount> for EncodedStateAccount {
77    fn from(account: StateAccount) -> Self {
78        let mut out = [0u8; Self::SIZE];
79        write_account(&mut out, &account);
80        Self(out)
81    }
82}
83
84impl From<StateAccount> for [u8; EncodedStateAccount::SIZE] {
85    fn from(account: StateAccount) -> Self {
86        EncodedStateAccount::from(account).into()
87    }
88}
89
90impl TryFrom<[u8; EncodedStateAccount::SIZE]> for StateAccount {
91    type Error = ProgramError;
92
93    fn try_from(bytes: [u8; EncodedStateAccount::SIZE]) -> Result<Self, Self::Error> {
94        let (discriminator, manager, reclaim_authority) = array_refs![
95            &bytes,
96            EncodedStateAccount::W_DISCRIMINATOR,
97            EncodedStateAccount::W_MANAGER,
98            EncodedStateAccount::W_RECLAIM_AUTHORITY
99        ];
100
101        if *discriminator != [EncodedStateAccount::DISCRIMINATOR] {
102            return Err(ProgramError::InvalidAccountData);
103        }
104
105        Ok(StateAccount {
106            manager: Pubkey::new_from_array(*manager),
107            reclaim_authority: Pubkey::new_from_array(*reclaim_authority),
108        })
109    }
110}
111
112impl TryFrom<EncodedStateAccount> for StateAccount {
113    type Error = ProgramError;
114
115    fn try_from(encoded: EncodedStateAccount) -> Result<Self, Self::Error> {
116        StateAccount::try_from(encoded.0)
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crate::tests::pubkey_from_seed;
124
125    /// Byte offset of the account discriminator within the encoding.
126    const DISCRIMINATOR_OFFSET: usize = 0;
127
128    fn sample_account() -> StateAccount {
129        StateAccount {
130            manager: pubkey_from_seed("sample_account's manager"),
131            reclaim_authority: pubkey_from_seed("sample_account's reclaim_authority"),
132        }
133    }
134
135    #[test]
136    fn decode_rejects_wrong_discriminator() {
137        let mut bytes: [u8; EncodedStateAccount::SIZE] =
138            EncodedStateAccount::from(sample_account()).into();
139        bytes[0] ^= 0xff;
140        let err = StateAccount::try_from(bytes).expect_err("wrong discriminator must be rejected");
141        assert_eq!(err, ProgramError::InvalidAccountData);
142    }
143
144    #[test]
145    fn direct_write_account_matches_state_account_encoding() {
146        let account = sample_account();
147        let mut buffer = [0u8; EncodedStateAccount::SIZE];
148        write_account(&mut buffer, &account);
149        let direct = EncodedStateAccount(buffer);
150        let via_state_account = EncodedStateAccount::from(account);
151        assert_eq!(direct, via_state_account);
152    }
153
154    mod proptest {
155        use ::proptest::prelude::*;
156
157        use super::*;
158
159        proptest! {
160            #[test]
161            fn account_encode_roundtrip(
162                reclaim_authority in any::<[u8; 32]>(),
163                manager in any::<[u8; 32]>(),
164            ) {
165                let account = StateAccount {
166                    reclaim_authority: Pubkey::new_from_array(reclaim_authority),
167                    manager: Pubkey::new_from_array(manager),
168                };
169                let encoded = EncodedStateAccount::from(account.clone());
170                let decoded = StateAccount::try_from(encoded).expect("should decode after encoding");
171                prop_assert_eq!(decoded, account);
172            }
173
174            #[test]
175            fn account_decode_roundtrip(
176                mut bytes in any::<[u8; EncodedStateAccount::SIZE]>(),
177            ) {
178                bytes[DISCRIMINATOR_OFFSET] = EncodedStateAccount::DISCRIMINATOR;
179                let encoded = EncodedStateAccount(bytes);
180
181                let decoded = StateAccount::try_from(encoded.clone()).expect("should decode from valid bytes");
182                let re_encoded = EncodedStateAccount::from(decoded);
183
184                prop_assert_eq!(re_encoded, encoded);
185            }
186        }
187    }
188}