1use std::convert::Infallible;
26use std::error::Error;
27use std::vec;
28
29use amplify::confinement::{LargeVec, SmallBlob, SmallOrdMap, TinyVec};
30use amplify::ByteArray;
31use commit_verify::{ReservedBytes, StrictHash};
32use hypersonic::Articles;
33use rgb::{OperationSeals, ReadOperation, RgbSeal, LIB_NAME_RGB};
34use strict_encoding::{DecodeError, ReadRaw, StrictDecode, StrictReader, TypedRead};
35
36use crate::{ContractId, Identity, Issue, SemanticError, Semantics, SigBlob, CONSIGN_VERSION};
37
38pub const MAX_CONSIGNMENT_OPS: u32 = u16::MAX as u32;
39
40#[derive(StrictType, StrictDumb, StrictEncode)]
41#[strict_type(lib = LIB_NAME_RGB)]
42pub struct Consignment<Seal: RgbSeal> {
43 header: ConsignmentHeader<Seal>,
44 operation_seals: LargeVec<OperationSeals<Seal>>,
45}
46
47#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
48#[strict_type(lib = LIB_NAME_RGB)]
49struct ConsignmentHeader<Seal: RgbSeal> {
50 extensions: TinyVec<SmallBlob>,
51 semantics: Semantics,
52 sig: Option<SigBlob>,
53 issue: Issue,
54 genesis_seals: SmallOrdMap<u16, Seal::Definition>,
55 witness: ReservedBytes<1>,
56 op_count: u32,
57}
58
59impl<Seal: RgbSeal> StrictDecode for Consignment<Seal> {
60 fn strict_decode(reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
61 let header = ConsignmentHeader::<Seal>::strict_decode(reader)?;
62 if header.op_count > MAX_CONSIGNMENT_OPS {
63 return Err(DecodeError::DataIntegrityError(format!(
64 "number of operations in contract consignment ({}) exceeds maximum allowed \
65 ({MAX_CONSIGNMENT_OPS})",
66 header.op_count
67 )));
68 }
69 let mut operation_seals = LargeVec::with_capacity(header.op_count as usize);
70 for _ in 0..header.op_count {
71 operation_seals
72 .push(OperationSeals::<Seal>::strict_decode(reader)?)
73 .ok();
74 }
75
76 Ok(Self { header, operation_seals })
77 }
78}
79
80impl<Seal: RgbSeal> Consignment<Seal> {
81 pub fn articles<E>(
82 &self,
83 sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
84 ) -> Result<Articles, SemanticError> {
85 Articles::with(
86 self.header.semantics.clone(),
87 self.header.issue.clone(),
88 self.header.sig.clone(),
89 sig_validator,
90 )
91 }
92
93 pub(crate) fn into_operations(self) -> InMemOps<Seal> {
94 let genesis = OperationSeals {
95 operation: self
96 .header
97 .issue
98 .genesis
99 .to_operation(ContractId::from_byte_array(
100 self.header.issue.codex_id().to_byte_array(),
101 )),
102 defined_seals: self.header.genesis_seals,
103 witness: None,
104 };
105 InMemOps(Some(genesis), self.operation_seals.into_iter())
106 }
107}
108
109pub(crate) struct InMemOps<Seal: RgbSeal>(
110 Option<OperationSeals<Seal>>,
111 vec::IntoIter<OperationSeals<Seal>>,
112);
113
114impl<Seal: RgbSeal> ReadOperation for InMemOps<Seal> {
115 type Seal = Seal;
116
117 fn read_operation(
118 &mut self,
119 ) -> Result<Option<OperationSeals<Self::Seal>>, impl Error + 'static> {
120 Result::<_, Infallible>::Ok(self.0.take().or_else(|| self.1.next()))
121 }
122}
123
124pub fn parse_consignment(
125 reader: &mut StrictReader<impl ReadRaw>,
126) -> Result<ContractId, DecodeError> {
127 ReservedBytes::<1, { CONSIGN_VERSION as u8 }>::strict_decode(reader).map_err(|e| {
128 if matches!(e, DecodeError::DataIntegrityError(_)) {
129 DecodeError::DataIntegrityError(s!("unsupported future consignment version"))
130 } else {
131 e
132 }
133 })?;
134 ContractId::strict_decode(reader)
135}