use core::cmp::Ordering;
use core::fmt::Debug;
use core::hash::{Hash, Hasher};
use amplify::confinement::{ConfinedBlob, TinyOrdMap, TinyString, U16 as U16MAX};
use amplify::num::u256;
use amplify::Bytes32;
use commit_verify::{CommitId, ReservedBytes};
use strict_types::{SemId, StrictDecode, StrictDumb, StrictEncode, StrictVal, TypeName, TypeSystem, VariantName};
use ultrasonic::{CallId, CodexId, Identity, StateData, StateValue};
use crate::embedded::EmbeddedProc;
use crate::{StateAtom, VmType, LIB_NAME_SONIC};
pub(super) const USED_FIEL_BYTES: usize = u256::BYTES as usize - 2;
pub(super) const TOTAL_BYTES: usize = USED_FIEL_BYTES * 3;
pub type StateName = VariantName;
pub type MethodName = VariantName;
#[derive(Clone, Debug, From)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict, id = ApiId)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_SONIC, tags = custom, dumb = Self::Embedded(strict_dumb!()))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub enum Api {
#[from]
#[strict_type(tag = 1)]
Embedded(ApiInner<EmbeddedProc>),
#[from]
#[strict_type(tag = 2)]
Alu(ApiInner<aluvm::Vm>),
}
impl PartialEq for Api {
fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal }
}
impl Eq for Api {}
impl PartialOrd for Api {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for Api {
fn cmp(&self, other: &Self) -> Ordering {
if self.api_id() == other.api_id() {
Ordering::Equal
} else {
self.timestamp().cmp(&other.timestamp())
}
}
}
impl Hash for Api {
fn hash<H: Hasher>(&self, state: &mut H) { self.api_id().hash(state); }
}
impl Api {
pub fn api_id(&self) -> ApiId { self.commit_id() }
pub fn vm_type(&self) -> VmType {
match self {
Api::Embedded(_) => VmType::Embedded,
Api::Alu(_) => VmType::AluVM,
}
}
pub fn codex_id(&self) -> CodexId {
match self {
Api::Embedded(api) => api.codex_id,
Api::Alu(api) => api.codex_id,
}
}
pub fn timestamp(&self) -> i64 {
match self {
Api::Embedded(api) => api.timestamp,
Api::Alu(api) => api.timestamp,
}
}
pub fn name(&self) -> Option<&TypeName> {
match self {
Api::Embedded(api) => api.name.as_ref(),
Api::Alu(api) => api.name.as_ref(),
}
}
pub fn developer(&self) -> &Identity {
match self {
Api::Embedded(api) => &api.developer,
Api::Alu(api) => &api.developer,
}
}
pub fn verifier(&self, method: impl Into<MethodName>) -> Option<CallId> {
let method = method.into();
match self {
Api::Embedded(api) => api.verifiers.get(&method),
Api::Alu(api) => api.verifiers.get(&method),
}
.copied()
}
pub fn readers(&self) -> Box<dyn Iterator<Item = &MethodName> + '_> {
match self {
Api::Embedded(api) => Box::new(api.readers.keys()),
Api::Alu(api) => Box::new(api.readers.keys()),
}
}
pub fn read<'s, I: IntoIterator<Item = &'s StateAtom>>(
&self,
name: &StateName,
state: impl Fn(&StateName) -> I,
) -> StrictVal {
match self {
Api::Embedded(api) => api
.readers
.get(name)
.expect("state name is unknown for the API")
.read(state),
Api::Alu(api) => api
.readers
.get(name)
.expect("state name is unknown for the API")
.read(state),
}
}
pub fn convert_immutable(&self, data: &StateData, sys: &TypeSystem) -> Option<(StateName, StateAtom)> {
match self {
Api::Embedded(api) => {
for (name, adaptor) in &api.append_only {
if let Some(atom) = adaptor.convert(data, sys) {
return Some((name.clone(), atom));
}
}
None
}
Api::Alu(api) => {
for (name, adaptor) in &api.append_only {
if let Some(atom) = adaptor.convert(data, sys) {
return Some((name.clone(), atom));
}
}
None
}
}
}
pub fn convert_destructible(&self, value: StateValue, sys: &TypeSystem) -> Option<(StateName, StrictVal)> {
match self {
Api::Embedded(api) => {
for (name, adaptor) in &api.destructible {
if let Some(atom) = adaptor.convert(value, sys) {
return Some((name.clone(), atom));
}
}
None
}
Api::Alu(api) => {
for (name, adaptor) in &api.destructible {
if let Some(atom) = adaptor.convert(value, sys) {
return Some((name.clone(), atom));
}
}
None
}
}
}
pub fn build_immutable(
&self,
name: impl Into<StateName>,
data: StrictVal,
raw: Option<StrictVal>,
sys: &TypeSystem,
) -> StateData {
let name = name.into();
match self {
Api::Embedded(api) => api
.append_only
.get(&name)
.expect("state name is unknown for the API")
.build(data, raw, sys),
Api::Alu(api) => api
.append_only
.get(&name)
.expect("state name is unknown for the API")
.build(data, raw, sys),
}
}
pub fn build_destructible(&self, name: impl Into<StateName>, data: StrictVal, sys: &TypeSystem) -> StateValue {
let name = name.into();
match self {
Api::Embedded(api) => api
.destructible
.get(&name)
.expect("state name is unknown for the API")
.build(data, sys),
Api::Alu(api) => api
.destructible
.get(&name)
.expect("state name is unknown for the API")
.build(data, sys),
}
}
pub fn calculate(&self, name: impl Into<StateName>) -> Box<dyn StateCalc> {
let name = name.into();
match self {
Api::Embedded(api) => {
let calc = api
.destructible
.get(&name)
.expect("state name is unknown for the API")
.arithmetics
.calculator();
Box::new(calc)
}
Api::Alu(api) => {
let calc = api
.destructible
.get(&name)
.expect("state name is unknown for the API")
.arithmetics
.calculator();
Box::new(calc)
}
}
}
}
#[derive(Clone, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_SONIC)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase", bound = ""))]
pub struct ApiInner<Vm: ApiVm> {
pub version: ReservedBytes<2>,
pub codex_id: CodexId,
pub timestamp: i64,
pub name: Option<TypeName>,
pub developer: Identity,
pub append_only: TinyOrdMap<StateName, AppendApi<Vm>>,
pub destructible: TinyOrdMap<StateName, DestructibleApi<Vm>>,
pub readers: TinyOrdMap<MethodName, Vm::Reader>,
pub verifiers: TinyOrdMap<MethodName, CallId>,
pub errors: TinyOrdMap<u256, TinyString>,
}
#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_SONIC)]
pub struct ApiId(
#[from]
#[from([u8; 32])]
Bytes32,
);
mod _baid4 {
use core::fmt::{self, Display, Formatter};
use core::str::FromStr;
use amplify::ByteArray;
use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
use commit_verify::{CommitmentId, DigestExt, Sha256};
use super::*;
impl DisplayBaid64 for ApiId {
const HRI: &'static str = "api";
const CHUNKING: bool = true;
const PREFIX: bool = false;
const EMBED_CHECKSUM: bool = false;
const MNEMONIC: bool = true;
fn to_baid64_payload(&self) -> [u8; 32] { self.to_byte_array() }
}
impl FromBaid64Str for ApiId {}
impl FromStr for ApiId {
type Err = Baid64ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
}
impl Display for ApiId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
}
impl From<Sha256> for ApiId {
fn from(hasher: Sha256) -> Self { hasher.finish().into() }
}
impl CommitmentId for ApiId {
const TAG: &'static str = "urn:ubideco:sonic:api#2024-11-20";
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_SONIC)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub struct AppendApi<Vm: ApiVm> {
pub sem_id: SemId,
pub raw_sem_id: SemId,
pub published: bool,
pub adaptor: Vm::Adaptor,
}
impl<Vm: ApiVm> AppendApi<Vm> {
pub fn convert(&self, data: &StateData, sys: &TypeSystem) -> Option<StateAtom> {
self.adaptor
.convert_immutable(self.sem_id, self.raw_sem_id, data, sys)
}
pub fn build(&self, value: StrictVal, raw: Option<StrictVal>, sys: &TypeSystem) -> StateData {
let raw = raw.map(|raw| {
let typed = sys
.typify(raw, self.raw_sem_id)
.expect("invalid strict value not matching semantic type information");
sys.strict_serialize_value::<U16MAX>(&typed)
.expect("strict value is too large")
.into()
});
let value = self.adaptor.build_state(self.sem_id, value, sys);
StateData { value, raw }
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_SONIC)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub struct DestructibleApi<Vm: ApiVm> {
pub sem_id: SemId,
pub arithmetics: Vm::Arithm,
pub adaptor: Vm::Adaptor,
}
impl<Vm: ApiVm> DestructibleApi<Vm> {
pub fn convert(&self, value: StateValue, sys: &TypeSystem) -> Option<StrictVal> {
self.adaptor.convert_destructible(self.sem_id, value, sys)
}
pub fn build(&self, value: StrictVal, sys: &TypeSystem) -> StateValue {
self.adaptor.build_state(self.sem_id, value, sys)
}
pub fn arithmetics(&self) -> &Vm::Arithm { &self.arithmetics }
}
#[cfg(not(feature = "serde"))]
trait Serde {}
#[cfg(not(feature = "serde"))]
impl<T> Serde for T {}
#[cfg(feature = "serde")]
trait Serde: serde::Serialize + for<'de> serde::Deserialize<'de> {}
#[cfg(feature = "serde")]
impl<T> Serde for T where T: serde::Serialize + for<'de> serde::Deserialize<'de> {}
pub trait ApiVm {
type Arithm: StateArithm;
type Reader: StateReader;
type Adaptor: StateAdaptor;
fn vm_type(&self) -> VmType;
}
pub trait StateReader: Clone + Ord + Debug + StrictDumb + StrictEncode + StrictDecode + Serde {
fn read<'s, I: IntoIterator<Item = &'s StateAtom>>(&self, state: impl Fn(&StateName) -> I) -> StrictVal;
}
pub trait StateAdaptor: Clone + Ord + Debug + StrictDumb + StrictEncode + StrictDecode + Serde {
fn convert_immutable(
&self,
sem_id: SemId,
raw_sem_id: SemId,
data: &StateData,
sys: &TypeSystem,
) -> Option<StateAtom>;
fn convert_destructible(&self, sem_id: SemId, value: StateValue, sys: &TypeSystem) -> Option<StrictVal>;
fn build_immutable(&self, value: ConfinedBlob<0, TOTAL_BYTES>) -> StateValue;
fn build_destructible(&self, value: ConfinedBlob<0, TOTAL_BYTES>) -> StateValue;
fn build_state(&self, sem_id: SemId, value: StrictVal, sys: &TypeSystem) -> StateValue {
let typed = sys
.typify(value, sem_id)
.expect("invalid strict value not matching semantic type information");
let ser = sys
.strict_serialize_value::<TOTAL_BYTES>(&typed)
.expect("strict value is too large");
self.build_immutable(ser)
}
}
pub trait StateArithm: Clone + Debug + StrictDumb + StrictEncode + StrictDecode + Serde {
type Calc: StateCalc;
fn measure(&self, state: StateValue, target: StateValue) -> Option<i8>;
fn calculator(&self) -> Self::Calc;
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)]
#[display("state can't be computed")]
pub struct UncountableState;
pub trait StateCalc {
fn accumulate(&mut self, state: StrictVal) -> Result<(), UncountableState>;
fn lessen(&mut self, state: StrictVal) -> Result<(), UncountableState>;
fn diff(&self) -> Result<Vec<StrictVal>, UncountableState>;
}