rgb/
consignment.rs

1// Standard Library for RGB smart contracts
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland.
9// Copyright (C) 2024-2025 LNP/BP Laboratories,
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2025 RGB Consortium, Switzerland.
12// Copyright (C) 2019-2025 Dr Maxim Orlovsky.
13// All rights under the above copyrights are reserved.
14//
15// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
16// in compliance with the License. You may obtain a copy of the License at
17//
18//        http://www.apache.org/licenses/LICENSE-2.0
19//
20// Unless required by applicable law or agreed to in writing, software distributed under the License
21// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
22// or implied. See the License for the specific language governing permissions and limitations under
23// the License.
24
25use 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}