1use std::collections::{BTreeMap, BTreeSet};
23use std::{iter, slice};
24
25use amplify::confinement::{LargeVec, MediumBlob, SmallOrdMap, TinyOrdMap, TinyOrdSet};
26use commit_verify::Conceal;
27use rgb::validation::{AnchoredBundle, ConsignmentApi};
28use rgb::{
29 validation, AttachId, BundleId, ContractHistory, ContractId, Extension, Genesis, GraphSeal,
30 OpId, OpRef, Operation, Schema, SchemaId, SecretSeal, SubSchema, Transition, TransitionBundle,
31 WitnessAnchor,
32};
33use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize};
34
35use super::{ContainerVer, ContentId, ContentSigs, Terminal};
36use crate::accessors::BundleExt;
37use crate::interface::{ContractSuppl, IfaceId, IfacePair};
38use crate::resolvers::ResolveHeight;
39use crate::LIB_NAME_RGB_STD;
40
41pub type Transfer = Consignment<true>;
42pub type Contract = Consignment<false>;
43
44#[derive(Clone, Debug)]
56#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
57#[strict_type(lib = LIB_NAME_RGB_STD)]
58#[cfg_attr(
59 feature = "serde",
60 derive(Serialize, Deserialize),
61 serde(crate = "serde_crate", rename_all = "camelCase")
62)]
63pub struct Consignment<const TYPE: bool> {
64 #[strict_type(skip, dumb = None)]
70 #[cfg_attr(feature = "serde", serde(skip))]
71 pub(super) validation_status: Option<validation::Status>,
72
73 pub version: ContainerVer,
75
76 pub transfer: bool,
80
81 pub schema: SubSchema,
83
84 pub ifaces: TinyOrdMap<IfaceId, IfacePair>,
86
87 pub supplements: TinyOrdSet<ContractSuppl>,
89
90 pub genesis: Genesis,
92
93 pub terminals: SmallOrdMap<BundleId, Terminal>,
95
96 pub bundles: LargeVec<AnchoredBundle>,
98
99 pub extensions: LargeVec<Extension>,
101
102 pub attachments: SmallOrdMap<AttachId, MediumBlob>,
106
107 pub signatures: TinyOrdMap<ContentId, ContentSigs>,
110}
111
112impl<const TYPE: bool> StrictSerialize for Consignment<TYPE> {}
113impl<const TYPE: bool> StrictDeserialize for Consignment<TYPE> {}
114
115impl<const TYPE: bool> Consignment<TYPE> {
116 pub fn new(schema: SubSchema, genesis: Genesis) -> Self {
120 assert_eq!(schema.schema_id(), genesis.schema_id);
121 Consignment {
122 validation_status: None,
123 version: ContainerVer::V1,
124 transfer: TYPE,
125 schema,
126 ifaces: none!(),
127 supplements: none!(),
128 genesis,
129 terminals: none!(),
130 bundles: none!(),
131 extensions: none!(),
132 attachments: none!(),
133 signatures: none!(),
134 }
135 }
136
137 #[inline]
138 pub fn schema_id(&self) -> SchemaId { self.schema.schema_id() }
139
140 #[inline]
141 pub fn root_schema_id(&self) -> Option<SchemaId> {
142 self.schema.subset_of.as_ref().map(Schema::schema_id)
143 }
144
145 #[inline]
146 pub fn contract_id(&self) -> ContractId { self.genesis.contract_id() }
147
148 pub fn anchored_bundle(&self, bundle_id: BundleId) -> Option<&AnchoredBundle> {
149 self.bundles
150 .iter()
151 .find(|anchored_bundle| anchored_bundle.bundle.bundle_id() == bundle_id)
152 }
153
154 pub fn validation_status(&self) -> Option<&validation::Status> {
155 self.validation_status.as_ref()
156 }
157
158 pub fn into_validation_status(self) -> Option<validation::Status> { self.validation_status }
159
160 pub fn update_history<R: ResolveHeight>(
161 &self,
162 history: Option<&ContractHistory>,
163 resolver: &mut R,
164 ) -> Result<ContractHistory, R::Error> {
165 let mut history = history.cloned().unwrap_or_else(|| {
166 ContractHistory::with(
167 self.schema_id(),
168 self.root_schema_id(),
169 self.contract_id(),
170 &self.genesis,
171 )
172 });
173
174 let mut extension_idx = self
175 .extensions
176 .iter()
177 .map(Extension::id)
178 .zip(iter::repeat(false))
179 .collect::<BTreeMap<_, _>>();
180 let mut ordered_extensions = BTreeMap::new();
181 for anchored_bundle in &self.bundles {
182 for item in anchored_bundle.bundle.values() {
183 if let Some(transition) = &item.transition {
184 let txid = anchored_bundle.anchor.txid;
185 let height = resolver.resolve_height(txid)?;
186 let ord_txid = WitnessAnchor::new(height, txid);
187 history.add_transition(transition, ord_txid);
188 for (id, used) in &mut extension_idx {
189 if *used {
190 continue;
191 }
192 for input in &transition.inputs {
193 if input.prev_out.op == *id {
194 *used = true;
195 if let Some(ord) = ordered_extensions.get_mut(id) {
196 if *ord > ord_txid {
197 *ord = ord_txid;
198 }
199 } else {
200 ordered_extensions.insert(*id, ord_txid);
201 }
202 }
203 }
204 }
205 }
206 }
207 }
208 for extension in &self.extensions {
209 if let Some(ord_txid) = ordered_extensions.get(&extension.id()) {
210 history.add_extension(extension, *ord_txid);
211 }
212 }
213
214 Ok(history)
215 }
216
217 pub fn reveal_bundle_seal(&mut self, bundle_id: BundleId, revealed: GraphSeal) {
218 for anchored_bundle in &mut self.bundles {
219 if anchored_bundle.bundle.bundle_id() == bundle_id {
220 anchored_bundle.bundle.reveal_seal(revealed);
221 }
222 }
223 }
224
225 pub fn into_contract(self) -> Contract {
226 Contract {
227 validation_status: self.validation_status,
228 version: self.version,
229 transfer: false,
230 schema: self.schema,
231 ifaces: self.ifaces,
232 supplements: self.supplements,
233 genesis: self.genesis,
234 terminals: self.terminals,
235 bundles: self.bundles,
236 extensions: self.extensions,
237 attachments: self.attachments,
238 signatures: self.signatures,
239 }
240 }
241}
242
243impl<const TYPE: bool> ConsignmentApi for Consignment<TYPE> {
244 type BundleIter<'container>
245 = slice::Iter<'container, AnchoredBundle> where Self: 'container;
246
247 fn schema(&self) -> &SubSchema { &self.schema }
248
249 fn operation(&self, opid: OpId) -> Option<OpRef> {
250 if opid == self.genesis.id() {
251 return Some(OpRef::Genesis(&self.genesis));
252 }
253 self.transition(opid)
254 .map(OpRef::from)
255 .or_else(|| self.extension(opid).map(OpRef::from))
256 }
257
258 fn genesis(&self) -> &Genesis { &self.genesis }
259
260 fn transition(&self, opid: OpId) -> Option<&Transition> {
261 for anchored_bundle in &self.bundles {
262 for (id, item) in anchored_bundle.bundle.iter() {
263 if *id == opid {
264 return item.transition.as_ref();
265 }
266 }
267 }
268 None
269 }
270
271 fn extension(&self, opid: OpId) -> Option<&Extension> {
272 self.extensions
273 .iter()
274 .find(|&extension| extension.id() == opid)
275 }
276
277 fn terminals(&self) -> BTreeSet<(BundleId, SecretSeal)> {
278 self.terminals
279 .iter()
280 .flat_map(|(bundle_id, terminal)| {
281 terminal
282 .seals
283 .iter()
284 .map(|seal| (*bundle_id, seal.conceal()))
285 })
286 .collect()
287 }
288
289 fn anchored_bundles(&self) -> Self::BundleIter<'_> { self.bundles.iter() }
290
291 fn bundle_by_id(&self, bundle_id: BundleId) -> Option<&TransitionBundle> {
292 self.anchored_bundle(bundle_id).map(|ab| &ab.bundle)
293 }
294
295 fn op_ids_except(&self, ids: &BTreeSet<OpId>) -> BTreeSet<OpId> {
296 let mut exceptions = BTreeSet::new();
297 for anchored_bundle in &self.bundles {
298 for item in anchored_bundle.bundle.values() {
299 if let Some(id) = item.transition.as_ref().map(Transition::id) {
300 if !ids.contains(&id) {
301 exceptions.insert(id);
302 }
303 }
304 }
305 }
306 exceptions
307 }
308
309 fn has_operation(&self, opid: OpId) -> bool { self.operation(opid).is_some() }
310
311 fn known_transitions_by_bundle_id(&self, bundle_id: BundleId) -> Option<Vec<&Transition>> {
312 self.bundle_by_id(bundle_id).map(|bundle| {
313 bundle
314 .values()
315 .filter_map(|item| item.transition.as_ref())
316 .collect()
317 })
318 }
319}