rgbstd/interface/
builder.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::HashMap;
23
24use amplify::confinement::{Confined, TinyOrdMap, U16, U8};
25use amplify::{confinement, Wrapper};
26use bp::secp256k1::rand::thread_rng;
27use bp::Chain;
28use rgb::{
29    Assign, AssignmentType, Assignments, ContractId, ExposedSeal, FungibleType, Genesis,
30    GenesisSeal, GlobalState, GraphSeal, Input, Inputs, Opout, RevealedData, RevealedValue,
31    StateSchema, SubSchema, Transition, TransitionType, TypedAssigns, BLANK_TRANSITION_ID,
32};
33use strict_encoding::{FieldName, SerializeError, StrictSerialize, TypeName};
34use strict_types::decode;
35
36use crate::containers::{BuilderSeal, Contract};
37use crate::interface::{Iface, IfaceImpl, IfacePair, TransitionIface, TypedState};
38
39#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
40#[display(doc_comments)]
41pub enum BuilderError {
42    /// interface implementation references different interface that the one
43    /// provided to the forge.
44    InterfaceMismatch,
45
46    /// interface implementation references different schema that the one
47    /// provided to the forge.
48    SchemaMismatch,
49
50    /// Global state `{0}` is not known to the schema.
51    GlobalNotFound(FieldName),
52
53    /// Assignment `{0}` is not known to the schema.
54    AssignmentNotFound(FieldName),
55
56    /// transition `{0}` is not known to the schema.
57    TransitionNotFound(TypeName),
58
59    /// state `{0}` provided to the builder has invalid name
60    InvalidStateField(FieldName),
61
62    /// state `{0}` provided to the builder has invalid name
63    InvalidState(AssignmentType),
64
65    /// interface doesn't specifies default operation name, thus an explicit
66    /// operation type must be provided with `set_operation_type` method.
67    NoOperationSubtype,
68
69    /// interface doesn't have a default assignment type.
70    NoDefaultAssignment,
71
72    #[from]
73    #[display(inner)]
74    StrictEncode(SerializeError),
75
76    #[from]
77    #[display(inner)]
78    Reify(decode::Error),
79
80    #[from]
81    #[display(inner)]
82    Confinement(confinement::Error),
83}
84
85#[derive(Clone, Debug)]
86pub struct ContractBuilder {
87    builder: OperationBuilder<GenesisSeal>,
88    chain: Chain,
89}
90
91impl ContractBuilder {
92    pub fn with(iface: Iface, schema: SubSchema, iimpl: IfaceImpl) -> Result<Self, BuilderError> {
93        Ok(Self {
94            builder: OperationBuilder::with(iface, schema, iimpl)?,
95            chain: default!(),
96        })
97    }
98
99    pub fn set_chain(mut self, chain: Chain) -> Self {
100        self.chain = chain;
101        self
102    }
103
104    pub fn assignments_type(&self, name: &FieldName) -> Option<AssignmentType> {
105        let name = self
106            .builder
107            .iface
108            .genesis
109            .assignments
110            .get(name)?
111            .name
112            .as_ref()
113            .unwrap_or(name);
114        self.builder.iimpl.assignments_type(name)
115    }
116
117    pub fn add_global_state(
118        mut self,
119        name: impl Into<FieldName>,
120        value: impl StrictSerialize,
121    ) -> Result<Self, BuilderError> {
122        self.builder = self.builder.add_global_state(name, value)?;
123        Ok(self)
124    }
125
126    pub fn add_fungible_state(
127        mut self,
128        name: impl Into<FieldName>,
129        seal: impl Into<GenesisSeal>,
130        value: u64,
131    ) -> Result<Self, BuilderError> {
132        let name = name.into();
133        let ty = self
134            .assignments_type(&name)
135            .ok_or(BuilderError::AssignmentNotFound(name))?;
136        self.builder = self
137            .builder
138            .add_raw_state(ty, seal.into(), TypedState::Amount(value))?;
139        Ok(self)
140    }
141
142    pub fn add_data_state(
143        mut self,
144        name: impl Into<FieldName>,
145        seal: impl Into<GenesisSeal>,
146        value: impl StrictSerialize,
147    ) -> Result<Self, BuilderError> {
148        let name = name.into();
149        let serialized = value.to_strict_serialized::<U16>()?;
150        let state = RevealedData::from(serialized);
151
152        let ty = self
153            .assignments_type(&name)
154            .ok_or(BuilderError::AssignmentNotFound(name))?;
155        self.builder = self
156            .builder
157            .add_raw_state(ty, seal.into(), TypedState::Data(state))?;
158        Ok(self)
159    }
160
161    pub fn issue_contract(self) -> Result<Contract, BuilderError> {
162        let (schema, iface_pair, global, assignments) = self.builder.complete();
163
164        let genesis = Genesis {
165            ffv: none!(),
166            schema_id: schema.schema_id(),
167            chain: self.chain,
168            metadata: empty!(),
169            globals: global,
170            assignments,
171            valencies: none!(),
172        };
173
174        // TODO: Validate against schema
175
176        let mut contract = Contract::new(schema, genesis);
177        contract.ifaces = tiny_bmap! { iface_pair.iface_id() => iface_pair };
178
179        Ok(contract)
180    }
181}
182
183#[derive(Clone, Debug)]
184pub struct TransitionBuilder {
185    builder: OperationBuilder<GraphSeal>,
186    transition_type: TransitionType,
187    inputs: Inputs,
188}
189
190impl TransitionBuilder {
191    pub fn blank_transition(
192        iface: Iface,
193        schema: SubSchema,
194        iimpl: IfaceImpl,
195    ) -> Result<Self, BuilderError> {
196        Self::with(iface, schema, iimpl, BLANK_TRANSITION_ID)
197    }
198
199    pub fn default_transition(
200        iface: Iface,
201        schema: SubSchema,
202        iimpl: IfaceImpl,
203    ) -> Result<Self, BuilderError> {
204        let transition_type = iface
205            .default_operation
206            .as_ref()
207            .and_then(|name| iimpl.transition_type(name))
208            .ok_or(BuilderError::NoOperationSubtype)?;
209        Self::with(iface, schema, iimpl, transition_type)
210    }
211
212    pub fn named_transition(
213        iface: Iface,
214        schema: SubSchema,
215        iimpl: IfaceImpl,
216        transition_name: impl Into<TypeName>,
217    ) -> Result<Self, BuilderError> {
218        let transition_name = transition_name.into();
219        let transition_type = iimpl
220            .transition_type(&transition_name)
221            .ok_or(BuilderError::TransitionNotFound(transition_name))?;
222        Self::with(iface, schema, iimpl, transition_type)
223    }
224
225    fn with(
226        iface: Iface,
227        schema: SubSchema,
228        iimpl: IfaceImpl,
229        transition_type: TransitionType,
230    ) -> Result<Self, BuilderError> {
231        Ok(Self {
232            builder: OperationBuilder::with(iface, schema, iimpl)?,
233            transition_type,
234            inputs: none!(),
235        })
236    }
237
238    fn transition_iface(&self) -> &TransitionIface {
239        let transition_name = self
240            .builder
241            .iimpl
242            .transition_name(self.transition_type)
243            .expect("reverse type");
244        self.builder
245            .iface
246            .transitions
247            .get(transition_name)
248            .expect("internal inconsistency")
249    }
250
251    pub fn assignments_type(&self, name: &FieldName) -> Option<AssignmentType> {
252        let name = self
253            .transition_iface()
254            .assignments
255            .get(name)?
256            .name
257            .as_ref()
258            .unwrap_or(name);
259        self.builder.iimpl.assignments_type(name)
260    }
261
262    pub fn add_input(mut self, opout: Opout) -> Result<Self, BuilderError> {
263        self.inputs.push(Input::with(opout))?;
264        Ok(self)
265    }
266
267    pub fn default_assignment(&self) -> Result<&FieldName, BuilderError> {
268        self.transition_iface()
269            .default_assignment
270            .as_ref()
271            .ok_or(BuilderError::NoDefaultAssignment)
272    }
273
274    pub fn add_global_state(
275        mut self,
276        name: impl Into<FieldName>,
277        value: impl StrictSerialize,
278    ) -> Result<Self, BuilderError> {
279        self.builder = self.builder.add_global_state(name, value)?;
280        Ok(self)
281    }
282
283    pub fn add_fungible_state_default(
284        self,
285        seal: impl Into<BuilderSeal<GraphSeal>>,
286        value: u64,
287    ) -> Result<Self, BuilderError> {
288        let assignment_name = self.default_assignment()?;
289        let id = self
290            .assignments_type(assignment_name)
291            .ok_or_else(|| BuilderError::InvalidStateField(assignment_name.clone()))?;
292
293        self.add_raw_state(id, seal, TypedState::Amount(value))
294    }
295
296    pub fn add_fungible_state(
297        mut self,
298        name: impl Into<FieldName>,
299        seal: impl Into<BuilderSeal<GraphSeal>>,
300        value: u64,
301    ) -> Result<Self, BuilderError> {
302        let name = name.into();
303        let ty = self
304            .assignments_type(&name)
305            .ok_or(BuilderError::AssignmentNotFound(name))?;
306        self.builder = self
307            .builder
308            .add_raw_state(ty, seal, TypedState::Amount(value))?;
309        Ok(self)
310    }
311
312    pub fn add_data_state(
313        mut self,
314        name: impl Into<FieldName>,
315        seal: impl Into<BuilderSeal<GraphSeal>>,
316        value: impl StrictSerialize,
317    ) -> Result<Self, BuilderError> {
318        let name = name.into();
319        let serialized = value.to_strict_serialized::<U16>()?;
320        let state = RevealedData::from(serialized);
321
322        let ty = self
323            .assignments_type(&name)
324            .ok_or(BuilderError::AssignmentNotFound(name))?;
325        self.builder = self
326            .builder
327            .add_raw_state(ty, seal, TypedState::Data(state))?;
328        Ok(self)
329    }
330
331    pub fn add_raw_state(
332        mut self,
333        type_id: AssignmentType,
334        seal: impl Into<BuilderSeal<GraphSeal>>,
335        state: TypedState,
336    ) -> Result<Self, BuilderError> {
337        self.builder = self.builder.add_raw_state(type_id, seal, state)?;
338        Ok(self)
339    }
340
341    pub fn complete_transition(self, contract_id: ContractId) -> Result<Transition, BuilderError> {
342        let (_, _, global, assignments) = self.builder.complete();
343
344        let transition = Transition {
345            ffv: none!(),
346            contract_id,
347            transition_type: self.transition_type,
348            metadata: empty!(),
349            globals: global,
350            inputs: self.inputs,
351            assignments,
352            valencies: none!(),
353        };
354
355        // TODO: Validate against schema
356
357        Ok(transition)
358    }
359}
360
361#[derive(Clone, Debug)]
362struct OperationBuilder<Seal: ExposedSeal> {
363    // TODO: use references instead of owned values
364    schema: SubSchema,
365    iface: Iface,
366    iimpl: IfaceImpl,
367
368    global: GlobalState,
369    // rights: TinyOrdMap<AssignmentType, Confined<HashSet<BuilderSeal<Seal>>, 1, U8>>,
370    fungible:
371        TinyOrdMap<AssignmentType, Confined<HashMap<BuilderSeal<Seal>, RevealedValue>, 1, U8>>,
372    data: TinyOrdMap<AssignmentType, Confined<HashMap<BuilderSeal<Seal>, RevealedData>, 1, U8>>,
373    // TODO: add attachments
374    // TODO: add valencies
375}
376
377impl<Seal: ExposedSeal> OperationBuilder<Seal> {
378    pub fn with(iface: Iface, schema: SubSchema, iimpl: IfaceImpl) -> Result<Self, BuilderError> {
379        if iimpl.iface_id != iface.iface_id() {
380            return Err(BuilderError::InterfaceMismatch);
381        }
382        if iimpl.schema_id != schema.schema_id() {
383            return Err(BuilderError::SchemaMismatch);
384        }
385
386        // TODO: check schema internal consistency
387        // TODO: check interface internal consistency
388        // TODO: check implmenetation internal consistency
389
390        Ok(OperationBuilder {
391            schema,
392            iface,
393            iimpl,
394
395            global: none!(),
396            fungible: none!(),
397            data: none!(),
398        })
399    }
400
401    pub fn add_global_state(
402        mut self,
403        name: impl Into<FieldName>,
404        value: impl StrictSerialize,
405    ) -> Result<Self, BuilderError> {
406        let name = name.into();
407        let serialized = value.to_strict_serialized::<{ u16::MAX as usize }>()?;
408
409        // Check value matches type requirements
410        let Some(type_id) = self
411            .iimpl
412            .global_state
413            .iter()
414            .find(|t| t.name == name)
415            .map(|t| t.id)
416        else {
417            return Err(BuilderError::GlobalNotFound(name));
418        };
419        let sem_id = self
420            .schema
421            .global_types
422            .get(&type_id)
423            .expect("schema should match interface: must be checked by the constructor")
424            .sem_id;
425        self.schema
426            .type_system
427            .strict_deserialize_type(sem_id, &serialized)?;
428
429        self.global.add_state(type_id, serialized.into())?;
430
431        Ok(self)
432    }
433
434    pub fn add_raw_state(
435        mut self,
436        type_id: AssignmentType,
437        seal: impl Into<BuilderSeal<Seal>>,
438        state: TypedState,
439    ) -> Result<Self, BuilderError> {
440        match state {
441            TypedState::Void => {
442                todo!()
443            }
444            TypedState::Amount(value) => {
445                let state = RevealedValue::new(value, &mut thread_rng());
446
447                let state_schema =
448                    self.schema.owned_types.get(&type_id).expect(
449                        "schema should match interface: must be checked by the constructor",
450                    );
451                if *state_schema != StateSchema::Fungible(FungibleType::Unsigned64Bit) {
452                    return Err(BuilderError::InvalidState(type_id));
453                }
454
455                match self.fungible.get_mut(&type_id) {
456                    Some(assignments) => {
457                        assignments.insert(seal.into(), state)?;
458                    }
459                    None => {
460                        self.fungible
461                            .insert(type_id, Confined::with((seal.into(), state)))?;
462                    }
463                }
464            }
465            TypedState::Data(data) => {
466                let state_schema =
467                    self.schema.owned_types.get(&type_id).expect(
468                        "schema should match interface: must be checked by the constructor",
469                    );
470
471                if let StateSchema::Structured(_) = *state_schema {
472                    match self.data.get_mut(&type_id) {
473                        Some(assignments) => {
474                            assignments.insert(seal.into(), data)?;
475                        }
476                        None => {
477                            self.data
478                                .insert(type_id, Confined::with((seal.into(), data)))?;
479                        }
480                    }
481                } else {
482                    return Err(BuilderError::InvalidState(type_id));
483                }
484            }
485            TypedState::Attachment(_) => {
486                todo!()
487            }
488        }
489        Ok(self)
490    }
491
492    fn complete(self) -> (SubSchema, IfacePair, GlobalState, Assignments<Seal>) {
493        let owned_state = self.fungible.into_iter().map(|(id, vec)| {
494            let vec = vec.into_iter().map(|(seal, value)| match seal {
495                BuilderSeal::Revealed(seal) => Assign::Revealed { seal, state: value },
496                BuilderSeal::Concealed(seal) => Assign::ConfidentialSeal { seal, state: value },
497            });
498            let state = Confined::try_from_iter(vec).expect("at least one element");
499            let state = TypedAssigns::Fungible(state);
500            (id, state)
501        });
502        let owned_state_data = self.data.into_iter().map(|(id, vec)| {
503            let vec_data = vec.into_iter().map(|(seal, value)| match seal {
504                BuilderSeal::Revealed(seal) => Assign::Revealed { seal, state: value },
505                BuilderSeal::Concealed(seal) => Assign::ConfidentialSeal { seal, state: value },
506            });
507            let state_data = Confined::try_from_iter(vec_data).expect("at least one element");
508            let state_data = TypedAssigns::Structured(state_data);
509            (id, state_data)
510        });
511
512        let owned_state = Confined::try_from_iter(owned_state).expect("same size");
513        let owned_state_data = Confined::try_from_iter(owned_state_data).expect("same size");
514
515        let mut assignments = Assignments::from_inner(owned_state);
516        assignments
517            .extend(Assignments::from_inner(owned_state_data).into_inner())
518            .expect("");
519
520        let iface_pair = IfacePair::with(self.iface, self.iimpl);
521
522        (self.schema, iface_pair, self.global, assignments)
523    }
524}