Skip to main content

alea_verifier/
state.rs

1use anchor_lang::prelude::*;
2
3/// drand evmnet configuration account.
4///
5/// Single PDA at `["config"]`. Stores the evmnet chain parameters
6/// needed to verify BLS signatures on-chain. Schema is part of the
7/// v1 CPI interface and frozen per ADR 0028 — exact field order and
8/// sizes must not change across program upgrades.
9#[account]
10pub struct Config {
11    /// drand evmnet G2 public key (uncompressed, Kyber byte ordering:
12    /// `x_c1 || x_c0 || y_c1 || y_c0`, each 32 BE bytes).
13    pub pubkey_g2: [u8; 128],
14
15    /// Genesis timestamp of the evmnet chain (Unix seconds).
16    pub genesis_time: u64,
17
18    /// Round period in seconds.
19    pub period: u64,
20
21    /// evmnet chain hash (identifies which drand chain this config points at).
22    pub chain_hash: [u8; 32],
23
24    /// Authority that can call `update_config`. `has_one` in the
25    /// `UpdateConfig` Accounts struct enforces the match.
26    pub authority: Pubkey,
27
28    /// Canonical PDA bump stored at init time. `verify` and
29    /// `update_config` use `bump = config.bump` to skip re-derivation
30    /// (~10K CU saving).
31    pub bump: u8,
32}
33
34impl Config {
35    /// Exact account size: 8 (Anchor discriminator) + 128 + 8 + 8 + 32
36    /// + 32 + 1 = 217 bytes. Per `program/spec.md §"Account Schema"`.
37    pub const LEN: usize = 8 + 128 + 8 + 8 + 32 + 32 + 1;
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use anchor_lang::{AnchorDeserialize, AnchorSerialize};
44
45    #[test]
46    fn config_len_is_217_bytes() {
47        assert_eq!(
48            Config::LEN,
49            217,
50            "Config::LEN must equal spec.md §Account Schema (217 bytes)"
51        );
52    }
53
54    // P08-T3-03 (Phase 2.5 Wave I, Bucket A) — Borsh serialization round-trip.
55    // Schema is frozen per ADR 0028; this test pins the exact field layout
56    // (order, sizes) and byte length so any accidental reordering or type
57    // change surfaces immediately at `cargo test`.
58    #[test]
59    fn config_borsh_roundtrip_pins_v1_schema() {
60        let original = Config {
61            pubkey_g2: [0xAB; 128],
62            genesis_time: 0x1122_3344_5566_7788,
63            period: 3,
64            chain_hash: [0xCD; 32],
65            authority: Pubkey::new_unique(),
66            bump: 255,
67        };
68
69        let bytes = original.try_to_vec().expect("serialize must succeed");
70        // Expected payload = 128 + 8 + 8 + 32 + 32 + 1 = 209 bytes (Config::LEN
71        // minus the 8-byte Anchor discriminator that prefixes on-chain accounts
72        // but not the raw Borsh-serialized struct).
73        assert_eq!(
74            bytes.len(),
75            Config::LEN - 8,
76            "Borsh-serialized Config must be exactly 209 bytes (Config::LEN - 8 discriminator)",
77        );
78
79        // Byte-layout sanity: Borsh writes fields in declaration order, so the
80        // first 128 bytes MUST equal pubkey_g2, the next 8 bytes MUST be
81        // genesis_time (little-endian u64), etc.
82        assert_eq!(
83            &bytes[0..128],
84            &[0xAB; 128],
85            "pubkey_g2 must be first field"
86        );
87        assert_eq!(
88            &bytes[128..136],
89            &0x1122_3344_5566_7788u64.to_le_bytes(),
90            "genesis_time must follow pubkey_g2 as LE u64"
91        );
92        assert_eq!(
93            &bytes[136..144],
94            &3u64.to_le_bytes(),
95            "period must follow genesis_time as LE u64"
96        );
97        assert_eq!(
98            &bytes[144..176],
99            &[0xCD; 32],
100            "chain_hash must follow period"
101        );
102        // authority (Pubkey) at 176..208; bump at 208.
103        assert_eq!(bytes[208], 255, "bump must be the last byte");
104
105        let recovered = Config::try_from_slice(&bytes).expect("deserialize must succeed");
106        assert_eq!(recovered.pubkey_g2, original.pubkey_g2);
107        assert_eq!(recovered.genesis_time, original.genesis_time);
108        assert_eq!(recovered.period, original.period);
109        assert_eq!(recovered.chain_hash, original.chain_hash);
110        assert_eq!(recovered.authority, original.authority);
111        assert_eq!(recovered.bump, original.bump);
112    }
113}