Skip to main content

csv_adapter_core/
genesis.rs

1//! Genesis: the initial state of a CSV contract
2//!
3//! Genesis represents the first instantiation of a contract. It defines
4//! the global state and assigns initial owned states to their seals.
5//! Every consignment chain starts from exactly one genesis.
6
7use alloc::vec::Vec;
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10
11use crate::hash::Hash;
12use crate::state::{GlobalState, Metadata, OwnedState};
13
14/// Contract genesis
15///
16/// The genesis is the root of every contract's state history.
17/// It is referenced by the first transition and indirectly by all subsequent ones.
18#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
19pub struct Genesis {
20    /// Unique contract identifier (user-facing, e.g., "USDT-on-Bitcoin:1")
21    pub contract_id: Hash,
22    /// Schema identifier binding this genesis to a contract schema
23    pub schema_id: Hash,
24    /// Initial global state values
25    pub global_state: Vec<GlobalState>,
26    /// Initial owned state assignments (e.g., initial token distribution)
27    pub owned_state: Vec<OwnedState>,
28    /// Genesis metadata (issuance date, issuer info, etc.)
29    pub metadata: Vec<Metadata>,
30}
31
32impl Genesis {
33    /// Create new genesis
34    pub fn new(
35        contract_id: Hash,
36        schema_id: Hash,
37        global_state: Vec<GlobalState>,
38        owned_state: Vec<OwnedState>,
39        metadata: Vec<Metadata>,
40    ) -> Self {
41        Self {
42            contract_id,
43            schema_id,
44            global_state,
45            owned_state,
46            metadata,
47        }
48    }
49
50    /// Compute the genesis hash
51    ///
52    /// This hash serves as the root commitment for all subsequent transitions.
53    pub fn hash(&self) -> Hash {
54        let mut hasher = Sha256::new();
55
56        // Domain separator for genesis
57        hasher.update(b"CSV-GENESIS-v1");
58
59        // Contract and schema IDs
60        hasher.update(self.contract_id.as_bytes());
61        hasher.update(self.schema_id.as_bytes());
62
63        // Global state: count + each (type_id || data)
64        hasher.update((self.global_state.len() as u64).to_le_bytes());
65        for state in &self.global_state {
66            hasher.update(state.type_id.to_le_bytes());
67            hasher.update(&state.data);
68        }
69
70        // Owned state: count + each (type_id || seal || data)
71        hasher.update((self.owned_state.len() as u64).to_le_bytes());
72        for state in &self.owned_state {
73            hasher.update(state.type_id.to_le_bytes());
74            hasher.update(state.seal.to_vec());
75            hasher.update(&state.data);
76        }
77
78        // Metadata: count + each (key || value)
79        hasher.update((self.metadata.len() as u64).to_le_bytes());
80        for meta in &self.metadata {
81            hasher.update(meta.key.as_bytes());
82            hasher.update(&meta.value);
83        }
84
85        let result = hasher.finalize();
86        let mut array = [0u8; 32];
87        array.copy_from_slice(&result);
88        Hash::new(array)
89    }
90
91    /// Get the total count of all state items
92    pub fn state_count(&self) -> usize {
93        self.global_state.len() + self.owned_state.len()
94    }
95
96    /// Find global states by type ID
97    pub fn global_states_of(&self, type_id: u16) -> Vec<&GlobalState> {
98        self.global_state
99            .iter()
100            .filter(|s| s.type_id == type_id)
101            .collect()
102    }
103
104    /// Find owned states by type ID
105    pub fn owned_states_of(&self, type_id: u16) -> Vec<&OwnedState> {
106        self.owned_state
107            .iter()
108            .filter(|s| s.type_id == type_id)
109            .collect()
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::seal::SealRef;
117
118    fn test_genesis() -> Genesis {
119        Genesis::new(
120            Hash::new([1u8; 32]),
121            Hash::new([2u8; 32]),
122            vec![
123                GlobalState::new(1, vec![100, 200]), // e.g., total_supply
124                GlobalState::from_hash(2, Hash::new([3u8; 32])), // e.g., config_hash
125            ],
126            vec![
127                OwnedState::new(
128                    10,
129                    SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
130                    1000u64.to_le_bytes().to_vec(),
131                ), // 1000 tokens to seal 1
132                OwnedState::new(
133                    10,
134                    SealRef::new(vec![0xBB; 16], Some(2)).unwrap(),
135                    500u64.to_le_bytes().to_vec(),
136                ), // 500 tokens to seal 2
137            ],
138            vec![
139                Metadata::from_string("issuer", "test-issuer"),
140                Metadata::from_string("date", "2026-04-06"),
141            ],
142        )
143    }
144
145    #[test]
146    fn test_genesis_creation() {
147        let genesis = test_genesis();
148        assert_eq!(genesis.contract_id, Hash::new([1u8; 32]));
149        assert_eq!(genesis.schema_id, Hash::new([2u8; 32]));
150        assert_eq!(genesis.global_state.len(), 2);
151        assert_eq!(genesis.owned_state.len(), 2);
152        assert_eq!(genesis.metadata.len(), 2);
153    }
154
155    #[test]
156    fn test_genesis_hash() {
157        let genesis = test_genesis();
158        let hash = genesis.hash();
159        assert_eq!(hash.as_bytes().len(), 32);
160    }
161
162    #[test]
163    fn test_genesis_hash_deterministic() {
164        let g1 = test_genesis();
165        let g2 = test_genesis();
166        assert_eq!(g1.hash(), g2.hash());
167    }
168
169    #[test]
170    fn test_genesis_hash_differs_by_contract_id() {
171        let mut g = test_genesis();
172        let original_hash = g.hash();
173        g.contract_id = Hash::new([99u8; 32]);
174        assert_ne!(g.hash(), original_hash);
175    }
176
177    #[test]
178    fn test_genesis_hash_differs_by_global_state() {
179        let mut g = test_genesis();
180        let original_hash = g.hash();
181        g.global_state.push(GlobalState::new(99, vec![1, 2, 3]));
182        assert_ne!(g.hash(), original_hash);
183    }
184
185    #[test]
186    fn test_genesis_hash_differs_by_owned_state() {
187        let mut g = test_genesis();
188        let original_hash = g.hash();
189        g.owned_state.push(OwnedState::new(
190            99,
191            SealRef::new(vec![0xCC; 16], Some(3)).unwrap(),
192            vec![42],
193        ));
194        assert_ne!(g.hash(), original_hash);
195    }
196
197    #[test]
198    fn test_genesis_hash_differs_by_metadata() {
199        let mut g = test_genesis();
200        let original_hash = g.hash();
201        g.metadata.push(Metadata::from_string("extra", "data"));
202        assert_ne!(g.hash(), original_hash);
203    }
204
205    #[test]
206    fn test_genesis_state_count() {
207        let genesis = test_genesis();
208        assert_eq!(genesis.state_count(), 4); // 2 global + 2 owned
209    }
210
211    #[test]
212    fn test_genesis_global_states_of() {
213        let genesis = test_genesis();
214        let states = genesis.global_states_of(1);
215        assert_eq!(states.len(), 1);
216        assert_eq!(states[0].type_id, 1);
217
218        let none = genesis.global_states_of(99);
219        assert!(none.is_empty());
220    }
221
222    #[test]
223    fn test_genesis_owned_states_of() {
224        let genesis = test_genesis();
225        let states = genesis.owned_states_of(10);
226        assert_eq!(states.len(), 2);
227
228        let none = genesis.owned_states_of(99);
229        assert!(none.is_empty());
230    }
231
232    #[test]
233    fn test_genesis_empty() {
234        let genesis = Genesis::new(
235            Hash::new([1u8; 32]),
236            Hash::new([2u8; 32]),
237            vec![],
238            vec![],
239            vec![],
240        );
241        assert_eq!(genesis.state_count(), 0);
242        assert!(genesis.global_states_of(1).is_empty());
243        assert!(genesis.owned_states_of(1).is_empty());
244
245        // Still produces a valid hash
246        assert_eq!(genesis.hash().as_bytes().len(), 32);
247    }
248
249    #[test]
250    fn test_genesis_serialization_roundtrip() {
251        let genesis = test_genesis();
252        let bytes = bincode::serialize(&genesis).unwrap();
253        let restored: Genesis = bincode::deserialize(&bytes).unwrap();
254        assert_eq!(genesis, restored);
255        assert_eq!(genesis.hash(), restored.hash());
256    }
257}