1use std::convert::Infallible;
25use std::ops::{Deref, DerefMut};
26
27use amplify::confinement::SmallVec;
28use amplify::num::u256;
29use chrono::{DateTime, Utc};
30use strict_encoding::TypeName;
31use strict_types::{StrictVal, TypeSystem};
32use ultrasonic::{
33 fe256, AuthToken, CallId, CellAddr, CellLock, CodexId, Consensus, ContractId, ContractMeta, ContractName, Genesis,
34 Identity, Input, Issue, Operation, StateCell, StateData, StateValue,
35};
36
37use crate::{Api, Articles, DataCell, Issuer, IssuerId, MethodName, StateAtom, StateName};
38
39#[derive(Clone, PartialEq, Eq, Debug)]
40#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
41pub struct NamedState<T> {
42 pub name: StateName,
43 #[cfg_attr(feature = "serde", serde(flatten))]
44 pub state: T,
45}
46
47impl NamedState<DataCell> {
48 pub fn new_unlocked(name: impl Into<StateName>, auth: impl Into<AuthToken>, data: impl Into<StrictVal>) -> Self {
49 NamedState { name: name.into(), state: DataCell::new_unlocked(auth, data) }
50 }
51}
52
53#[derive(Clone, PartialEq, Eq, Debug)]
54#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
55pub struct CoreParams {
56 pub method: MethodName,
57 pub global: Vec<NamedState<StateAtom>>,
58 pub owned: Vec<NamedState<DataCell>>,
59}
60
61impl CoreParams {
62 pub fn new(method: impl Into<MethodName>) -> Self {
63 Self { method: method.into(), global: none!(), owned: none!() }
64 }
65
66 pub fn push_global_verified(&mut self, name: impl Into<StateName>, state: impl Into<StateAtom>) {
67 self.global
68 .push(NamedState { name: name.into(), state: state.into() });
69 }
70
71 pub fn push_owned_unlocked(
72 &mut self,
73 name: impl Into<StateName>,
74 auth: impl Into<AuthToken>,
75 data: impl Into<StrictVal>,
76 ) {
77 self.owned.push(NamedState::new_unlocked(name, auth, data));
78 }
79}
80
81#[derive(Copy, Clone, PartialEq, Eq, Debug)]
82#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase", untagged))]
83pub enum VersionRange {
84 Range { min: u16, max: u16 },
85 After { min: u16 },
86 Before { max: u16 },
87}
88
89#[derive(Copy, Clone, PartialEq, Eq, Debug, From)]
90#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase", untagged))]
91pub enum IssuerSpec {
92 #[from]
93 Exact(IssuerId),
94
95 #[from]
96 Latest(CodexId),
97
98 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
99 ExactVer { codex_id: CodexId, version: u16 },
100
101 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
102 VersionRange { codex_id: CodexId, version: VersionRange },
103}
104
105impl IssuerSpec {
106 pub fn check(&self, issuer_id: IssuerId) -> bool {
107 match self {
108 IssuerSpec::Exact(id) => *id == issuer_id,
109 IssuerSpec::Latest(codex_id) => *codex_id == issuer_id.codex_id,
110 IssuerSpec::ExactVer { codex_id, version } => {
111 *codex_id == issuer_id.codex_id && *version == issuer_id.version
112 }
113 IssuerSpec::VersionRange { codex_id, version: VersionRange::After { min } } => {
114 *codex_id == issuer_id.codex_id && issuer_id.version >= *min
115 }
116 IssuerSpec::VersionRange { codex_id, version: VersionRange::Before { max } } => {
117 *codex_id == issuer_id.codex_id && issuer_id.version < *max
118 }
119 IssuerSpec::VersionRange { codex_id, version: VersionRange::Range { min, max } } => {
120 *codex_id == issuer_id.codex_id && (*min..*max).contains(&issuer_id.version)
121 }
122 }
123 }
124
125 pub fn codex_id(&self) -> CodexId {
126 match self {
127 IssuerSpec::Exact(id) => id.codex_id,
128 IssuerSpec::Latest(codex_id) => *codex_id,
129 IssuerSpec::ExactVer { codex_id, .. } => *codex_id,
130 IssuerSpec::VersionRange { codex_id, .. } => *codex_id,
131 }
132 }
133}
134
135#[derive(Clone, PartialEq, Eq, Debug)]
136#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
137pub struct IssueParams {
138 pub issuer: IssuerSpec,
139 pub name: TypeName,
140 pub consensus: Consensus,
141 pub testnet: bool,
142 pub timestamp: Option<DateTime<Utc>>,
143 #[cfg_attr(feature = "serde", serde(flatten))]
144 pub core: CoreParams,
145}
146
147impl Deref for IssueParams {
148 type Target = CoreParams;
149
150 fn deref(&self) -> &Self::Target { &self.core }
151}
152
153impl DerefMut for IssueParams {
154 fn deref_mut(&mut self) -> &mut Self::Target { &mut self.core }
155}
156
157impl IssueParams {
158 pub fn new_testnet(codex_id: CodexId, name: impl Into<TypeName>, consensus: Consensus) -> Self {
159 Self {
160 issuer: IssuerSpec::Latest(codex_id),
161 name: name.into(),
162 consensus,
163 testnet: true,
164 timestamp: None,
165 core: CoreParams::new("issue"),
166 }
167 }
168
169 pub fn set_timestamp(&mut self, timestamp: DateTime<Utc>) { self.timestamp = Some(timestamp); }
170
171 pub fn set_timestamp_now(&mut self) { self.timestamp = Some(Utc::now()); }
172}
173
174impl Issuer {
175 pub fn start_issue(self, method: impl Into<MethodName>, consensus: Consensus, testnet: bool) -> IssueBuilder {
176 let builder = Builder::new(self.call_id(method));
177 IssueBuilder { builder, issuer: self, testnet, consensus }
178 }
179
180 pub fn start_issue_mainnet(self, method: impl Into<MethodName>, consensus: Consensus) -> IssueBuilder {
181 self.start_issue(method, consensus, false)
182 }
183 pub fn start_issue_testnet(self, method: impl Into<MethodName>, consensus: Consensus) -> IssueBuilder {
184 self.start_issue(method, consensus, true)
185 }
186
187 pub fn issue(self, params: IssueParams) -> Articles {
188 if !params.issuer.check(self.issuer_id()) {
189 panic!("issuer version does not match requested version");
190 }
191
192 let mut builder = self.start_issue(params.core.method, params.consensus, params.testnet);
193
194 for NamedState { name, state } in params.core.global {
195 builder = builder.append(name, state.verified, state.unverified)
196 }
197 for NamedState { name, state } in params.core.owned {
198 builder = builder.assign(name, state.auth, state.data, state.lock)
199 }
200
201 let timestamp = params.timestamp.unwrap_or_else(Utc::now).timestamp();
202 builder.finish(params.name, timestamp)
203 }
204}
205
206#[derive(Clone, Debug)]
207pub struct IssueBuilder {
208 builder: Builder,
209 issuer: Issuer,
210 testnet: bool,
211 consensus: Consensus,
212}
213
214impl IssueBuilder {
215 pub fn append(mut self, name: impl Into<StateName>, data: StrictVal, raw: Option<StrictVal>) -> Self {
216 self.builder = self
217 .builder
218 .add_global(name, data, raw, self.issuer.default_api(), self.issuer.types());
219 self
220 }
221
222 pub fn assign(
223 mut self,
224 name: impl Into<StateName>,
225 auth: AuthToken,
226 data: StrictVal,
227 lock: Option<CellLock>,
228 ) -> Self {
229 self.builder = self
230 .builder
231 .add_owned(name, auth, data, lock, self.issuer.default_api(), self.issuer.types());
232 self
233 }
234
235 pub fn finish(self, name: impl Into<TypeName>, timestamp: i64) -> Articles {
236 let meta = ContractMeta {
237 consensus: self.consensus,
238 testnet: self.testnet,
239 timestamp,
240 features: default!(),
241 name: ContractName::Named(name.into()),
242 issuer: Identity::default(),
243 };
244 let genesis = self.builder.issue_genesis(self.issuer.codex_id());
245 let (codex, semantics) = self.issuer.dismember();
246 let issue = Issue { version: default!(), meta, codex, genesis };
247 Articles::with(semantics, issue, None, |_, _, _| -> Result<_, Infallible> { unreachable!() })
248 .expect("broken issue builder")
249 }
250}
251
252#[derive(Clone, Debug)]
253pub struct Builder {
254 call_id: CallId,
255 destructible_out: SmallVec<StateCell>,
256 immutable_out: SmallVec<StateData>,
257}
258
259impl Builder {
260 pub fn new(call_id: CallId) -> Self { Builder { call_id, destructible_out: none!(), immutable_out: none!() } }
261
262 pub fn add_global(
263 mut self,
264 name: impl Into<StateName>,
265 data: StrictVal,
266 raw: Option<StrictVal>,
267 api: &Api,
268 sys: &TypeSystem,
269 ) -> Self {
270 let name = name.into();
271 let data = api
272 .build_immutable(name.clone(), data, raw, sys)
273 .unwrap_or_else(|e| panic!("invalid immutable state '{name}'; {e}"));
274 self.immutable_out
275 .push(data)
276 .expect("too many state elements");
277 self
278 }
279
280 pub fn add_owned(
281 mut self,
282 name: impl Into<StateName>,
283 auth: AuthToken,
284 data: StrictVal,
285 lock: Option<CellLock>,
286 api: &Api,
287 sys: &TypeSystem,
288 ) -> Self {
289 let data = api
290 .build_destructible(name, data, sys)
291 .expect("invalid destructible state");
292 let cell = StateCell { data, auth, lock };
293 self.destructible_out
294 .push(cell)
295 .expect("too many state elements");
296 self
297 }
298
299 pub fn issue_genesis(self, codex_id: CodexId) -> Genesis {
300 Genesis {
301 version: default!(),
302 codex_id,
303 call_id: self.call_id,
304 nonce: fe256::from(u256::ZERO),
305 blank0: zero!(),
306 blank1: zero!(),
307 blank2: zero!(),
308 destructible_out: self.destructible_out,
309 immutable_out: self.immutable_out,
310 }
311 }
312}
313
314#[derive(Clone, Debug)]
315pub struct BuilderRef<'c> {
316 type_system: &'c TypeSystem,
317 api: &'c Api,
318 inner: Builder,
319}
320
321impl<'c> BuilderRef<'c> {
322 pub fn new(api: &'c Api, call_id: CallId, sys: &'c TypeSystem) -> Self {
323 BuilderRef { type_system: sys, api, inner: Builder::new(call_id) }
324 }
325
326 pub fn add_global(mut self, name: impl Into<StateName>, data: StrictVal, raw: Option<StrictVal>) -> Self {
327 self.inner = self
328 .inner
329 .add_global(name, data, raw, self.api, self.type_system);
330 self
331 }
332
333 pub fn add_owned(
334 mut self,
335 name: impl Into<StateName>,
336 auth: AuthToken,
337 data: StrictVal,
338 lock: Option<CellLock>,
339 ) -> Self {
340 self.inner = self
341 .inner
342 .add_owned(name, auth, data, lock, self.api, self.type_system);
343 self
344 }
345
346 pub fn issue_genesis(self, codex_id: CodexId) -> Genesis { self.inner.issue_genesis(codex_id) }
347}
348
349#[derive(Clone, Debug)]
350pub struct OpBuilder {
351 contract_id: ContractId,
352 destructible_in: SmallVec<Input>,
353 immutable_in: SmallVec<CellAddr>,
354 inner: Builder,
355}
356
357impl OpBuilder {
358 pub fn new(contract_id: ContractId, call_id: CallId) -> Self {
359 let inner = Builder::new(call_id);
360 Self {
361 contract_id,
362 destructible_in: none!(),
363 immutable_in: none!(),
364 inner,
365 }
366 }
367
368 pub fn add_global(
369 mut self,
370 name: impl Into<StateName>,
371 data: StrictVal,
372 raw: Option<StrictVal>,
373 api: &Api,
374 sys: &TypeSystem,
375 ) -> Self {
376 self.inner = self.inner.add_global(name, data, raw, api, sys);
377 self
378 }
379
380 pub fn add_owned(
381 mut self,
382 name: impl Into<StateName>,
383 auth: AuthToken,
384 data: StrictVal,
385 lock: Option<CellLock>,
386 api: &Api,
387 sys: &TypeSystem,
388 ) -> Self {
389 self.inner = self.inner.add_owned(name, auth, data, lock, api, sys);
390 self
391 }
392
393 pub fn access(mut self, addr: CellAddr) -> Self {
394 self.immutable_in
395 .push(addr)
396 .expect("number of read memory cells exceeds 64k limit");
397 self
398 }
399
400 pub fn destroy(mut self, addr: CellAddr) -> Self {
401 let input = Input { addr, witness: StateValue::None };
402 self.destructible_in
403 .push(input)
404 .expect("the number of inputs exceeds the 64k limit");
405 self
406 }
407
408 pub fn destroy_satisfy(
409 mut self,
410 addr: CellAddr,
411 name: impl Into<StateName>,
412 witness: StrictVal,
413 api: &Api,
414 sys: &TypeSystem,
415 ) -> Self {
416 let witness = api
417 .build_witness(name, witness, sys)
418 .expect("invalid witness data");
419 let input = Input { addr, witness };
420 self.destructible_in
421 .push(input)
422 .expect("the number of inputs exceeds the 64k limit");
423 self
424 }
425
426 pub fn finalize(self) -> Operation {
427 Operation {
428 version: default!(),
429 contract_id: self.contract_id,
430 call_id: self.inner.call_id,
431 nonce: fe256::from(u256::ZERO),
432 witness: none!(),
433 destructible_in: self.destructible_in,
434 immutable_in: self.immutable_in,
435 destructible_out: self.inner.destructible_out,
436 immutable_out: self.inner.immutable_out,
437 }
438 }
439}
440
441#[derive(Clone, Debug)]
442pub struct OpBuilderRef<'c> {
443 type_system: &'c TypeSystem,
444 api: &'c Api,
445 inner: OpBuilder,
446}
447
448impl<'c> OpBuilderRef<'c> {
449 pub fn new(api: &'c Api, contract_id: ContractId, call_id: CallId, sys: &'c TypeSystem) -> Self {
450 let inner = OpBuilder::new(contract_id, call_id);
451 Self { api, type_system: sys, inner }
452 }
453
454 pub fn add_global(mut self, name: impl Into<StateName>, data: StrictVal, raw: Option<StrictVal>) -> Self {
455 self.inner = self
456 .inner
457 .add_global(name, data, raw, self.api, self.type_system);
458 self
459 }
460
461 pub fn add_owned(
462 mut self,
463 name: impl Into<StateName>,
464 auth: AuthToken,
465 data: StrictVal,
466 lock: Option<CellLock>,
467 ) -> Self {
468 self.inner = self
469 .inner
470 .add_owned(name, auth, data, lock, self.api, self.type_system);
471 self
472 }
473
474 pub fn access(mut self, addr: CellAddr) -> Self {
475 self.inner = self.inner.access(addr);
476 self
477 }
478
479 pub fn destroy(mut self, addr: CellAddr) -> Self {
480 self.inner = self.inner.destroy(addr);
481 self
482 }
483
484 pub fn destroy_satisfy(
485 mut self,
486 addr: CellAddr,
487 name: impl Into<StateName>,
488 witness: StrictVal,
489 api: &Api,
490 sys: &TypeSystem,
491 ) -> Self {
492 self.inner = self.inner.destroy_satisfy(addr, name, witness, api, sys);
493 self
494 }
495
496 pub fn finalize(self) -> Operation { self.inner.finalize() }
497}
498
499#[cfg(test)]
500mod test {
501 #![cfg_attr(coverage_nightly, coverage(off))]
502
503 use core::str::FromStr;
504
505 use super::*;
506 use crate::ApisChecksum;
507
508 #[test]
509 fn issuer_spec_yaml_latest() {
510 let val = IssuerSpec::Latest(strict_dumb!());
511 let s = "AAAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA#origami-bruno-life
512";
513 assert_eq!(serde_yaml::to_string(&val).unwrap(), s);
514 assert_eq!(serde_yaml::from_str::<IssuerSpec>(s).unwrap(), val);
515 }
516
517 #[test]
518 fn issuer_spec_yaml_exact() {
519 let val = IssuerSpec::Exact(strict_dumb!());
520 let s = "\
521codexId: AAAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA#origami-bruno-life
522version: 0
523checksum: AAAAAA
524";
525 assert_eq!(serde_yaml::to_string(&val).unwrap(), s);
526 assert_eq!(serde_yaml::from_str::<IssuerSpec>(s).unwrap(), val);
527 }
528
529 #[test]
530 fn issuer_spec_yaml_ver_nosum() {
531 let val = IssuerSpec::ExactVer { codex_id: strict_dumb!(), version: 0 };
532 let s = "\
533codexId: AAAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA#origami-bruno-life
534version: 0
535";
536 assert_eq!(serde_yaml::to_string(&val).unwrap(), s);
537 assert_eq!(serde_yaml::from_str::<IssuerSpec>(s).unwrap(), val);
538 }
539
540 #[test]
541 fn issuer_spec_yaml_min() {
542 let val = IssuerSpec::VersionRange {
543 codex_id: strict_dumb!(),
544 version: VersionRange::After { min: 0 },
545 };
546 let s = "\
547codexId: AAAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA#origami-bruno-life
548version:
549 min: 0
550";
551 assert_eq!(serde_yaml::to_string(&val).unwrap(), s);
552 assert_eq!(serde_yaml::from_str::<IssuerSpec>(s).unwrap(), val);
553 }
554
555 #[test]
556 fn issuer_spec_yaml_max() {
557 let val = IssuerSpec::VersionRange {
558 codex_id: strict_dumb!(),
559 version: VersionRange::Before { max: 1 },
560 };
561 let s = "\
562codexId: AAAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA#origami-bruno-life
563version:
564 max: 1
565";
566 assert_eq!(serde_yaml::to_string(&val).unwrap(), s);
567 assert_eq!(serde_yaml::from_str::<IssuerSpec>(s).unwrap(), val);
568 }
569
570 #[test]
571 fn issuer_spec_yaml_range() {
572 let val = IssuerSpec::VersionRange {
573 codex_id: strict_dumb!(),
574 version: VersionRange::Range { min: 0, max: 1 },
575 };
576 let s = "\
577codexId: AAAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA#origami-bruno-life
578version:
579 min: 0
580 max: 1
581";
582 assert_eq!(serde_yaml::to_string(&val).unwrap(), s);
583 assert_eq!(serde_yaml::from_str::<IssuerSpec>(s).unwrap(), val);
584 }
585
586 #[test]
587 fn issuer_display_fromstr() {
588 let s = "nmThRWDr-0hOJgJt-OFVCZTA-XX8aOWj-bkqWzK7-_jAtdhQ/0#NRIsWA";
589 let issuer_id = IssuerId::from_str(s).unwrap();
590 assert_eq!(issuer_id.to_string(), s);
591 assert_eq!(issuer_id.codex_id, CodexId::from_str(s.split_once("/").unwrap().0).unwrap());
592 assert_eq!(issuer_id.checksum, ApisChecksum::from_str(s.split_once("#").unwrap().1).unwrap());
593 assert_eq!(issuer_id.version, 0);
594 }
595
596 #[test]
597 fn issuer_check() {
598 let s = "nmThRWDr-0hOJgJt-OFVCZTA-XX8aOWj-bkqWzK7-_jAtdhQ/0#NRIsWA";
599 let issuer_id = IssuerId::from_str(s).unwrap();
600 assert!(IssuerSpec::Exact(issuer_id).check(issuer_id));
601
602 let mut changed_ver = issuer_id;
603 changed_ver.version += 1;
604 assert!(!IssuerSpec::Exact(issuer_id).check(changed_ver));
605 assert!(IssuerSpec::Latest(issuer_id.codex_id).check(changed_ver));
606 assert!(!IssuerSpec::ExactVer { codex_id: issuer_id.codex_id, version: issuer_id.version }.check(changed_ver));
607
608 let mut changed_sum = issuer_id;
609 changed_sum.checksum = ApisChecksum::from_str("rLAuRQ").unwrap();
610 assert!(!IssuerSpec::Exact(issuer_id).check(changed_sum));
611 assert!(IssuerSpec::Latest(issuer_id.codex_id).check(changed_sum));
612 assert!(IssuerSpec::ExactVer { codex_id: issuer_id.codex_id, version: issuer_id.version }.check(changed_sum));
613 }
614}