1use alloc::string::String;
8use alloc::vec::Vec;
9use serde::{Deserialize, Serialize};
10use sha2::{Digest, Sha256};
11
12use crate::hash::Hash;
13use crate::state::StateTypeId;
14use crate::transition::Transition;
15
16#[derive(Debug)]
18#[allow(missing_docs)]
19pub enum SchemaError {
20 TypeNotFound { type_id: StateTypeId },
22 TransitionNotFound { transition_id: u16 },
24 InputTypeMismatch {
26 transition_id: u16,
27 expected: Vec<StateTypeId>,
28 actual: Vec<StateTypeId>,
29 },
30 OutputTypeMismatch {
32 transition_id: u16,
33 expected: Vec<StateTypeId>,
34 actual: Vec<StateTypeId>,
35 },
36}
37
38impl core::fmt::Display for SchemaError {
39 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40 match self {
41 SchemaError::TypeNotFound { type_id } => {
42 write!(f, "Type ID {} not found in schema", type_id)
43 }
44 SchemaError::TransitionNotFound { transition_id } => {
45 write!(f, "Transition ID {} not found in schema", transition_id)
46 }
47 SchemaError::InputTypeMismatch {
48 transition_id,
49 expected,
50 actual,
51 } => {
52 write!(
53 f,
54 "Transition {} input type mismatch: expected {:?}, got {:?}",
55 transition_id, expected, actual
56 )
57 }
58 SchemaError::OutputTypeMismatch {
59 transition_id,
60 expected,
61 actual,
62 } => {
63 write!(
64 f,
65 "Transition {} output type mismatch: expected {:?}, got {:?}",
66 transition_id, expected, actual
67 )
68 }
69 }
70 }
71}
72
73#[derive(Debug)]
75#[allow(missing_docs)]
76pub enum TransitionValidationError {
77 InputNotFound { type_id: StateTypeId },
79 OutputTypeNotDefined { type_id: StateTypeId },
81 ScriptExecutionFailed(String),
83 SignatureVerificationFailed,
85}
86
87impl core::fmt::Display for TransitionValidationError {
88 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
89 match self {
90 TransitionValidationError::InputNotFound { type_id } => {
91 write!(f, "Input state type {} not found", type_id)
92 }
93 TransitionValidationError::OutputTypeNotDefined { type_id } => {
94 write!(f, "Output state type {} not defined in schema", type_id)
95 }
96 TransitionValidationError::ScriptExecutionFailed(msg) => {
97 write!(f, "Validation script failed: {}", msg)
98 }
99 TransitionValidationError::SignatureVerificationFailed => {
100 write!(f, "Signature verification failed")
101 }
102 }
103 }
104}
105
106pub const SCHEMA_VERSION: u8 = 1;
108
109#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
111pub enum StateDataType {
112 FixedSize(u32),
114 Integer64,
116 Integer32,
118 Integer8,
120 Blob,
122 Hash256,
124}
125
126impl StateDataType {
127 pub fn fixed_size(&self) -> Option<u32> {
129 match self {
130 StateDataType::FixedSize(s) => Some(*s),
131 StateDataType::Integer64 => Some(8),
132 StateDataType::Integer32 => Some(4),
133 StateDataType::Integer8 => Some(1),
134 StateDataType::Hash256 => Some(32),
135 StateDataType::Blob => None,
136 }
137 }
138}
139
140#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
145pub struct GlobalStateType {
146 pub type_id: StateTypeId,
148 pub name: String,
150 pub data_type: StateDataType,
152 pub is_homomorphic: bool,
154}
155
156impl GlobalStateType {
157 pub fn new(
159 type_id: StateTypeId,
160 name: impl Into<String>,
161 data_type: StateDataType,
162 is_homomorphic: bool,
163 ) -> Self {
164 Self {
165 type_id,
166 name: name.into(),
167 data_type,
168 is_homomorphic,
169 }
170 }
171}
172
173#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
178pub struct OwnedStateType {
179 pub type_id: StateTypeId,
181 pub name: String,
183 pub data_type: StateDataType,
185 pub is_fungible: bool,
187}
188
189impl OwnedStateType {
190 pub fn new(
192 type_id: StateTypeId,
193 name: impl Into<String>,
194 data_type: StateDataType,
195 is_fungible: bool,
196 ) -> Self {
197 Self {
198 type_id,
199 name: name.into(),
200 data_type,
201 is_fungible,
202 }
203 }
204}
205
206#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
211pub struct TransitionDef {
212 pub transition_id: u16,
214 pub name: String,
216 pub owned_inputs: Vec<StateTypeId>,
218 pub owned_outputs: Vec<StateTypeId>,
220 pub global_updates: Vec<StateTypeId>,
222 pub validation_script: Vec<u8>,
224}
225
226impl TransitionDef {
227 pub fn new(
229 transition_id: u16,
230 name: impl Into<String>,
231 owned_inputs: Vec<StateTypeId>,
232 owned_outputs: Vec<StateTypeId>,
233 global_updates: Vec<StateTypeId>,
234 validation_script: Vec<u8>,
235 ) -> Self {
236 Self {
237 transition_id,
238 name: name.into(),
239 owned_inputs,
240 owned_outputs,
241 global_updates,
242 validation_script,
243 }
244 }
245}
246
247#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
253pub struct Schema {
254 pub version: u8,
256 pub schema_id: Hash,
258 pub name: String,
260 pub global_types: Vec<GlobalStateType>,
262 pub owned_types: Vec<OwnedStateType>,
264 pub transitions: Vec<TransitionDef>,
266 pub root_script: Vec<u8>,
268}
269
270impl Schema {
271 pub fn new(
273 schema_id: Hash,
274 name: impl Into<String>,
275 global_types: Vec<GlobalStateType>,
276 owned_types: Vec<OwnedStateType>,
277 transitions: Vec<TransitionDef>,
278 root_script: Vec<u8>,
279 ) -> Self {
280 Self {
281 version: SCHEMA_VERSION,
282 schema_id,
283 name: name.into(),
284 global_types,
285 owned_types,
286 transitions,
287 root_script,
288 }
289 }
290
291 pub fn hash(&self) -> Hash {
293 let mut hasher = Sha256::new();
294
295 hasher.update(b"CSV-SCHEMA-v1");
296 hasher.update(self.version.to_le_bytes());
297 hasher.update(self.name.as_bytes());
298
299 hasher.update((self.global_types.len() as u64).to_le_bytes());
301 for gt in &self.global_types {
302 hasher.update(gt.type_id.to_le_bytes());
303 hasher.update(gt.name.as_bytes());
304 hasher.update((gt.data_type.fixed_size().unwrap_or(0)).to_le_bytes());
305 hasher.update([gt.is_homomorphic as u8]);
306 }
307
308 hasher.update((self.owned_types.len() as u64).to_le_bytes());
310 for ot in &self.owned_types {
311 hasher.update(ot.type_id.to_le_bytes());
312 hasher.update(ot.name.as_bytes());
313 hasher.update((ot.data_type.fixed_size().unwrap_or(0)).to_le_bytes());
314 hasher.update([ot.is_fungible as u8]);
315 }
316
317 hasher.update((self.transitions.len() as u64).to_le_bytes());
319 for t in &self.transitions {
320 hasher.update(t.transition_id.to_le_bytes());
321 hasher.update(t.name.as_bytes());
322 hasher.update((t.owned_inputs.len() as u64).to_le_bytes());
323 for id in &t.owned_inputs {
324 hasher.update(id.to_le_bytes());
325 }
326 hasher.update((t.owned_outputs.len() as u64).to_le_bytes());
327 for id in &t.owned_outputs {
328 hasher.update(id.to_le_bytes());
329 }
330 hasher.update((t.validation_script.len() as u64).to_le_bytes());
331 hasher.update(&t.validation_script);
332 }
333
334 hasher.update((self.root_script.len() as u64).to_le_bytes());
336 hasher.update(&self.root_script);
337
338 let result = hasher.finalize();
339 let mut array = [0u8; 32];
340 array.copy_from_slice(&result);
341 Hash::new(array)
342 }
343
344 pub fn global_type(&self, type_id: StateTypeId) -> Option<&GlobalStateType> {
346 self.global_types.iter().find(|t| t.type_id == type_id)
347 }
348
349 pub fn owned_type(&self, type_id: StateTypeId) -> Option<&OwnedStateType> {
351 self.owned_types.iter().find(|t| t.type_id == type_id)
352 }
353
354 pub fn transition_def(&self, transition_id: u16) -> Option<&TransitionDef> {
356 self.transitions
357 .iter()
358 .find(|t| t.transition_id == transition_id)
359 }
360
361 pub fn has_type(&self, type_id: StateTypeId) -> bool {
363 self.global_type(type_id).is_some() || self.owned_type(type_id).is_some()
364 }
365
366 pub fn has_transition(&self, transition_id: u16) -> bool {
368 self.transition_def(transition_id).is_some()
369 }
370
371 pub fn validate_transition(&self, transition: &Transition) -> Result<(), SchemaError> {
373 let def = self.transition_def(transition.transition_id).ok_or(
374 SchemaError::TransitionNotFound {
375 transition_id: transition.transition_id,
376 },
377 )?;
378
379 let actual_input_types: Vec<StateTypeId> =
381 transition.owned_inputs.iter().map(|i| i.type_id).collect();
382 if actual_input_types != def.owned_inputs {
383 return Err(SchemaError::InputTypeMismatch {
384 transition_id: transition.transition_id,
385 expected: def.owned_inputs.clone(),
386 actual: actual_input_types,
387 });
388 }
389
390 let actual_output_types: Vec<StateTypeId> =
392 transition.owned_outputs.iter().map(|o| o.type_id).collect();
393 if actual_output_types != def.owned_outputs {
394 return Err(SchemaError::OutputTypeMismatch {
395 transition_id: transition.transition_id,
396 expected: def.owned_outputs.clone(),
397 actual: actual_output_types,
398 });
399 }
400
401 for update in &transition.global_updates {
403 if !self.has_type(update.type_id) {
404 return Err(SchemaError::TypeNotFound {
405 type_id: update.type_id,
406 });
407 }
408 }
409
410 Ok(())
411 }
412
413 pub fn fungible_token(schema_id: Hash, name: impl Into<String>) -> Self {
415 let name = name.into();
416 Self::new(
417 schema_id,
418 name.clone(),
419 vec![GlobalStateType::new(
420 1,
421 "supply",
422 StateDataType::Integer64,
423 true,
424 )],
425 vec![OwnedStateType::new(
426 10,
427 "asset",
428 StateDataType::Integer64,
429 true,
430 )],
431 vec![
432 TransitionDef::new(
434 0,
435 "genesis",
436 vec![],
437 vec![10],
438 vec![1],
439 vec![0x01], ),
441 TransitionDef::new(
443 1,
444 "transfer",
445 vec![10],
446 vec![10],
447 vec![],
448 vec![0x02], ),
450 TransitionDef::new(
452 2,
453 "burn",
454 vec![10],
455 vec![],
456 vec![1], vec![0x03], ),
459 ],
460 vec![0x00], )
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468 use crate::seal::SealRef;
469 use crate::state::{StateAssignment, StateRef};
470
471 fn test_schema() -> Schema {
472 Schema::fungible_token(Hash::new([1u8; 32]), "TestToken")
473 }
474
475 #[test]
476 fn test_schema_creation() {
477 let s = test_schema();
478 assert_eq!(s.version, SCHEMA_VERSION);
479 assert_eq!(s.name, "TestToken");
480 assert_eq!(s.global_types.len(), 1);
481 assert_eq!(s.owned_types.len(), 1);
482 assert_eq!(s.transitions.len(), 3);
483 }
484
485 #[test]
486 fn test_schema_hash() {
487 let s = test_schema();
488 let hash = s.hash();
489 assert_eq!(hash.as_bytes().len(), 32);
490 }
491
492 #[test]
493 fn test_schema_hash_deterministic() {
494 let s1 = test_schema();
495 let s2 = test_schema();
496 assert_eq!(s1.hash(), s2.hash());
497 }
498
499 #[test]
500 fn test_schema_hash_differs_by_name() {
501 let mut s = test_schema();
502 let original = s.hash();
503 s.name = "DifferentToken".to_string();
504 assert_ne!(s.hash(), original);
505 }
506
507 #[test]
508 fn test_schema_hash_differs_by_types() {
509 let mut s = test_schema();
510 let original = s.hash();
511 s.global_types.push(GlobalStateType::new(
512 99,
513 "extra",
514 StateDataType::Integer8,
515 false,
516 ));
517 assert_ne!(s.hash(), original);
518 }
519
520 #[test]
521 fn test_global_type_lookup() {
522 let s = test_schema();
523 let gt = s.global_type(1);
524 assert!(gt.is_some());
525 assert_eq!(gt.unwrap().name, "supply");
526 assert!(s.global_type(99).is_none());
527 }
528
529 #[test]
530 fn test_owned_type_lookup() {
531 let s = test_schema();
532 let ot = s.owned_type(10);
533 assert!(ot.is_some());
534 assert_eq!(ot.unwrap().name, "asset");
535 assert!(s.owned_type(99).is_none());
536 }
537
538 #[test]
539 fn test_transition_def_lookup() {
540 let s = test_schema();
541 let td = s.transition_def(1);
542 assert!(td.is_some());
543 assert_eq!(td.unwrap().name, "transfer");
544 assert!(s.transition_def(99).is_none());
545 }
546
547 #[test]
548 fn test_has_type() {
549 let s = test_schema();
550 assert!(s.has_type(1)); assert!(s.has_type(10)); assert!(!s.has_type(99));
553 }
554
555 #[test]
556 fn test_has_transition() {
557 let s = test_schema();
558 assert!(s.has_transition(0)); assert!(s.has_transition(1)); assert!(s.has_transition(2)); assert!(!s.has_transition(99));
562 }
563
564 #[test]
565 fn test_state_data_type_sizes() {
566 assert_eq!(StateDataType::Integer64.fixed_size(), Some(8));
567 assert_eq!(StateDataType::Integer32.fixed_size(), Some(4));
568 assert_eq!(StateDataType::Integer8.fixed_size(), Some(1));
569 assert_eq!(StateDataType::Hash256.fixed_size(), Some(32));
570 assert_eq!(StateDataType::Blob.fixed_size(), None);
571 assert_eq!(StateDataType::FixedSize(128).fixed_size(), Some(128));
572 }
573
574 #[test]
575 fn test_schema_serialization_roundtrip() {
576 let s = test_schema();
577 let bytes = bincode::serialize(&s).unwrap();
578 let restored: Schema = bincode::deserialize(&bytes).unwrap();
579 assert_eq!(s, restored);
580 assert_eq!(s.hash(), restored.hash());
581 }
582
583 #[test]
584 fn test_fungible_token_schema_structure() {
585 let s = test_schema();
586
587 let genesis = s.transition_def(0).unwrap();
589 assert!(genesis.owned_inputs.is_empty());
590 assert_eq!(genesis.owned_outputs, vec![10]);
591
592 let transfer = s.transition_def(1).unwrap();
594 assert_eq!(transfer.owned_inputs, vec![10]);
595 assert_eq!(transfer.owned_outputs, vec![10]);
596
597 let burn = s.transition_def(2).unwrap();
599 assert_eq!(burn.owned_inputs, vec![10]);
600 assert!(burn.owned_outputs.is_empty());
601 }
602
603 #[test]
604 fn test_validate_transition_valid() {
605 let s = test_schema();
606
607 let transfer = Transition::new(
609 1,
610 vec![StateRef::new(10, Hash::new([1u8; 32]), 0)],
611 vec![StateAssignment::new(
612 10,
613 SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
614 500u64.to_le_bytes().to_vec(),
615 )],
616 vec![],
617 vec![],
618 vec![0x02],
619 vec![],
620 );
621 assert!(s.validate_transition(&transfer).is_ok());
622 }
623
624 #[test]
625 fn test_validate_transition_unknown_transition_id() {
626 let s = test_schema();
627 let bad = Transition::new(99, vec![], vec![], vec![], vec![], vec![], vec![]);
628 let err = s.validate_transition(&bad).unwrap_err();
629 assert!(matches!(err, SchemaError::TransitionNotFound { .. }));
630 }
631
632 #[test]
633 fn test_validate_transition_input_type_mismatch() {
634 let s = test_schema();
635
636 let bad = Transition::new(
638 1,
639 vec![StateRef::new(99, Hash::new([1u8; 32]), 0)],
640 vec![],
641 vec![],
642 vec![],
643 vec![0x02],
644 vec![],
645 );
646 let err = s.validate_transition(&bad).unwrap_err();
647 assert!(matches!(err, SchemaError::InputTypeMismatch { .. }));
648 }
649
650 #[test]
651 fn test_validate_transition_output_type_mismatch() {
652 let s = test_schema();
653
654 let bad = Transition::new(
656 1,
657 vec![StateRef::new(10, Hash::new([1u8; 32]), 0)],
658 vec![StateAssignment::new(
659 99,
660 SealRef::new(vec![0xAA; 16], Some(1)).unwrap(),
661 500u64.to_le_bytes().to_vec(),
662 )],
663 vec![],
664 vec![],
665 vec![0x02],
666 vec![],
667 );
668 let err = s.validate_transition(&bad).unwrap_err();
669 assert!(matches!(err, SchemaError::OutputTypeMismatch { .. }));
670 }
671}