1use core::cmp::Ordering;
37use core::fmt::Debug;
38use core::hash::{Hash, Hasher};
39
40use amplify::confinement::{ConfinedBlob, TinyOrdMap, TinyString, U16 as U16MAX};
41use amplify::num::u256;
42use amplify::Bytes32;
43use commit_verify::{CommitId, ReservedBytes};
44use sonic_callreq::{CallState, MethodName, StateName};
45use strict_types::{SemId, StrictDecode, StrictDumb, StrictEncode, StrictVal, TypeName, TypeSystem};
46use ultrasonic::{CallId, CodexId, Identity, StateData, StateValue};
47
48use crate::embedded::EmbeddedProc;
49use crate::{StateAtom, VmType, LIB_NAME_SONIC};
50
51pub(super) const USED_FIEL_BYTES: usize = u256::BYTES as usize - 2;
52pub(super) const TOTAL_BYTES: usize = USED_FIEL_BYTES * 3;
53
54#[derive(Clone, Debug, From)]
55#[derive(CommitEncode)]
56#[commit_encode(strategy = strict, id = ApiId)]
57#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
58#[strict_type(lib = LIB_NAME_SONIC, tags = custom, dumb = Self::Embedded(strict_dumb!()))]
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
60pub enum Api {
61 #[from]
62 #[strict_type(tag = 1)]
63 Embedded(ApiInner<EmbeddedProc>),
64
65 #[from]
66 #[strict_type(tag = 2)]
67 Alu(ApiInner<aluvm::Vm>),
68}
69
70impl PartialEq for Api {
71 fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal }
72}
73impl Eq for Api {}
74impl PartialOrd for Api {
75 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
76}
77impl Ord for Api {
78 fn cmp(&self, other: &Self) -> Ordering {
79 if self.api_id() == other.api_id() {
80 Ordering::Equal
81 } else {
82 self.timestamp().cmp(&other.timestamp())
83 }
84 }
85}
86impl Hash for Api {
87 fn hash<H: Hasher>(&self, state: &mut H) { self.api_id().hash(state); }
88}
89
90impl Api {
91 pub fn api_id(&self) -> ApiId { self.commit_id() }
92
93 pub fn vm_type(&self) -> VmType {
94 match self {
95 Api::Embedded(_) => VmType::Embedded,
96 Api::Alu(_) => VmType::AluVM,
97 }
98 }
99
100 pub fn codex_id(&self) -> CodexId {
101 match self {
102 Api::Embedded(api) => api.codex_id,
103 Api::Alu(api) => api.codex_id,
104 }
105 }
106
107 pub fn timestamp(&self) -> i64 {
108 match self {
109 Api::Embedded(api) => api.timestamp,
110 Api::Alu(api) => api.timestamp,
111 }
112 }
113
114 pub fn name(&self) -> Option<&TypeName> {
115 match self {
116 Api::Embedded(api) => api.name.as_ref(),
117 Api::Alu(api) => api.name.as_ref(),
118 }
119 }
120
121 pub fn developer(&self) -> &Identity {
122 match self {
123 Api::Embedded(api) => &api.developer,
124 Api::Alu(api) => &api.developer,
125 }
126 }
127
128 pub fn default_call(&self) -> Option<&CallState> {
129 match self {
130 Api::Embedded(api) => api.default_call.as_ref(),
131 Api::Alu(api) => api.default_call.as_ref(),
132 }
133 }
134
135 pub fn verifier(&self, method: impl Into<MethodName>) -> Option<CallId> {
136 let method = method.into();
137 match self {
138 Api::Embedded(api) => api.verifiers.get(&method),
139 Api::Alu(api) => api.verifiers.get(&method),
140 }
141 .copied()
142 }
143
144 pub fn readers(&self) -> Box<dyn Iterator<Item = &MethodName> + '_> {
145 match self {
146 Api::Embedded(api) => Box::new(api.readers.keys()),
147 Api::Alu(api) => Box::new(api.readers.keys()),
148 }
149 }
150
151 pub fn read<'s, I: IntoIterator<Item = &'s StateAtom>>(
152 &self,
153 name: &StateName,
154 state: impl Fn(&StateName) -> I,
155 ) -> StrictVal {
156 match self {
157 Api::Embedded(api) => api
158 .readers
159 .get(name)
160 .expect("state name is unknown for the API")
161 .read(state),
162 Api::Alu(api) => api
163 .readers
164 .get(name)
165 .expect("state name is unknown for the API")
166 .read(state),
167 }
168 }
169
170 pub fn convert_immutable(&self, data: &StateData, sys: &TypeSystem) -> Option<(StateName, StateAtom)> {
171 match self {
172 Api::Embedded(api) => {
173 for (name, adaptor) in &api.append_only {
174 if let Some(atom) = adaptor.convert(data, sys) {
175 return Some((name.clone(), atom));
176 }
177 }
178 None
179 }
180 Api::Alu(api) => {
181 for (name, adaptor) in &api.append_only {
182 if let Some(atom) = adaptor.convert(data, sys) {
183 return Some((name.clone(), atom));
184 }
185 }
186 None
187 }
188 }
189 }
190
191 pub fn convert_destructible(&self, value: StateValue, sys: &TypeSystem) -> Option<(StateName, StrictVal)> {
192 match self {
197 Api::Embedded(api) => {
198 for (name, adaptor) in &api.destructible {
199 if let Some(atom) = adaptor.convert(value, sys) {
200 return Some((name.clone(), atom));
201 }
202 }
203 None
204 }
205 Api::Alu(api) => {
206 for (name, adaptor) in &api.destructible {
207 if let Some(atom) = adaptor.convert(value, sys) {
208 return Some((name.clone(), atom));
209 }
210 }
211 None
212 }
213 }
214 }
215
216 pub fn build_immutable(
217 &self,
218 name: impl Into<StateName>,
219 data: StrictVal,
220 raw: Option<StrictVal>,
221 sys: &TypeSystem,
222 ) -> StateData {
223 let name = name.into();
224 match self {
225 Api::Embedded(api) => api
226 .append_only
227 .get(&name)
228 .expect("state name is unknown for the API")
229 .build(data, raw, sys),
230 Api::Alu(api) => api
231 .append_only
232 .get(&name)
233 .expect("state name is unknown for the API")
234 .build(data, raw, sys),
235 }
236 }
237
238 pub fn build_destructible(&self, name: impl Into<StateName>, data: StrictVal, sys: &TypeSystem) -> StateValue {
239 let name = name.into();
240 match self {
241 Api::Embedded(api) => api
242 .destructible
243 .get(&name)
244 .expect("state name is unknown for the API")
245 .build(data, sys),
246 Api::Alu(api) => api
247 .destructible
248 .get(&name)
249 .expect("state name is unknown for the API")
250 .build(data, sys),
251 }
252 }
253
254 pub fn calculate(&self, name: impl Into<StateName>) -> Box<dyn StateCalc> {
255 let name = name.into();
256 match self {
257 Api::Embedded(api) => api
258 .destructible
259 .get(&name)
260 .expect("state name is unknown for the API")
261 .arithmetics
262 .calculator(),
263 #[allow(clippy::let_unit_value)]
264 Api::Alu(api) => api
265 .destructible
266 .get(&name)
267 .expect("state name is unknown for the API")
268 .arithmetics
269 .calculator(),
270 }
271 }
272}
273
274#[derive(Clone, Debug)]
283#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
284#[strict_type(lib = LIB_NAME_SONIC)]
285#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase", bound = ""))]
286pub struct ApiInner<Vm: ApiVm> {
287 pub version: ReservedBytes<2>,
289
290 pub codex_id: CodexId,
292
293 pub timestamp: i64,
295
296 pub name: Option<TypeName>,
298
299 pub developer: Identity,
301
302 pub conforms: Option<TypeName>,
304
305 pub default_call: Option<CallState>,
307
308 pub reserved: ReservedBytes<8>,
310
311 pub append_only: TinyOrdMap<StateName, AppendApi<Vm>>,
314
315 pub destructible: TinyOrdMap<StateName, DestructibleApi<Vm>>,
318
319 pub readers: TinyOrdMap<MethodName, Vm::Reader>,
325
326 pub verifiers: TinyOrdMap<MethodName, CallId>,
331
332 pub errors: TinyOrdMap<u256, TinyString>,
335}
336
337#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
338#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
339#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
340#[strict_type(lib = LIB_NAME_SONIC)]
341pub struct ApiId(
342 #[from]
343 #[from([u8; 32])]
344 Bytes32,
345);
346
347mod _baid4 {
348 use core::fmt::{self, Display, Formatter};
349 use core::str::FromStr;
350
351 use amplify::ByteArray;
352 use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
353 use commit_verify::{CommitmentId, DigestExt, Sha256};
354
355 use super::*;
356
357 impl DisplayBaid64 for ApiId {
358 const HRI: &'static str = "api";
359 const CHUNKING: bool = true;
360 const PREFIX: bool = false;
361 const EMBED_CHECKSUM: bool = false;
362 const MNEMONIC: bool = true;
363 fn to_baid64_payload(&self) -> [u8; 32] { self.to_byte_array() }
364 }
365 impl FromBaid64Str for ApiId {}
366 impl FromStr for ApiId {
367 type Err = Baid64ParseError;
368 fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
369 }
370 impl Display for ApiId {
371 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
372 }
373
374 impl From<Sha256> for ApiId {
375 fn from(hasher: Sha256) -> Self { hasher.finish().into() }
376 }
377
378 impl CommitmentId for ApiId {
379 const TAG: &'static str = "urn:ubideco:sonic:api#2024-11-20";
380 }
381}
382
383#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
390#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
391#[strict_type(lib = LIB_NAME_SONIC)]
392#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
393pub struct AppendApi<Vm: ApiVm> {
394 pub sem_id: SemId,
396 pub raw_sem_id: SemId,
398
399 pub published: bool,
400 pub adaptor: Vm::Adaptor,
403}
404
405impl<Vm: ApiVm> AppendApi<Vm> {
406 pub fn convert(&self, data: &StateData, sys: &TypeSystem) -> Option<StateAtom> {
407 self.adaptor
408 .convert_immutable(self.sem_id, self.raw_sem_id, data, sys)
409 }
410
411 pub fn build(&self, value: StrictVal, raw: Option<StrictVal>, sys: &TypeSystem) -> StateData {
417 let raw = raw.map(|raw| {
418 let typed = sys
419 .typify(raw, self.raw_sem_id)
420 .expect("invalid strict value not matching semantic type information");
421 sys.strict_serialize_value::<U16MAX>(&typed)
422 .expect("strict value is too large")
423 .into()
424 });
425 let value = self.adaptor.build_state(self.sem_id, value, sys);
426 StateData { value, raw }
427 }
428}
429
430#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
431#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
432#[strict_type(lib = LIB_NAME_SONIC)]
433#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
434pub struct DestructibleApi<Vm: ApiVm> {
435 pub sem_id: SemId,
436
437 pub arithmetics: Vm::Arithm,
439
440 pub adaptor: Vm::Adaptor,
443}
444
445impl<Vm: ApiVm> DestructibleApi<Vm> {
446 pub fn convert(&self, value: StateValue, sys: &TypeSystem) -> Option<StrictVal> {
447 self.adaptor.convert_destructible(self.sem_id, value, sys)
448 }
449 pub fn build(&self, value: StrictVal, sys: &TypeSystem) -> StateValue {
450 self.adaptor.build_state(self.sem_id, value, sys)
451 }
452 pub fn arithmetics(&self) -> &Vm::Arithm { &self.arithmetics }
453}
454
455#[cfg(not(feature = "serde"))]
456trait Serde {}
457#[cfg(not(feature = "serde"))]
458impl<T> Serde for T {}
459
460#[cfg(feature = "serde")]
461trait Serde: serde::Serialize + for<'de> serde::Deserialize<'de> {}
462#[cfg(feature = "serde")]
463impl<T> Serde for T where T: serde::Serialize + for<'de> serde::Deserialize<'de> {}
464
465pub trait ApiVm {
466 type Arithm: StateArithm;
467 type Reader: StateReader;
468 type Adaptor: StateAdaptor;
469
470 fn vm_type(&self) -> VmType;
471}
472
473#[allow(private_bounds)]
476pub trait StateReader: Clone + Ord + Debug + StrictDumb + StrictEncode + StrictDecode + Serde {
477 fn read<'s, I: IntoIterator<Item = &'s StateAtom>>(&self, state: impl Fn(&StateName) -> I) -> StrictVal;
478}
479
480#[allow(private_bounds)]
482pub trait StateAdaptor: Clone + Ord + Debug + StrictDumb + StrictEncode + StrictDecode + Serde {
483 fn convert_immutable(
484 &self,
485 sem_id: SemId,
486 raw_sem_id: SemId,
487 data: &StateData,
488 sys: &TypeSystem,
489 ) -> Option<StateAtom>;
490 fn convert_destructible(&self, sem_id: SemId, value: StateValue, sys: &TypeSystem) -> Option<StrictVal>;
491
492 fn build_immutable(&self, value: ConfinedBlob<0, TOTAL_BYTES>) -> StateValue;
493 fn build_destructible(&self, value: ConfinedBlob<0, TOTAL_BYTES>) -> StateValue;
494
495 fn build_state(&self, sem_id: SemId, value: StrictVal, sys: &TypeSystem) -> StateValue {
496 let typed = sys
497 .typify(value, sem_id)
498 .expect("invalid strict value not matching semantic type information");
499 let ser = sys
500 .strict_serialize_value::<TOTAL_BYTES>(&typed)
501 .expect("strict value is too large");
502 self.build_immutable(ser)
503 }
504}
505
506#[allow(private_bounds)]
507pub trait StateArithm: Clone + Debug + StrictDumb + StrictEncode + StrictDecode + Serde {
508 fn calculator(&self) -> Box<dyn StateCalc>;
511}
512
513#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)]
514#[display(doc_comments)]
515pub enum StateCalcError {
516 Overflow,
518
519 UncountableState,
521}
522
523pub trait StateCalc {
524 fn compare(&self, a: &StrictVal, b: &StrictVal) -> Option<Ordering>;
526
527 fn accumulate(&mut self, state: &StrictVal) -> Result<(), StateCalcError>;
529
530 fn lessen(&mut self, state: &StrictVal) -> Result<(), StateCalcError>;
532
533 fn diff(&self) -> Result<Vec<StrictVal>, StateCalcError>;
536
537 fn is_satisfied(&self, state: &StrictVal) -> bool;
539}
540
541impl StateCalc for Box<dyn StateCalc> {
542 fn compare(&self, a: &StrictVal, b: &StrictVal) -> Option<Ordering> { self.as_ref().compare(a, b) }
543
544 fn accumulate(&mut self, state: &StrictVal) -> Result<(), StateCalcError> { self.as_mut().accumulate(state) }
545
546 fn lessen(&mut self, state: &StrictVal) -> Result<(), StateCalcError> { self.as_mut().lessen(state) }
547
548 fn diff(&self) -> Result<Vec<StrictVal>, StateCalcError> { self.as_ref().diff() }
549
550 fn is_satisfied(&self, state: &StrictVal) -> bool { self.as_ref().is_satisfied(state) }
551}