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