1use std::any::TypeId;
2use std::collections::HashMap;
3use std::fmt::Debug;
4
5use once_cell::sync::OnceCell;
6
7use sha2::{Digest, Sha256};
8pub mod container;
9mod primitive;
10pub mod safe_string;
11pub mod transaction_templates;
12use borsh::{BorshDeserialize, BorshSerialize};
13pub use container::Container;
14use nmt_rs::simple_merkle::db::MemDb;
15use nmt_rs::simple_merkle::tree::MerkleTree;
16use nmt_rs::TmSha2Hasher;
17pub use primitive::Primitive;
18#[cfg(feature = "serde")]
19use serde::{Deserialize, Serialize};
20mod schema_impls;
21
22use thiserror::Error;
23use transaction_templates::TransactionTemplateSet;
24
25use crate::display::{Context as DisplayContext, DisplayVisitor, FormatError};
26#[cfg(feature = "serde")]
27use crate::json_to_borsh::{Context as EncodeContext, EncodeError, EncodeVisitor};
28use crate::ty::byte_display::ByteParseError;
29use crate::ty::{ContainerSerdeMetadata, LinkingScheme, Ty};
30#[cfg(feature = "eip712")]
31use crate::visitors::eip712::{Context as Eip712Context, Eip712Error, Eip712Visitor};
32#[cfg(feature = "eip712")]
33use alloy_dyn_abi::{Eip712Types, Error as AlloyEip712Error, PropertyDef, TypedData};
34
35#[must_use]
41pub const fn chain_hash_fragment(chain_hash: &[u8; 32]) -> u64 {
42 u64::from_le_bytes([
43 chain_hash[0],
44 chain_hash[1],
45 chain_hash[2],
46 chain_hash[3],
47 chain_hash[4],
48 chain_hash[5],
49 chain_hash[6],
50 chain_hash[7],
51 ])
52}
53
54#[derive(Debug, Error)]
55pub enum SchemaError {
56 #[error(transparent)]
57 FormatError(#[from] FormatError),
58 #[error(transparent)]
59 BorshError(#[from] borsh::io::Error),
60 #[cfg(feature = "serde")]
61 #[error(transparent)]
62 EncodeError(#[from] EncodeError),
63 #[cfg(feature = "serde")]
64 #[error(transparent)]
65 JsonError(#[from] serde_json::Error),
66 #[cfg(feature = "eip712")]
67 #[error(transparent)]
68 Eip712Error(#[from] Eip712Error),
69 #[cfg(feature = "eip712")]
70 #[error(transparent)]
71 AlloyEip712Error(#[from] AlloyEip712Error),
72 #[error(transparent)]
73 Bech32Error(#[from] ByteParseError),
74 #[error("Rollup type {0:?} was missing from schema")]
75 MissingRollupRoot(RollupRoots),
76 #[error("Template {0} not found in schema")]
77 UnknownTemplate(String),
78 #[error("Index {0} not found in schema")]
79 InvalidIndex(usize),
80 #[error("Metadata hash must be provided but was not initialized. The schema was not properly finalized, or the serialized schema was invalid.")]
81 MetadataHashNotInitialized,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
85#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86pub struct IndexLinking;
87
88impl LinkingScheme for IndexLinking {
89 type TypeLink = Link;
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
104#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
105pub enum Link {
106 ByIndex(usize),
107 Immediate(Primitive),
108 Placeholder,
109 IndexedPlaceholder(usize),
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
114enum MaybePartialLink {
115 Partial(Link),
116 Complete(Link),
117}
118
119impl MaybePartialLink {
120 fn into_inner(self) -> Link {
121 match self {
122 MaybePartialLink::Partial(link) => link,
123 MaybePartialLink::Complete(link) => link,
124 }
125 }
126}
127
128#[derive(Default)]
131#[allow(clippy::type_complexity)] struct ConstructedMerkleTree(OnceCell<(MerkleTree<MemDb<[u8; 32]>, TmSha2Hasher>, [u8; 32])>);
133
134impl Debug for ConstructedMerkleTree {
135 fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 Ok(())
137 }
138}
139
140#[derive(Default)]
151struct MetadataHash(OnceCell<[u8; 32]>);
152
153impl Debug for MetadataHash {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 self.0.get().fmt(f)
156 }
157}
158
159impl BorshSerialize for MetadataHash {
160 fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
161 let hash = self.0.get().copied()
164 .ok_or_else(|| std::io::Error::new(
165 std::io::ErrorKind::InvalidData,
166 "Cannot serialize Schema: metadata_hash not initialized. Call finalize() before serializing"
167 ))?;
168 BorshSerialize::serialize(&hash, writer)
169 }
170}
171
172impl BorshDeserialize for MetadataHash {
173 fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
174 let hash: [u8; 32] = BorshDeserialize::deserialize_reader(reader)?;
175 let metadata_hash = MetadataHash::default();
176 metadata_hash.0.set(hash).map_err(|_| {
178 std::io::Error::new(
179 std::io::ErrorKind::InvalidData,
180 "Failed to set metadata_hash in OnceCell during deserialization",
181 )
182 })?;
183 Ok(metadata_hash)
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, Hash)]
188pub struct ItemId(pub TypeId);
189
190impl ItemId {
191 pub fn of<T: 'static + UniversalWallet>() -> Self {
192 T::id_override().unwrap_or(ItemId(TypeId::of::<T>()))
193 }
194}
195
196#[derive(Debug, Copy, Clone)]
199pub enum RollupRoots {
200 Transaction = 0,
201 TransactionSigningPayload = 1,
202 RuntimeCall = 2,
203 Address = 3,
204}
205
206#[derive(Debug, Default, Clone, BorshSerialize, BorshDeserialize)]
209#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
210pub struct ChainData {
211 pub chain_id: u64,
212 pub chain_name: String,
213}
214
215#[derive(Default, Debug, BorshSerialize, BorshDeserialize)]
226#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
227pub struct Schema {
228 types: Vec<Ty<IndexLinking>>,
233
234 root_type_indices: Vec<usize>,
237
238 chain_data: ChainData,
240
241 #[cfg_attr(feature = "serde", serde(skip))]
244 extra_metadata_hash: MetadataHash,
245
246 #[cfg_attr(feature = "serde", serde(skip))]
252 #[borsh(skip)]
253 chain_hash: OnceCell<[u8; 32]>,
254
255 #[borsh(skip)]
259 templates: Vec<TransactionTemplateSet>,
260
261 #[borsh(skip)]
270 serde_metadata: Vec<ContainerSerdeMetadata>,
271
272 #[cfg_attr(feature = "serde", serde(skip))]
274 #[borsh(skip)]
275 merkle_tree: ConstructedMerkleTree,
276
277 #[cfg_attr(feature = "serde", serde(skip))]
281 #[borsh(skip)]
282 known_types: HashMap<ItemId, usize>,
283
284 #[cfg_attr(feature = "serde", serde(skip))]
287 #[borsh(skip)]
288 under_construction: HashMap<ItemId, usize>,
289}
290
291impl Schema {
292 pub fn of_single_type<T: UniversalWallet>() -> Result<Self, SchemaError> {
295 let mut schema = Self::default();
297 T::make_root_of(&mut schema);
298 schema.finalize()?;
299 Ok(schema)
300 }
301
302 pub fn of_rollup_types_with_chain_data<
308 Transaction: UniversalWallet,
309 TransactionSigningPayload: UniversalWallet,
310 RuntimeCall: UniversalWallet,
311 Address: UniversalWallet,
312 >(
313 chain_data: ChainData,
314 ) -> Result<Self, SchemaError> {
315 let mut schema = Schema {
316 chain_data,
317 ..Self::default()
318 };
319 Transaction::make_root_of(&mut schema);
320 TransactionSigningPayload::make_root_of(&mut schema);
321 RuntimeCall::make_root_of(&mut schema);
322 Address::make_root_of(&mut schema);
323
324 schema.finalize()?;
325 Ok(schema)
326 }
327
328 pub fn chain_data(&self) -> &ChainData {
330 &self.chain_data
331 }
332
333 #[cfg(not(feature = "serde"))]
334 pub fn metadata_hash(&self) -> Result<[u8; 32], SchemaError> {
335 self.extra_metadata_hash
338 .0
339 .get()
340 .copied()
341 .ok_or(SchemaError::MetadataHashNotInitialized)
342 }
343
344 #[cfg(feature = "serde")]
345 pub fn metadata_hash(&self) -> Result<[u8; 32], SchemaError> {
346 self.extra_metadata_hash
348 .0
349 .get_or_try_init(|| self.calculate_metadata_hash())
350 .copied()
351 }
352
353 #[cfg(feature = "serde")]
354 fn calculate_metadata_hash(&self) -> Result<[u8; 32], SchemaError> {
355 let mut hasher = Sha256::new();
356 hasher.update(&borsh::to_vec(&self.templates)?);
357 hasher.update(&borsh::to_vec(&self.serde_metadata)?);
358 Ok(hasher.finalize().into())
359 }
360
361 #[cfg(feature = "serde")]
362 pub fn from_json(input: &str) -> Result<Self, serde_json::Error> {
363 serde_json::from_str(input)
364 }
365
366 pub fn rollup_expected_index(&self, rollup_type: RollupRoots) -> Result<usize, SchemaError> {
367 self.root_type_indices
368 .get(rollup_type as usize)
369 .copied()
370 .ok_or(SchemaError::MissingRollupRoot(rollup_type))
371 }
372
373 pub fn display(&self, type_index: usize, input: &[u8]) -> Result<String, SchemaError> {
375 let mut output = String::new();
376 let input = &mut &input[..];
377 let mut visitor = DisplayVisitor::new(input, &mut output);
378 self.types
379 .get(type_index)
380 .ok_or(SchemaError::InvalidIndex(type_index))?
381 .visit(self, &mut visitor, DisplayContext::default())?;
382
383 if !visitor.has_displayed_whole_input() {
384 return Err(FormatError::UnusedInput.into());
385 }
386 Ok(output)
387 }
388
389 #[cfg(feature = "eip712")]
392 pub fn eip712_json(&self, type_index: usize, input: &[u8]) -> Result<String, SchemaError> {
393 let Some(typed_data) = self.eip712_get_typed_data_inner(type_index, input)? else {
394 return Ok(String::default());
395 };
396 Ok(serde_json::to_string(&typed_data)?)
397 }
398
399 #[cfg(feature = "eip712")]
402 pub fn eip712_signing_hash(
403 &self,
404 type_index: usize,
405 input: &[u8],
406 ) -> Result<[u8; 32], SchemaError> {
407 let Some(typed_data) = self.eip712_get_typed_data_inner(type_index, input)? else {
408 return Ok(Default::default());
409 };
410 Ok(typed_data.eip712_signing_hash()?.into())
411 }
412
413 #[cfg(feature = "eip712")]
416 pub fn eip712_signing_digest(
417 &self,
418 type_index: usize,
419 input: &[u8],
420 ) -> Result<[u8; 66], SchemaError> {
421 let Some(typed_data) = self.eip712_get_typed_data_inner(type_index, input)? else {
422 return Ok([0; 66]);
423 };
424 let mut buf = [0u8; 66];
428 buf[0] = 0x19;
429 buf[1] = 0x01;
430 buf[2..34].copy_from_slice(typed_data.domain.separator().as_slice());
431 buf[34..].copy_from_slice(typed_data.hash_struct()?.as_slice());
432 Ok(buf)
433 }
434
435 #[cfg(feature = "eip712")]
436 fn eip712_get_typed_data_inner(
437 &self,
438 type_index: usize,
439 input: &[u8],
440 ) -> Result<Option<TypedData>, SchemaError> {
441 let mut out_types = Eip712Types::default();
442 let input = &mut &input[..];
443 let mut visitor = Eip712Visitor::new(input, &mut out_types);
444 let root_type = self
445 .types()
446 .get(type_index)
447 .ok_or(SchemaError::InvalidIndex(type_index))?;
448 let Some(visitor_return) = root_type.visit(self, &mut visitor, Eip712Context::default())?
449 else {
450 return Ok(None);
451 };
452 if !visitor.has_displayed_whole_input() {
453 return Err(FormatError::UnusedInput.into());
454 }
455
456 out_types.insert(
459 "EIP712Domain".to_string(),
460 vec![
461 PropertyDef::new("string", "name").unwrap(),
462 PropertyDef::new("uint256", "chainId").unwrap(),
463 PropertyDef::new("bytes32", "salt").unwrap(),
464 ],
465 );
466
467 Ok(Some(TypedData {
468 domain: alloy_dyn_abi::Eip712Domain {
469 name: Some(self.chain_data.chain_name.clone().into()),
470 version: None,
471 chain_id: Some(alloy_primitives::U256::from(self.chain_data.chain_id)),
472 verifying_contract: None,
475 salt: Some(self.chain_hash()?.into()),
476 },
477 resolver: out_types.into(),
478 primary_type: visitor_return.unique_type_name,
479 message: visitor_return.json_value,
480 }))
481 }
482
483 #[cfg(feature = "serde")]
486 pub fn json_to_borsh(&self, type_index: usize, input: &str) -> Result<Vec<u8>, SchemaError> {
487 let mut output = Vec::new();
488
489 let mut visitor = EncodeVisitor::new(&mut output)?;
490
491 self.types
492 .get(type_index)
493 .ok_or(SchemaError::InvalidIndex(type_index))?
494 .visit(self, &mut visitor, EncodeContext::new(input, type_index)?)?;
495
496 Ok(output)
497 }
498
499 #[cfg(feature = "serde")]
501 pub fn fill_template_from_json(
502 &self,
503 root_index: usize,
504 template_name: &str,
505 input: &str,
506 ) -> Result<Vec<u8>, SchemaError> {
507 fn serde_to_schema_err(e: serde_json::Error) -> SchemaError {
508 SchemaError::EncodeError(EncodeError::Json(e.to_string()))
509 }
510
511 let template = self
512 .templates
513 .get(root_index)
514 .ok_or(SchemaError::InvalidIndex(root_index))?
515 .0
516 .get(template_name)
517 .ok_or(SchemaError::UnknownTemplate(template_name.to_string()))?;
518
519 let mut input_map: serde_json::Map<String, serde_json::Value> =
521 serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(input)
522 .map_err(serde_to_schema_err)?;
523
524 let mut output = template.preencoded_bytes().to_owned();
525
526 for (name, input) in template.inputs().iter().rev() {
529 let ty = match input.type_link() {
531 Link::ByIndex(i) => self.types.get(*i).expect("Template {name} contained an invalid link: {i}. This is a major bug with template generation."),
532 Link::Immediate(ty) => &ty.clone().into(),
533 Link::Placeholder | Link::IndexedPlaceholder(_) => panic!("Template {name} contained placeholder link. This is a major bug with template generation.")
534 };
535 let json_value = input_map.remove(name).ok_or(EncodeError::MissingType {
537 name: name.to_owned(),
538 })?;
539 let mut buf = Vec::new();
541 let mut visitor = EncodeVisitor::new(&mut buf)?;
542 ty.visit(
543 self,
544 &mut visitor,
545 EncodeContext::from_val(json_value, input.type_link()),
546 )?;
547
548 output.splice(input.offset()..input.offset(), buf);
550 }
551
552 if !input_map.is_empty() {
553 return Err(SchemaError::EncodeError(EncodeError::UnusedInput {
555 value: input_map.iter().next().unwrap().0.to_owned(),
556 }));
557 }
558
559 Ok(output)
560 }
561
562 #[cfg(feature = "serde")]
564 pub fn templates(&self, index: usize) -> Result<Vec<String>, SchemaError> {
565 Ok(self
566 .templates
567 .get(index)
568 .ok_or(SchemaError::InvalidIndex(index))?
569 .0
570 .keys()
571 .cloned()
572 .collect())
573 }
574
575 pub fn chain_hash(&self) -> Result<[u8; 32], SchemaError> {
580 self.chain_hash
581 .get_or_try_init(|| {
582 let merkle_root = self.merkle_root()?;
584
585 let mut hasher = Sha256::new();
587 hasher.update(&borsh::to_vec(&self.root_type_indices)?);
588 hasher.update(&borsh::to_vec(&self.chain_data)?);
589 let internal_data_hash: [u8; 32] = hasher.finalize().into();
590
591 let metadata_hash = self.metadata_hash()?;
593
594 let mut hasher = Sha256::new();
596 hasher.update(merkle_root);
597 hasher.update(internal_data_hash);
598 hasher.update(metadata_hash);
599
600 let chain_hash: [u8; 32] = hasher.finalize().into();
601 Ok(chain_hash)
602 })
603 .copied()
604 }
605
606 fn merkle_root(&self) -> Result<[u8; 32], SchemaError> {
607 let (_, root) = self.merkle_tree.0.get_or_try_init(|| {
608 let mut tree = MerkleTree::new();
609 for ty in &self.types {
610 tree.push_raw_leaf(&borsh::to_vec(ty)?)
611 }
612 let root = tree.root();
613 Ok::<_, SchemaError>((tree, root))
614 })?;
615 Ok(*root)
616 }
617
618 fn finalize(&self) -> Result<(), SchemaError> {
619 self.metadata_hash()?;
621 self.chain_hash()?;
622 Ok(())
623 }
624
625 pub fn types(&self) -> &[Ty<IndexLinking>] {
626 &self.types
627 }
628
629 pub fn serde_metadata(&self) -> &[ContainerSerdeMetadata] {
630 &self.serde_metadata
631 }
632
633 pub fn root_types(&self) -> &[usize] {
634 &self.root_type_indices
635 }
636
637 fn find_item_by_id(&self, item_id: &ItemId) -> Option<usize> {
638 self.known_types.get(item_id).copied()
639 }
640
641 fn link_child_to_parent(&mut self, parent: ItemId, child: Link) {
643 let idx = self.known_types.get(&parent).unwrap_or_else(|| panic!("Tried to link a child to a parent ({parent:?}) that the schema doesn't have. This is a bug in a hand-written schema."));
644
645 let remaining_children = *self.under_construction.get(&parent).unwrap_or_else(|| panic!("Tried to link too many children to parent ({parent:?}). This is a bug in a hand-written schema."));
646 if remaining_children == 1 {
647 self.under_construction.remove(&parent);
648 } else {
649 self.under_construction
650 .insert(parent, remaining_children - 1);
651 }
652 self.types[*idx].fill_next_placholder(child);
653 }
654
655 fn get_partial_link_to(
660 &mut self,
661 item: Item<IndexLinking>,
662 item_id: ItemId,
663 ) -> MaybePartialLink {
664 match item {
665 Item::Container(c) => {
666 if let Some(location) = self.find_item_by_id(&item_id) {
667 MaybePartialLink::Complete(Link::ByIndex(location))
668 } else {
669 let num_children = c.num_children();
670 let serde_metadata = c.serde();
671 let location = self.types.len();
672 self.known_types.insert(item_id.clone(), location);
673 self.types.push(c.into());
674 self.serde_metadata.push(serde_metadata);
675 if num_children != 0 {
676 self.under_construction.insert(item_id, num_children);
677 MaybePartialLink::Partial(Link::ByIndex(location))
678 } else {
679 MaybePartialLink::Complete(Link::ByIndex(location))
680 }
681 }
682 }
683 Item::Atom(primitive) => MaybePartialLink::Complete(Link::Immediate(primitive)),
684 }
685 }
686
687 fn push_root_link(&mut self, link: Link) {
693 match link {
694 Link::ByIndex(i) => self.root_type_indices.push(i),
695 Link::Immediate(..) => {},
696 Link::Placeholder | Link::IndexedPlaceholder(_) => panic!("Attempted to register a placeholder link as a schema root - are you passing the right link?"),
697 }
698 }
699}
700
701pub enum Item<L: LinkingScheme> {
702 Container(Container<L>),
703 Atom(Primitive),
704}
705
706pub trait UniversalWallet: Sized + 'static {
711 fn get_child_links(schema: &mut Schema) -> Vec<Link>;
720
721 fn scaffold() -> Item<IndexLinking>;
724
725 fn write_schema(schema: &mut Schema) -> Link {
730 let item = Self::scaffold();
731 let item_id = ItemId::of::<Self>();
732 match item {
733 Item::Atom(_primitive) => {
734 panic!("Creating a schema for primitive root types is not supported. If this is necessary, wrap the primitive in a newtype struct. If you did not specify a primitive root type, this may be a bug in schema generation.");
739 }
740 Item::Container(container) => {
741 let link = schema.get_partial_link_to(Item::Container(container), item_id.clone());
742 if let MaybePartialLink::Complete(link) = link {
743 return link;
744 }
745
746 for child in Self::get_child_links(schema) {
747 schema.link_child_to_parent(item_id.clone(), child);
748 }
749 link.into_inner()
750 }
751 }
752 }
753
754 fn make_root_of(schema: &mut Schema) {
757 let link = Self::write_schema(schema);
758 assert!(
759 schema.under_construction.is_empty(),
760 "Schema generation left some types partially constructed. This is a bug in the schema. {schema:?}"
761 );
762 schema.push_root_link(link);
763 let templates = Self::get_child_templates(schema);
764 schema.templates.push(templates);
765 }
766
767 fn get_child_templates(_schema: &mut Schema) -> TransactionTemplateSet {
771 Default::default()
772 }
773
774 fn make_linkable(schema: &mut Schema) -> Link {
776 match Self::scaffold() {
777 Item::Container(_) => Self::write_schema(schema),
778 Item::Atom(atom) => Link::Immediate(atom),
779 }
780 }
781
782 fn id_override() -> Option<ItemId> {
785 None
786 }
787}
788
789pub trait OverrideSchema {
797 type Output: UniversalWallet;
798}
799
800impl<T: OverrideSchema + 'static> UniversalWallet for T {
801 fn scaffold() -> Item<IndexLinking> {
802 <Self as OverrideSchema>::Output::scaffold()
803 }
804 fn get_child_links(schema: &mut Schema) -> Vec<Link> {
805 <Self as OverrideSchema>::Output::get_child_links(schema)
806 }
807 fn id_override() -> Option<ItemId> {
808 <Self as OverrideSchema>::Output::id_override()
809 }
810 fn make_linkable(schema: &mut Schema) -> Link {
811 <Self as OverrideSchema>::Output::make_linkable(schema)
812 }
813 fn write_schema(schema: &mut Schema) -> Link {
814 <Self as OverrideSchema>::Output::write_schema(schema)
815 }
816}