1use 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 InterfaceMismatch,
45
46 SchemaMismatch,
49
50 GlobalNotFound(FieldName),
52
53 AssignmentNotFound(FieldName),
55
56 TransitionNotFound(TypeName),
58
59 InvalidStateField(FieldName),
61
62 InvalidState(AssignmentType),
64
65 NoOperationSubtype,
68
69 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 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 Ok(transition)
358 }
359}
360
361#[derive(Clone, Debug)]
362struct OperationBuilder<Seal: ExposedSeal> {
363 schema: SubSchema,
365 iface: Iface,
366 iimpl: IfaceImpl,
367
368 global: GlobalState,
369 fungible:
371 TinyOrdMap<AssignmentType, Confined<HashMap<BuilderSeal<Seal>, RevealedValue>, 1, U8>>,
372 data: TinyOrdMap<AssignmentType, Confined<HashMap<BuilderSeal<Seal>, RevealedData>, 1, U8>>,
373 }
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 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 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}