rgbstd/interface/
contract.rs

1// RGB standard library for working with smart contracts on Bitcoin & Lightning
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2023 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use std::collections::{BTreeSet, HashSet};
23
24use amplify::confinement::{LargeOrdMap, LargeVec, SmallVec};
25use bp::Outpoint;
26use rgb::{
27    AssignmentType, AttachId, ContractId, ContractState, FungibleOutput, MediaType, RevealedAttach,
28    RevealedData, SealWitness,
29};
30use strict_encoding::FieldName;
31use strict_types::typify::TypedVal;
32use strict_types::{decode, StrictVal};
33
34use crate::interface::IfaceImpl;
35
36#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
37#[display(doc_comments)]
38pub enum ContractError {
39    /// field name {0} is unknown to the contract interface
40    FieldNameUnknown(FieldName),
41
42    #[from]
43    #[display(inner)]
44    Reify(decode::Error),
45}
46
47#[derive(Clone, Eq, PartialEq, Debug, Display, From)]
48#[display(inner)]
49pub enum TypedState {
50    #[display("")]
51    Void,
52    #[from]
53    Amount(u64),
54    #[from]
55    Data(RevealedData),
56    #[from]
57    Attachment(AttachedState),
58}
59
60#[derive(Clone, Eq, PartialEq, Debug, Display)]
61#[display("{id}:{media_type}")]
62pub struct AttachedState {
63    pub id: AttachId,
64    pub media_type: MediaType,
65}
66
67impl From<RevealedAttach> for AttachedState {
68    fn from(attach: RevealedAttach) -> Self {
69        AttachedState {
70            id: attach.id,
71            media_type: attach.media_type,
72        }
73    }
74}
75
76#[derive(Copy, Clone, Eq, PartialEq, Debug)]
77pub struct FungibleAllocation {
78    pub owner: Outpoint,
79    pub witness: SealWitness,
80    pub value: u64,
81}
82
83impl From<FungibleOutput> for FungibleAllocation {
84    fn from(out: FungibleOutput) -> Self { Self::from(&out) }
85}
86
87impl From<&FungibleOutput> for FungibleAllocation {
88    fn from(out: &FungibleOutput) -> Self {
89        FungibleAllocation {
90            owner: out.seal,
91            witness: out.witness,
92            value: out.state.value.as_u64(),
93        }
94    }
95}
96
97pub trait OutpointFilter {
98    fn include_outpoint(&self, outpoint: Outpoint) -> bool;
99}
100
101impl OutpointFilter for Option<&[Outpoint]> {
102    fn include_outpoint(&self, outpoint: Outpoint) -> bool {
103        self.map(|filter| filter.include_outpoint(outpoint))
104            .unwrap_or(true)
105    }
106}
107
108impl OutpointFilter for &[Outpoint] {
109    fn include_outpoint(&self, outpoint: Outpoint) -> bool { self.contains(&outpoint) }
110}
111
112impl OutpointFilter for Vec<Outpoint> {
113    fn include_outpoint(&self, outpoint: Outpoint) -> bool { self.contains(&outpoint) }
114}
115
116impl OutpointFilter for HashSet<Outpoint> {
117    fn include_outpoint(&self, outpoint: Outpoint) -> bool { self.contains(&outpoint) }
118}
119
120impl OutpointFilter for BTreeSet<Outpoint> {
121    fn include_outpoint(&self, outpoint: Outpoint) -> bool { self.contains(&outpoint) }
122}
123
124/// Contract state is an in-memory structure providing API to read structured
125/// data from the [`rgb::ContractHistory`].
126#[derive(Clone, Eq, PartialEq, Debug)]
127pub struct ContractIface {
128    pub state: ContractState,
129    pub iface: IfaceImpl,
130}
131
132impl ContractIface {
133    pub fn contract_id(&self) -> ContractId { self.state.contract_id() }
134
135    /// # Panics
136    ///
137    /// If data are corrupted and contract schema doesn't match interface
138    /// implementations.
139    pub fn global(&self, name: impl Into<FieldName>) -> Result<SmallVec<StrictVal>, ContractError> {
140        let name = name.into();
141        let type_system = &self.state.schema.type_system;
142        let type_id = self
143            .iface
144            .global_type(&name)
145            .ok_or(ContractError::FieldNameUnknown(name))?;
146        let type_schema = self
147            .state
148            .schema
149            .global_types
150            .get(&type_id)
151            .expect("schema doesn't match interface");
152        let state = unsafe { self.state.global_unchecked(type_id) };
153        let state = state
154            .into_iter()
155            .map(|revealed| {
156                type_system
157                    .strict_deserialize_type(type_schema.sem_id, revealed.as_ref())
158                    .map(TypedVal::unbox)
159            })
160            .take(type_schema.max_items as usize)
161            .collect::<Result<Vec<_>, _>>()?;
162        Ok(SmallVec::try_from_iter(state).expect("same or smaller collection size"))
163    }
164
165    pub fn fungible(
166        &self,
167        name: impl Into<FieldName>,
168        filter: &impl OutpointFilter,
169    ) -> Result<LargeVec<FungibleAllocation>, ContractError> {
170        let name = name.into();
171        let type_id = self
172            .iface
173            .assignments_type(&name)
174            .ok_or(ContractError::FieldNameUnknown(name))?;
175        let state = self
176            .state
177            .fungibles()
178            .iter()
179            .filter(|outp| outp.opout.ty == type_id)
180            .filter(|outp| filter.include_outpoint(outp.seal))
181            .map(FungibleAllocation::from);
182        Ok(LargeVec::try_from_iter(state).expect("same or smaller collection size"))
183    }
184
185    // TODO: Add rights, attachments and structured data APIs
186    pub fn outpoint(
187        &self,
188        _outpoint: Outpoint,
189    ) -> LargeOrdMap<AssignmentType, LargeVec<TypedState>> {
190        todo!()
191    }
192}