Skip to main content

csv_adapter_core/
state.rs

1//! Typed state enums for CSV contracts
2//!
3//! These types define the structured state that contracts operate on.
4//! Unlike opaque byte vectors, typed state enables schema validation
5//! and consumer-friendly decoding.
6
7use alloc::string::String;
8use alloc::vec::Vec;
9use serde::{Deserialize, Serialize};
10
11use crate::hash::Hash;
12use crate::seal::SealRef;
13
14/// Unique identifier for a state type within a schema
15pub type StateTypeId = u16;
16
17/// Global state: contract-wide values visible to all parties
18///
19/// Examples: total supply, epoch number, configuration flags.
20/// Global state is not tied to any specific seal or owner.
21#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
22pub struct GlobalState {
23    /// Type identifier (defined in the schema)
24    pub type_id: StateTypeId,
25    /// Serialized state data (schema-defined format)
26    pub data: Vec<u8>,
27}
28
29impl GlobalState {
30    /// Create new global state
31    pub fn new(type_id: StateTypeId, data: Vec<u8>) -> Self {
32        Self { type_id, data }
33    }
34
35    /// Create global state from a single hash value
36    pub fn from_hash(type_id: StateTypeId, value: Hash) -> Self {
37        Self {
38            type_id,
39            data: value.to_vec(),
40        }
41    }
42
43    /// Get the state as a hash (convenience for 32-byte states)
44    pub fn as_hash(&self) -> Option<Hash> {
45        if self.data.len() == 32 {
46            let mut bytes = [0u8; 32];
47            bytes.copy_from_slice(&self.data);
48            Some(Hash::new(bytes))
49        } else {
50            None
51        }
52    }
53}
54
55/// Owned state: state bound to a specific single-use seal
56///
57/// Examples: token ownership, NFT assignment, escrow position.
58/// Only the seal owner can consume or transfer this state.
59#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
60pub struct OwnedState {
61    /// Type identifier (defined in the schema)
62    pub type_id: StateTypeId,
63    /// The seal that owns this state
64    pub seal: SealRef,
65    /// Serialized state data (schema-defined format)
66    pub data: Vec<u8>,
67}
68
69impl OwnedState {
70    /// Create new owned state
71    pub fn new(type_id: StateTypeId, seal: SealRef, data: Vec<u8>) -> Self {
72        Self {
73            type_id,
74            seal,
75            data,
76        }
77    }
78
79    /// Create owned state from a single hash value
80    pub fn from_hash(type_id: StateTypeId, seal: SealRef, value: Hash) -> Self {
81        Self {
82            type_id,
83            seal,
84            data: value.to_vec(),
85        }
86    }
87
88    /// Get the state as a hash (convenience for 32-byte states)
89    pub fn as_hash(&self) -> Option<Hash> {
90        if self.data.len() == 32 {
91            let mut bytes = [0u8; 32];
92            bytes.copy_from_slice(&self.data);
93            Some(Hash::new(bytes))
94        } else {
95            None
96        }
97    }
98}
99
100/// Metadata: auxiliary data attached to state transitions
101///
102/// Examples: timestamps, annotations, oracle feeds.
103/// Metadata is not validated by the VM and is opaque to consensus.
104#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
105pub struct Metadata {
106    /// Metadata key (human-readable identifier)
107    pub key: String,
108    /// Serialized metadata value
109    pub value: Vec<u8>,
110}
111
112impl Metadata {
113    /// Create new metadata
114    pub fn new(key: impl Into<String>, value: Vec<u8>) -> Self {
115        Self {
116            key: key.into(),
117            value,
118        }
119    }
120
121    /// Create metadata from a string value
122    pub fn from_string(key: impl Into<String>, value: impl Into<String>) -> Self {
123        Self {
124            key: key.into(),
125            value: value.into().into_bytes(),
126        }
127    }
128
129    /// Try to decode value as UTF-8 string
130    pub fn as_string(&self) -> Option<String> {
131        String::from_utf8(self.value.clone()).ok()
132    }
133}
134
135/// State assignment: specifies which seal receives which state
136///
137/// Used in transitions to declare new owned state outputs.
138#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
139pub struct StateAssignment {
140    /// Type of state being assigned
141    pub type_id: StateTypeId,
142    /// Seal that will own this state
143    pub seal: SealRef,
144    /// State data
145    pub data: Vec<u8>,
146}
147
148impl StateAssignment {
149    /// Create new state assignment
150    pub fn new(type_id: StateTypeId, seal: SealRef, data: Vec<u8>) -> Self {
151        Self {
152            type_id,
153            seal,
154            data,
155        }
156    }
157}
158
159/// State reference: identifies existing state to consume
160///
161/// Used in transitions to declare owned state inputs.
162#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
163pub struct StateRef {
164    /// Type of state being referenced
165    pub type_id: StateTypeId,
166    /// Commitment hash that created this state
167    pub commitment: Hash,
168    /// Index within the commitment's outputs
169    pub output_index: u32,
170}
171
172impl StateRef {
173    /// Create new state reference
174    pub fn new(type_id: StateTypeId, commitment: Hash, output_index: u32) -> Self {
175        Self {
176            type_id,
177            commitment,
178            output_index,
179        }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_global_state_creation() {
189        let state = GlobalState::new(1, vec![1, 2, 3]);
190        assert_eq!(state.type_id, 1);
191        assert_eq!(state.data, vec![1, 2, 3]);
192    }
193
194    #[test]
195    fn test_global_state_from_hash() {
196        let hash = Hash::new([42u8; 32]);
197        let state = GlobalState::from_hash(1, hash);
198        assert_eq!(state.type_id, 1);
199        assert_eq!(state.as_hash(), Some(hash));
200    }
201
202    #[test]
203    fn test_global_state_wrong_size() {
204        let state = GlobalState::new(1, vec![1, 2]); // Not 32 bytes
205        assert!(state.as_hash().is_none());
206    }
207
208    #[test]
209    fn test_owned_state_creation() {
210        let seal = SealRef::new(vec![1, 2, 3], Some(42)).unwrap();
211        let state = OwnedState::new(2, seal.clone(), vec![4, 5, 6]);
212        assert_eq!(state.type_id, 2);
213        assert_eq!(state.seal, seal);
214        assert_eq!(state.data, vec![4, 5, 6]);
215    }
216
217    #[test]
218    fn test_owned_state_from_hash() {
219        let seal = SealRef::new(vec![1, 2, 3], Some(42)).unwrap();
220        let hash = Hash::new([99u8; 32]);
221        let state = OwnedState::from_hash(2, seal.clone(), hash);
222        assert_eq!(state.seal, seal);
223        assert_eq!(state.as_hash(), Some(hash));
224    }
225
226    #[test]
227    fn test_metadata_creation() {
228        let meta = Metadata::new("timestamp", 1700000000u64.to_le_bytes().to_vec());
229        assert_eq!(meta.key, "timestamp");
230    }
231
232    #[test]
233    fn test_metadata_from_string() {
234        let meta = Metadata::from_string("note", "hello world");
235        assert_eq!(meta.as_string(), Some("hello world".to_string()));
236    }
237
238    #[test]
239    fn test_metadata_binary() {
240        let meta = Metadata::new("binary", vec![0x00, 0xFF, 0x80]);
241        assert!(meta.as_string().is_none()); // Not valid UTF-8
242    }
243
244    #[test]
245    fn test_state_assignment() {
246        let seal = SealRef::new(vec![1, 2, 3], Some(42)).unwrap();
247        let assignment = StateAssignment::new(3, seal.clone(), vec![7, 8, 9]);
248        assert_eq!(assignment.type_id, 3);
249        assert_eq!(assignment.seal, seal);
250    }
251
252    #[test]
253    fn test_state_ref() {
254        let state_ref = StateRef::new(1, Hash::new([5u8; 32]), 0);
255        assert_eq!(state_ref.type_id, 1);
256        assert_eq!(state_ref.output_index, 0);
257    }
258
259    #[test]
260    fn test_state_serialization_roundtrip() {
261        let seal = SealRef::new(vec![1, 2, 3], Some(42)).unwrap();
262        let state = OwnedState::new(2, seal, vec![4, 5, 6]);
263        let bytes = bincode::serialize(&state).unwrap();
264        let restored: OwnedState = bincode::deserialize(&bytes).unwrap();
265        assert_eq!(state, restored);
266    }
267
268    #[test]
269    fn test_global_state_serialization_roundtrip() {
270        let state = GlobalState::new(1, vec![1, 2, 3]);
271        let bytes = bincode::serialize(&state).unwrap();
272        let restored: GlobalState = bincode::deserialize(&bytes).unwrap();
273        assert_eq!(state, restored);
274    }
275
276    #[test]
277    fn test_metadata_serialization_roundtrip() {
278        let meta = Metadata::from_string("note", "test value");
279        let bytes = bincode::serialize(&meta).unwrap();
280        let restored: Metadata = bincode::deserialize(&bytes).unwrap();
281        assert_eq!(meta, restored);
282    }
283}