1use alloc::collections::BTreeMap;
26use core::borrow::Borrow;
27use core::error::Error;
28use core::marker::PhantomData;
29use std::io;
30
31use amplify::confinement::SmallOrdMap;
32use amplify::{IoError, MultiError};
33use chrono::{DateTime, Utc};
34use commit_verify::{ReservedBytes, StrictHash};
35use hypersonic::{
36 AcceptError, Articles, AuthToken, CallParams, CellAddr, Codex, Consensus, ContractId,
37 CoreParams, DataCell, EffectiveState, IssueError, IssueParams, Ledger, LibRepo, Memory,
38 MethodName, NamedState, Operation, Opid, SemanticError, Semantics, SigBlob, StateAtom,
39 StateName, Stock, Transition,
40};
41use indexmap::{IndexMap, IndexSet};
42use rgb::{
43 ContractApi, ContractVerify, OperationSeals, ReadOperation, RgbSeal, RgbSealDef,
44 VerificationError,
45};
46use single_use_seals::{ClientSideWitness, PublishedWitness, SealWitness};
47use strict_encoding::{
48 DecodeError, ReadRaw, StrictDecode, StrictDumb, StrictEncode, StrictReader, StrictWriter,
49 TypeName, TypedRead, WriteRaw,
50};
51use strict_types::StrictVal;
52
53use crate::{
54 parse_consignment, Consignment, ContractMeta, Identity, Issue, Issuer, IssuerError, IssuerSpec,
55 OpRels, Pile, VerifiedOperation, Witness, WitnessStatus,
56};
57
58#[derive(Copy, Clone, PartialEq, Eq, Debug, From)]
59#[cfg_attr(
60 feature = "serde",
61 derive(Serialize, Deserialize),
62 serde(untagged, bound = "Seal: serde::Serialize + for<'d> serde::Deserialize<'d>")
63)]
64pub enum EitherSeal<Seal> {
65 Alt(Seal),
66
67 #[from]
68 Token(AuthToken),
69}
70
71impl<Seal> EitherSeal<Seal> {
72 pub fn auth_token(&self) -> AuthToken
73 where Seal: RgbSealDef {
74 match self {
75 EitherSeal::Alt(seal) => seal.auth_token(),
76 EitherSeal::Token(auth) => *auth,
77 }
78 }
79
80 pub fn to_explicit(&self) -> Option<Seal>
81 where Seal: Clone {
82 match self {
83 EitherSeal::Alt(seal) => Some(seal.clone()),
84 EitherSeal::Token(_) => None,
85 }
86 }
87}
88
89#[derive(Clone, PartialEq, Eq, Debug)]
90#[cfg_attr(
91 feature = "serde",
92 derive(Serialize, Deserialize),
93 serde(bound = "Seal: serde::Serialize + for<'d> serde::Deserialize<'d>")
94)]
95pub struct Assignment<Seal> {
96 pub seal: Seal,
97 pub data: StrictVal,
98}
99
100impl<Seal> Assignment<Seal> {
101 pub fn new(seal: Seal, data: impl Into<StrictVal>) -> Self { Self { seal, data: data.into() } }
102}
103
104impl<Seal> Assignment<EitherSeal<Seal>> {
105 pub fn new_external(auth: AuthToken, data: impl Into<StrictVal>) -> Self {
106 Self { seal: EitherSeal::Token(auth), data: data.into() }
107 }
108 pub fn new_internal(seal: Seal, data: impl Into<StrictVal>) -> Self {
109 Self { seal: EitherSeal::Alt(seal), data: data.into() }
110 }
111}
112
113#[derive(Clone, PartialEq, Eq, Debug)]
115#[cfg_attr(
116 feature = "serde",
117 derive(Serialize, Deserialize),
118 serde(bound = "Seal: serde::Serialize + for<'d> serde::Deserialize<'d>")
119)]
120pub struct OwnedState<Seal> {
121 pub addr: CellAddr,
123
124 #[cfg_attr(feature = "serde", serde(flatten))]
126 pub assignment: Assignment<Seal>,
127
128 pub status: WitnessStatus,
139}
140
141#[derive(Clone, PartialEq, Eq, Debug)]
142#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
143pub struct ImmutableState {
144 pub addr: CellAddr,
145 #[cfg_attr(feature = "serde", serde(flatten))]
146 pub data: StateAtom,
147 pub status: WitnessStatus,
148}
149
150#[derive(Clone, Eq, PartialEq, Debug, Default)]
151#[cfg_attr(
152 feature = "serde",
153 derive(Serialize, Deserialize),
154 serde(
155 rename_all = "camelCase",
156 bound = "Seal: serde::Serialize + for<'d> serde::Deserialize<'d>"
157 )
158)]
159pub struct ContractState<Seal> {
160 pub immutable: BTreeMap<StateName, Vec<ImmutableState>>,
161 pub owned: BTreeMap<StateName, Vec<OwnedState<Seal>>>,
162 pub aggregated: BTreeMap<StateName, StrictVal>,
163}
164
165impl<Seal> ContractState<Seal> {
166 pub fn map<To>(self, f: impl Fn(Seal) -> To) -> ContractState<To> {
167 ContractState {
168 immutable: self.immutable,
169 owned: self
170 .owned
171 .into_iter()
172 .map(|(name, map)| {
173 let map = map
174 .into_iter()
175 .map(|owned| OwnedState {
176 addr: owned.addr,
177 assignment: Assignment {
178 seal: f(owned.assignment.seal),
179 data: owned.assignment.data,
180 },
181 status: owned.status,
182 })
183 .collect();
184 (name, map)
185 })
186 .collect(),
187 aggregated: self.aggregated,
188 }
189 }
190
191 pub fn filter_map<To>(self, f: impl Fn(Seal) -> Option<To>) -> ContractState<To> {
192 ContractState {
193 immutable: self.immutable,
194 owned: self
195 .owned
196 .into_iter()
197 .map(|(name, map)| {
198 let map = map
199 .into_iter()
200 .filter_map(|owned| {
201 Some(OwnedState {
202 addr: owned.addr,
203 assignment: Assignment {
204 seal: f(owned.assignment.seal)?,
205 data: owned.assignment.data,
206 },
207 status: owned.status,
208 })
209 })
210 .collect();
211 (name, map)
212 })
213 .collect(),
214 aggregated: self.aggregated,
215 }
216 }
217}
218
219#[derive(Clone, Debug)]
224#[cfg_attr(
225 feature = "serde",
226 derive(Serialize, Deserialize),
227 serde(
228 rename_all = "camelCase",
229 bound = "Seal: serde::Serialize + for<'d> serde::Deserialize<'d>"
230 )
231)]
232pub struct CreateParams<Seal: Clone> {
233 pub issuer: IssuerSpec,
234 pub consensus: Consensus,
235 pub testnet: bool,
236 pub method: MethodName,
237 pub name: TypeName,
238 pub timestamp: Option<DateTime<Utc>>,
239 pub global: Vec<NamedState<StateAtom>>,
240 pub owned: Vec<NamedState<Assignment<EitherSeal<Seal>>>>,
241}
242
243impl<Seal: Clone> CreateParams<Seal> {
244 pub fn new_testnet(
245 issuer: impl Into<IssuerSpec>,
246 consensus: Consensus,
247 name: impl Into<TypeName>,
248 ) -> Self {
249 Self {
250 issuer: issuer.into(),
251 consensus,
252 testnet: true,
253 method: vname!("issue"),
254 name: name.into(),
255 timestamp: None,
256 global: none![],
257 owned: none![],
258 }
259 }
260
261 pub fn with_global_verified(
262 mut self,
263 name: impl Into<StateName>,
264 data: impl Into<StrictVal>,
265 ) -> Self {
266 self.global
267 .push(NamedState { name: name.into(), state: StateAtom::new_verified(data) });
268 self
269 }
270
271 pub fn push_owned_unlocked(
272 &mut self,
273 name: impl Into<StateName>,
274 assignment: Assignment<EitherSeal<Seal>>,
275 ) {
276 self.owned
277 .push(NamedState { name: name.into(), state: assignment });
278 }
279}
280
281#[derive(Clone, Debug)]
282pub struct Contract<S: Stock, P: Pile> {
283 contract_id: ContractId,
285 ledger: Ledger<S>,
286 pile: P,
287}
288
289impl<S: Stock, P: Pile> Contract<S, P> {
290 pub fn with(
292 articles: Articles,
293 consignment: Consignment<P::Seal>,
294 conf: S::Conf,
295 ) -> Result<Self, MultiError<ConsumeError<<P::Seal as RgbSeal>::Definition>, S::Error, P::Error>>
296 where
297 P::Conf: From<S::Conf>,
298 <P::Seal as RgbSeal>::Client: StrictDecode,
299 <P::Seal as RgbSeal>::Published: StrictDecode,
300 <P::Seal as RgbSeal>::WitnessId: StrictDecode,
301 {
302 let contract_id = articles.contract_id();
303 let genesis_opid = articles.genesis_opid();
304 let ledger = Ledger::new(articles, conf)
305 .map_err(MultiError::with_third)
306 .map_err(MultiError::from_other_a)?;
307 let conf: S::Conf = ledger.config();
308 let mut pile = P::new(conf.into()).map_err(MultiError::C)?;
309 pile.add_seals(genesis_opid, none!());
310 let mut contract = Self { ledger, pile, contract_id };
311 contract
312 .evaluate_commit(consignment.into_operations())
313 .map_err(MultiError::from_a)?;
314 Ok(contract)
315 }
316
317 pub fn issue(
318 issuer: Issuer,
319 params: CreateParams<<P::Seal as RgbSeal>::Definition>,
320 conf: impl FnOnce(&Articles) -> Result<S::Conf, S::Error>,
321 ) -> Result<Self, MultiError<IssuerError, S::Error, P::Error>>
322 where
323 P::Conf: From<S::Conf>,
324 {
325 if !params.issuer.check(issuer.issuer_id()) {
326 return Err(MultiError::A(IssuerError::IssuerMismatch));
327 }
328
329 let seals = SmallOrdMap::try_from_iter(params.owned.iter().enumerate().filter_map(
330 |(pos, assignment)| {
331 assignment
332 .state
333 .seal
334 .to_explicit()
335 .map(|seal| (pos as u16, seal))
336 },
337 ))
338 .expect("too many outputs");
339 let params = IssueParams {
340 issuer: params.issuer,
341 name: params.name,
342 consensus: params.consensus,
343 testnet: params.testnet,
344 timestamp: params.timestamp,
345 core: CoreParams {
346 method: params.method,
347 global: params.global,
348 owned: params
349 .owned
350 .into_iter()
351 .map(|assignment| NamedState {
352 name: assignment.name,
353 state: DataCell {
354 auth: assignment.state.seal.auth_token(),
355 data: assignment.state.data,
356 lock: None,
357 },
358 })
359 .collect(),
360 },
361 };
362
363 let articles = issuer.issue(params);
364 let conf = conf(&articles).map_err(MultiError::B)?;
365 let ledger = Ledger::new(articles, conf)
366 .map_err(MultiError::with_third)
367 .map_err(MultiError::from_other_a)?;
368 let conf: S::Conf = ledger.config();
369 let contract_id = ledger.contract_id();
370
371 let mut pile = P::new(conf.into()).map_err(MultiError::C)?;
373 pile.add_seals(ledger.articles().genesis_opid(), seals);
374
375 Ok(Self { ledger, pile, contract_id })
376 }
377
378 pub fn load(
379 stock_conf: S::Conf,
380 pile_conf: P::Conf,
381 ) -> Result<Self, MultiError<S::Error, P::Error>> {
382 let ledger = Ledger::load(stock_conf).map_err(MultiError::A)?;
383 let contract_id = ledger.contract_id();
384 let pile = P::load(pile_conf).map_err(MultiError::B)?;
385 Ok(Self { ledger, pile, contract_id })
386 }
387
388 #[inline]
389 pub(crate) fn witness_status(&self, wid: <P::Seal as RgbSeal>::WitnessId) -> WitnessStatus {
390 self.pile.witness_status(wid)
391 }
392
393 fn best_op_status(&self, opid: Opid) -> WitnessStatus {
395 self.pile
396 .op_witness_ids(opid)
397 .map(|wid| self.witness_status(wid))
398 .reduce(|best, other| best.best(other))
400 .unwrap_or(WitnessStatus::Genesis)
401 }
402
403 fn retrieve(&self, opid: Opid) -> Option<SealWitness<P::Seal>> {
404 let (status, wid) = self
405 .pile
406 .op_witness_ids(opid)
407 .map(|wid| (self.pile.witness_status(wid), wid))
408 .reduce(|best, other| if best.0.is_better(other.0) { best } else { other })?;
409 if !status.is_valid() {
410 return None;
411 }
412 let client = self.pile.cli_witness(wid);
413 let published = self.pile.pub_witness(wid);
414 Some(SealWitness::new(published, client))
415 }
416
417 pub fn contract_id(&self) -> ContractId { self.contract_id }
418
419 pub fn articles(&self) -> &Articles { self.ledger.articles() }
420
421 pub fn operations(
425 &self,
426 ) -> impl Iterator<Item = (Opid, Operation, OpRels<P::Seal>)> + use<'_, S, P> {
427 self.ledger.operations().map(|(opid, op)| {
428 let rels = self.pile.op_relations(opid, op.destructible_out.len_u16());
429 (opid, op, rels)
430 })
431 }
432
433 pub fn trace(&self) -> impl Iterator<Item = (Opid, Transition)> + use<'_, S, P> {
434 self.ledger.trace()
435 }
436
437 pub fn witness_ids(
438 &self,
439 ) -> impl Iterator<Item = <P::Seal as RgbSeal>::WitnessId> + use<'_, S, P> {
440 self.pile.witness_ids()
441 }
442
443 pub fn witnesses(&self) -> impl Iterator<Item = Witness<P::Seal>> + use<'_, S, P> {
444 self.pile.witnesses()
445 }
446
447 pub fn ops_by_witness_id(
448 &self,
449 wid: <P::Seal as RgbSeal>::WitnessId,
450 ) -> impl Iterator<Item = Opid> + use<'_, S, P> {
451 self.pile.ops_by_witness_id(wid)
452 }
453
454 pub fn op_seals(&self, opid: Opid, up_to: u16) -> OpRels<P::Seal> {
455 self.pile.op_relations(opid, up_to)
456 }
457
458 pub fn seal(&self, seal: &<P::Seal as RgbSeal>::Definition) -> Option<CellAddr> {
459 let auth = seal.auth_token();
460 self.ledger.state().raw.auth.get(&auth).copied()
461 }
462
463 pub fn state(&self) -> ContractState<P::Seal> {
468 let mut cache = bmap! {};
469 let mut ancestor_cache = bmap! {};
470 let mut cached_status = |opid: Opid| {
471 *cache
472 .entry(opid)
473 .or_insert_with(|| self.best_op_status(opid))
474 };
475 let mut get_status = |opid: Opid, or: WitnessStatus| {
476 (*ancestor_cache.entry(opid).or_insert_with(|| {
477 self.ledger
478 .ancestors([opid])
479 .map(|ancestor| self.best_op_status(ancestor))
480 .fold(WitnessStatus::Genesis, |worst, other| worst.worst(other))
481 }))
482 .worst(or)
483 };
484 let state = self.ledger.state().main.clone();
485 let mut owned = bmap! {};
486 for (name, map) in state.owned {
487 let mut state = vec![];
488 for (addr, data) in map {
489 let Some(seal) = self.pile.seal(addr) else {
490 continue;
491 };
492 if let Some(seal) = seal.to_src() {
493 state.push(OwnedState {
494 addr,
495 assignment: Assignment { seal, data },
496 status: get_status(addr.opid, cached_status(addr.opid)),
497 });
498 } else {
499 for wid in self.pile.op_witness_ids(addr.opid) {
501 state.push(OwnedState {
502 addr,
503 assignment: Assignment { seal: seal.resolve(wid), data: data.clone() },
504 status: get_status(addr.opid, self.pile.witness_status(wid)),
505 });
506 }
507 }
508 }
509 owned.insert(name, state);
510 }
511 let mut immutable = bmap! {};
512 for (name, map) in state.global {
513 let mut state = vec![];
514 for (addr, data) in map {
515 let status = get_status(addr.opid, cached_status(addr.opid));
516 state.push(ImmutableState { addr, data, status });
517 }
518 immutable.insert(name, state);
519 }
520 ContractState { immutable, owned, aggregated: state.aggregated }
521 }
522
523 pub fn full_state(&self) -> &EffectiveState { self.ledger.state() }
524
525 pub fn sync(
531 &mut self,
532 changed: impl IntoIterator<Item = (<P::Seal as RgbSeal>::WitnessId, WitnessStatus)>,
533 ) -> Result<(), MultiError<AcceptError, S::Error>> {
534 let mut affected_wids = IndexMap::new();
536 for (wid, status) in changed {
537 if !self.pile.has_witness(wid) {
538 continue;
539 }
540 let prev_status = self.pile.witness_status(wid);
541 if status == prev_status {
542 continue;
543 }
544
545 let old_status = affected_wids.insert(wid, status);
546 debug_assert!(
547 old_status.is_none() || old_status == Some(status),
548 "the same transaction with different status passed to a sync operation"
549 );
550 }
551
552 let mut affected_ops = IndexMap::new();
555 for wid in affected_wids.keys() {
556 for opid in self.pile.ops_by_witness_id(*wid) {
557 let op_status = self.best_op_status(opid);
558 let old = affected_ops.insert(opid, op_status);
559 debug_assert!(old.is_none() || old == Some(op_status));
560 }
561 }
562
563 for (wid, status) in affected_wids {
568 self.pile.update_witness_status(wid, status);
569 }
570
571 let mut roll_back = IndexSet::new();
574 let mut forward = IndexSet::new();
575 for (opid, old_status) in affected_ops {
576 let new_status = self.best_op_status(opid);
577 if old_status.is_valid() == new_status.is_valid() {
578 continue;
579 }
580 if new_status.is_valid() {
581 forward.insert(opid);
582 } else {
583 roll_back.insert(opid);
584 }
585 }
586 debug_assert_eq!(forward.intersection(&roll_back).count(), 0);
587
588 self.ledger.rollback(roll_back).map_err(MultiError::B)?;
590 self.pile.commit_transaction();
592
593 self.ledger.forward(forward)?;
594 self.pile.commit_transaction();
596
597 Ok(())
598 }
599
600 pub fn call(
607 &mut self,
608 call: CallParams,
609 seals: SmallOrdMap<u16, <P::Seal as RgbSeal>::Definition>,
610 ) -> Result<Operation, MultiError<AcceptError, S::Error>> {
611 let opid = self.ledger.call(call)?;
612 let operation = self.ledger.operation(opid);
613 debug_assert_eq!(operation.opid(), opid);
614 self.pile.add_seals(opid, seals);
615 debug_assert_eq!(operation.contract_id, self.contract_id());
616 Ok(operation)
617 }
618
619 pub fn include(
622 &mut self,
623 opid: Opid,
624 anchor: <P::Seal as RgbSeal>::Client,
625 published: &<P::Seal as RgbSeal>::Published,
626 ) {
627 let wid = published.pub_id();
628 let anchor = if self.pile.has_witness(wid) {
629 let mut prev_anchor = self.pile.cli_witness(wid);
630 if prev_anchor != anchor {
631 prev_anchor.merge(anchor).expect(
632 "the existing anchor is not compatible with the new one; this indicates \
633 either a bug in the RGB standard library or a compromised storage",
634 );
635 }
636 prev_anchor
637 } else {
638 anchor
639 };
640 self.pile
641 .add_witness(opid, wid, published, &anchor, WitnessStatus::Tentative);
642 self.pile.commit_transaction();
643 }
644
645 fn aux<W: WriteRaw>(
646 &self,
647 opid: Opid,
648 op: &Operation,
649 mut writer: StrictWriter<W>,
650 ) -> io::Result<StrictWriter<W>> {
651 let seals = self.pile.seals(opid, op.destructible_out.len_u16());
653 writer = seals.strict_encode(writer)?;
654
655 let witness = self.retrieve(opid);
657 writer = witness.is_some().strict_encode(writer)?;
658 if let Some(witness) = witness {
659 writer = witness.strict_encode(writer)?;
660 }
661
662 Ok(writer)
663 }
664
665 pub fn export(&self, writer: StrictWriter<impl WriteRaw>) -> io::Result<()>
672 where
673 <P::Seal as RgbSeal>::Client: StrictDumb + StrictEncode,
674 <P::Seal as RgbSeal>::Published: StrictDumb + StrictEncode,
675 <P::Seal as RgbSeal>::WitnessId: StrictEncode,
676 {
677 self.ledger
678 .export_all_aux(writer, |opid, op, writer| self.aux(opid, op, writer))
679 }
680
681 pub fn consign(
689 &self,
690 terminals: impl IntoIterator<Item = impl Borrow<AuthToken>>,
691 writer: StrictWriter<impl WriteRaw>,
692 ) -> io::Result<()>
693 where
694 <P::Seal as RgbSeal>::Client: StrictDumb + StrictEncode,
695 <P::Seal as RgbSeal>::Published: StrictDumb + StrictEncode,
696 <P::Seal as RgbSeal>::WitnessId: StrictEncode,
697 {
698 self.ledger
699 .export_aux(terminals, writer, |opid, op, writer| self.aux(opid, op, writer))
700 }
701
702 pub fn consume<E>(
718 &mut self,
719 reader: &mut StrictReader<impl ReadRaw>,
720 seal_resolver: impl FnMut(&Operation) -> BTreeMap<u16, <P::Seal as RgbSeal>::Definition>,
721 sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
722 ) -> Result<(), MultiError<ConsumeError<<P::Seal as RgbSeal>::Definition>, S::Error>>
723 where
724 <P::Seal as RgbSeal>::Client: StrictDecode,
725 <P::Seal as RgbSeal>::Published: StrictDecode,
726 <P::Seal as RgbSeal>::WitnessId: StrictDecode,
727 {
728 let contract_id = parse_consignment(reader).map_err(MultiError::from_a)?;
729 if contract_id != self.contract_id() {
730 return Err(MultiError::A(ConsumeError::UnknownContract(contract_id)));
731 }
732 self.consume_internal(reader, seal_resolver, sig_validator)
733 }
734
735 pub(crate) fn consume_internal<E>(
736 &mut self,
737 reader: &mut StrictReader<impl ReadRaw>,
738 seal_resolver: impl FnMut(&Operation) -> BTreeMap<u16, <P::Seal as RgbSeal>::Definition>,
739 sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
740 ) -> Result<(), MultiError<ConsumeError<<P::Seal as RgbSeal>::Definition>, S::Error>>
741 where
742 <P::Seal as RgbSeal>::Client: StrictDecode,
743 <P::Seal as RgbSeal>::Published: StrictDecode,
744 <P::Seal as RgbSeal>::WitnessId: StrictDecode,
745 {
746 let articles = (|| -> Result<Articles, ConsumeError<_>> {
747 let ext_blocks = u8::strict_decode(reader)?;
749 for _ in 0..ext_blocks {
750 let len = u16::strict_decode(reader)?;
751 let r = unsafe { reader.raw_reader() };
752 let _ = r.read_raw::<{ u16::MAX as usize }>(len as usize)?;
753 }
754
755 let semantics = Semantics::strict_decode(reader)?;
757 let sig = Option::<SigBlob>::strict_decode(reader)?;
758
759 let issue_version = ReservedBytes::<1>::strict_decode(reader)?;
760 let meta = ContractMeta::strict_decode(reader)?;
761 let codex = Codex::strict_decode(reader)?;
762
763 let op_reader = OpReader {
764 stream: reader,
765 seal_resolver,
766 count: u32::MAX,
769 _phantom: PhantomData,
770 };
771 self.evaluate_commit(op_reader)?;
772
773 let genesis = self.ledger.articles().genesis().clone();
775 let issue = Issue { version: issue_version, meta, codex, genesis };
776 let articles = Articles::with(semantics, issue, sig, sig_validator)?;
777
778 Ok(articles)
779 })()
780 .map_err(MultiError::A)?;
781
782 self.ledger
787 .upgrade_apis(articles)
788 .map_err(MultiError::from_other_a)?;
789 Ok(())
790 }
791
792 pub(crate) fn evaluate_commit<R: ReadOperation<Seal = P::Seal>>(
793 &mut self,
794 reader: R,
795 ) -> Result<(), VerificationError<P::Seal>>
796 where
797 <P::Seal as RgbSeal>::Client: StrictDecode,
798 <P::Seal as RgbSeal>::Published: StrictDecode,
799 <P::Seal as RgbSeal>::WitnessId: StrictDecode,
800 {
801 self.evaluate(reader)?;
802 self.ledger.commit_transaction();
803 self.pile.commit_transaction();
804 Ok(())
805 }
806}
807
808pub struct OpReader<
809 'r,
810 Seal: RgbSeal,
811 R: ReadRaw,
812 F: FnMut(&Operation) -> BTreeMap<u16, Seal::Definition>,
813> {
814 stream: &'r mut StrictReader<R>,
815 count: u32,
816 seal_resolver: F,
817 _phantom: PhantomData<Seal>,
818}
819
820impl<'r, Seal: RgbSeal, R: ReadRaw, F: FnMut(&Operation) -> BTreeMap<u16, Seal::Definition>>
821 ReadOperation for OpReader<'r, Seal, R, F>
822{
823 type Seal = Seal;
824
825 fn read_operation(
826 &mut self,
827 ) -> Result<Option<OperationSeals<Self::Seal>>, impl Error + 'static> {
828 if self.count == 0 {
829 return Result::<_, DecodeError>::Ok(None);
830 }
831 let operation = Operation::strict_decode(self.stream)?;
832
833 let mut defined_seals = SmallOrdMap::strict_decode(self.stream)?;
834 defined_seals
835 .extend((self.seal_resolver)(&operation))
836 .map_err(|_| {
837 DecodeError::DataIntegrityError(format!(
838 "too many seals defined for the operation {}",
839 operation.opid()
840 ))
841 })?;
842
843 let witness = Option::<SealWitness<Seal>>::strict_decode(self.stream)?;
844
845 if self.count == u32::MAX {
848 self.count = u32::strict_decode(self.stream)?;
849 } else {
850 self.count -= 1;
851 }
852
853 Ok(Some(OperationSeals { operation, defined_seals, witness }))
854 }
855}
856
857impl<S: Stock, P: Pile> ContractApi<P::Seal> for Contract<S, P> {
858 fn contract_id(&self) -> ContractId { self.ledger.contract_id() }
859
860 fn codex(&self) -> &Codex { self.ledger.articles().codex() }
861
862 fn repo(&self) -> &impl LibRepo { self.ledger.articles() }
863
864 fn memory(&self) -> &impl Memory { &self.ledger.state().raw }
865
866 fn is_known(&self, opid: Opid) -> bool { self.ledger.is_valid(opid) }
867
868 fn apply_operation(&mut self, op: VerifiedOperation) {
869 self.ledger.apply(op).expect("unable to apply operation");
870 }
871
872 fn apply_seals(
873 &mut self,
874 opid: Opid,
875 seals: SmallOrdMap<u16, <P::Seal as RgbSeal>::Definition>,
876 ) {
877 self.pile.add_seals(opid, seals);
878 }
879
880 fn apply_witness(&mut self, opid: Opid, witness: SealWitness<P::Seal>) {
881 self.include(opid, witness.client, &witness.published)
882 }
883}
884
885#[derive(Debug, Display, Error, From)]
886#[display(inner)]
887pub enum ConsumeError<Seal: RgbSealDef> {
888 #[from]
889 #[from(io::Error)]
890 Io(IoError),
891
892 #[display(doc_comments)]
894 UnknownContract(ContractId),
895
896 #[from]
897 Semantics(SemanticError),
898
899 #[from]
900 Decode(DecodeError),
901
902 #[from]
903 Verify(VerificationError<Seal::Src>),
904
905 #[from]
906 #[from(IssueError)]
907 Issue(IssuerError),
909}
910
911#[cfg(feature = "binfile")]
912mod fs {
913 use std::path::Path;
914
915 use binfile::BinFile;
916 use strict_encoding::{StreamWriter, StrictDumb, StrictEncode};
917
918 use super::*;
919 use crate::{CONSIGN_MAGIC_NUMBER, CONSIGN_VERSION};
920
921 impl<S: Stock, P: Pile> Contract<S, P> {
922 pub fn export_to_file(&self, path: impl AsRef<Path>) -> io::Result<()>
929 where
930 <P::Seal as RgbSeal>::Client: StrictDumb + StrictEncode,
931 <P::Seal as RgbSeal>::Published: StrictDumb + StrictEncode,
932 <P::Seal as RgbSeal>::WitnessId: StrictEncode,
933 {
934 let file = BinFile::<CONSIGN_MAGIC_NUMBER, CONSIGN_VERSION>::create_new(path)?;
935 let writer = StrictWriter::with(StreamWriter::new::<{ usize::MAX }>(file));
936 self.export(writer)
937 }
938
939 pub fn consign_to_file(
947 &self,
948 path: impl AsRef<Path>,
949 terminals: impl IntoIterator<Item = impl Borrow<AuthToken>>,
950 ) -> io::Result<()>
951 where
952 <P::Seal as RgbSeal>::Client: StrictDumb + StrictEncode,
953 <P::Seal as RgbSeal>::Published: StrictDumb + StrictEncode,
954 <P::Seal as RgbSeal>::WitnessId: StrictEncode,
955 {
956 let file = BinFile::<CONSIGN_MAGIC_NUMBER, CONSIGN_VERSION>::create_new(path)?;
957 let writer = StrictWriter::with(StreamWriter::new::<{ usize::MAX }>(file));
958 self.consign(terminals, writer)
959 }
960 }
961}