1#![allow(unused_braces)]
25
26use core::fmt;
27use core::fmt::{Display, Formatter};
28use core::str::FromStr;
29
30use aluvm::{Lib, LibId};
31use amplify::confinement::NonEmptyBlob;
32use amplify::Wrapper;
33use baid64::DisplayBaid64;
34use commit_verify::{CommitEncode, CommitId, StrictHash};
35use sonic_callreq::MethodName;
36use strict_encoding::TypeName;
37use strict_types::TypeSystem;
38use ultrasonic::{
39 CallId, Codex, CodexId, ContractId, ContractMeta, ContractName, Genesis, Identity, Issue, LibRepo, Opid,
40};
41
42use crate::{Api, ApisChecksum, ParseVersionedError, SemanticError, Semantics, LIB_NAME_SONIC};
43
44#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
58#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
59#[strict_type(lib = LIB_NAME_SONIC)]
60#[derive(CommitEncode)]
61#[commit_encode(strategy = strict, id = StrictHash)]
62#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
63pub struct ArticlesId {
64 pub contract_id: ContractId,
66 pub version: u16,
68 pub checksum: ApisChecksum,
70}
71
72impl Display for ArticlesId {
73 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
74 write!(f, "{}/{}#", self.contract_id, self.version)?;
75 self.checksum.fmt_baid64(f)
76 }
77}
78
79impl FromStr for ArticlesId {
80 type Err = ParseVersionedError;
81 fn from_str(s: &str) -> Result<Self, Self::Err> {
82 let (id, remnant) = s
83 .split_once('/')
84 .ok_or_else(|| ParseVersionedError::NoVersion(s.to_string()))?;
85 let (version, api_id) = remnant
86 .split_once('#')
87 .ok_or_else(|| ParseVersionedError::NoChecksum(s.to_string()))?;
88 Ok(Self {
89 contract_id: id.parse().map_err(ParseVersionedError::Id)?,
90 version: version.parse().map_err(ParseVersionedError::Version)?,
91 checksum: api_id.parse().map_err(ParseVersionedError::Checksum)?,
92 })
93 }
94}
95
96#[derive(Clone, Eq, PartialEq, Debug)]
106#[derive(StrictType, StrictDumb, StrictEncode)]
107#[strict_type(lib = LIB_NAME_SONIC)]
110pub struct Articles {
111 semantics: Semantics,
115 sig: Option<SigBlob>,
120 issue: Issue,
122}
123
124impl Articles {
125 pub fn with<E>(
128 semantics: Semantics,
129 issue: Issue,
130 sig: Option<SigBlob>,
131 sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
132 ) -> Result<Self, SemanticError> {
133 semantics.check(&issue.codex)?;
134 let mut me = Self { semantics, issue, sig: None };
135 let id = me.articles_id().commit_id();
136 if let Some(sig) = &sig {
137 sig_validator(id, &me.issue.meta.issuer, sig).map_err(|_| SemanticError::InvalidSignature)?;
138 }
139 me.sig = sig;
140 Ok(me)
141 }
142
143 pub fn articles_id(&self) -> ArticlesId {
146 ArticlesId {
147 contract_id: self.issue.contract_id(),
148 version: self.semantics.version,
149 checksum: self.semantics.apis_checksum(),
150 }
151 }
152 pub fn contract_id(&self) -> ContractId { self.issue.contract_id() }
154 pub fn codex_id(&self) -> CodexId { self.issue.codex_id() }
156 pub fn genesis_opid(&self) -> Opid { self.issue.genesis_opid() }
158
159 pub fn semantics(&self) -> &Semantics { &self.semantics }
161 pub fn default_api(&self) -> &Api { &self.semantics.default }
163 pub fn custom_apis(&self) -> impl Iterator<Item = (&TypeName, &Api)> { self.semantics.custom.iter() }
165 pub fn types(&self) -> &TypeSystem { &self.semantics.types }
167 pub fn apis(&self) -> impl Iterator<Item = &Api> { self.semantics.apis() }
169 pub fn codex_libs(&self) -> impl Iterator<Item = &Lib> { self.semantics.codex_libs.iter() }
171
172 pub fn issue(&self) -> &Issue { &self.issue }
174 pub fn codex(&self) -> &Codex { &self.issue.codex }
176 pub fn genesis(&self) -> &Genesis { &self.issue.genesis }
178 pub fn contract_meta(&self) -> &ContractMeta { &self.issue.meta }
180 pub fn contract_name(&self) -> &ContractName { &self.issue.meta.name }
182
183 pub fn sig(&self) -> &Option<SigBlob> { &self.sig }
185 pub fn is_signed(&self) -> bool { self.sig.is_some() }
187
188 pub fn upgrade_apis(&mut self, other: Self) -> Result<bool, SemanticError> {
194 if self.contract_id() != other.contract_id() {
195 return Err(SemanticError::ContractMismatch);
196 }
197
198 Ok(match (&self.sig, &other.sig) {
199 (None, None) | (Some(_), Some(_)) if other.semantics.version > self.semantics.version => {
200 self.semantics = other.semantics;
201 true
202 }
203 (None, Some(_)) => {
204 self.semantics = other.semantics;
205 true
206 }
207 _ => false, })
209 }
210
211 pub fn call_id(&self, method: impl Into<MethodName>) -> CallId {
217 let method = method.into();
218 let name = method.to_string();
219 self.semantics
220 .default
221 .verifier(method)
222 .unwrap_or_else(|| panic!("requesting a method `{name}` absent in the contract API"))
223 }
224}
225
226impl LibRepo for Articles {
227 fn get_lib(&self, lib_id: LibId) -> Option<&Lib> {
228 self.semantics
229 .codex_libs
230 .iter()
231 .find(|lib| lib.lib_id() == lib_id)
232 }
233}
234
235#[derive(Wrapper, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, From, Display)]
239#[wrapper(Deref, AsSlice, BorrowSlice, Hex)]
240#[display(LowerHex)]
241#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
242#[strict_type(lib = LIB_NAME_SONIC, dumb = { Self(NonEmptyBlob::with(0)) })]
243#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
244pub struct SigBlob(NonEmptyBlob<4096>);
245
246impl SigBlob {
247 pub fn from_slice_checked(data: impl AsRef<[u8]>) -> SigBlob {
253 Self(NonEmptyBlob::from_checked(data.as_ref().to_vec()))
254 }
255
256 pub fn from_vec_checked(data: Vec<u8>) -> SigBlob { Self(NonEmptyBlob::from_checked(data)) }
262}