1use core::fmt::Debug;
25use core::str::FromStr;
26
27use amplify::{ByteArray, Bytes32, Wrapper};
28use commit_verify::{
29 CommitEncode, CommitEngine, CommitId, CommitmentId, DigestExt, ReservedBytes, Sha256,
30};
31use strict_encoding::{StrictDecode, StrictDumb, StrictEncode, TypeName};
32
33use crate::{Codex, CodexId, Genesis, Identity, Opid, LIB_NAME_ULTRASONIC};
34
35#[derive(Clone, Eq, Debug)]
37#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
38#[strict_type(lib = LIB_NAME_ULTRASONIC)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
40pub struct Issue {
41 pub version: ReservedBytes<1>,
64 pub meta: ContractMeta,
66 pub codex: Codex,
68 pub genesis: Genesis,
70}
71
72impl PartialEq for Issue {
73 fn eq(&self, other: &Self) -> bool { self.commit_id() == other.commit_id() }
74}
75
76impl CommitEncode for Issue {
77 type CommitmentId = ContractId;
78
79 fn commit_encode(&self, e: &mut CommitEngine) {
80 e.commit_to_serialized(&self.version);
81 e.commit_to_serialized(&self.meta);
82 e.commit_to_serialized(&self.codex_id());
83 e.commit_to_serialized(&self.genesis.opid(ContractId::from_byte_array([0xFFu8; 32])));
84 }
85}
86
87impl Issue {
88 #[inline]
93 pub fn contract_id(&self) -> ContractId { self.commit_id() }
94
95 #[inline]
99 pub fn codex_id(&self) -> CodexId { self.codex.codex_id() }
100
101 #[inline]
105 pub fn genesis_opid(&self) -> Opid { self.genesis.opid(self.contract_id()) }
106}
107
108#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Display)]
110#[display(lowercase)]
111#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
112#[strict_type(lib = LIB_NAME_ULTRASONIC, tags = repr, into_u8, try_from_u8)]
113#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
114#[repr(u8)]
115pub enum Consensus {
116 #[strict_type(dumb)]
121 None = 0,
122
123 Bitcoin = 0x10,
125
126 Liquid = 0x11,
128
129 Prime = 0x20,
131}
132
133impl FromStr for Consensus {
134 type Err = String;
135
136 fn from_str(s: &str) -> Result<Self, Self::Err> {
137 match s {
138 "none" => Ok(Consensus::None),
139 "bitcoin" => Ok(Consensus::Bitcoin),
140 "liquid" => Ok(Consensus::Liquid),
141 "prime" => Ok(Consensus::Prime),
142 _ => Err(s.to_owned()),
143 }
144 }
145}
146
147#[derive(Clone, Eq, PartialEq, Debug)]
149#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
150#[strict_type(lib = LIB_NAME_ULTRASONIC)]
151#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
152pub struct ContractMeta {
153 pub testnet: bool,
155 pub consensus: Consensus,
157 pub timestamp: i64,
159 pub features: ReservedBytes<6>,
163 pub name: ContractName,
165 pub issuer: Identity,
169}
170
171#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display)]
173#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
174#[strict_type(lib = LIB_NAME_ULTRASONIC, tags = custom)]
175#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
176pub enum ContractName {
177 #[strict_type(tag = 0, dumb)]
179 #[display("~")]
180 Unnamed,
181
182 #[strict_type(tag = 1)]
184 #[display(inner)]
185 Named(TypeName),
186}
187
188#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
190#[wrapper(AsSlice, Deref, BorrowSlice, Hex, Index, RangeOps)]
191#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
192#[strict_type(lib = LIB_NAME_ULTRASONIC)]
193#[cfg_attr(
194 all(feature = "serde", not(feature = "baid64")),
195 derive(Serialize, Deserialize),
196 serde(transparent)
197)]
198pub struct ContractId(
199 #[from]
200 #[from([u8; 32])]
201 Bytes32,
202);
203
204#[cfg(all(feature = "serde", feature = "baid64"))]
205impl_serde_str_bin_wrapper!(ContractId, Bytes32);
206
207impl From<Sha256> for ContractId {
208 fn from(hasher: Sha256) -> Self { hasher.finish().into() }
209}
210
211impl CommitmentId for ContractId {
212 const TAG: &'static str = "urn:ubideco:sonic:contract#2024-11-16";
213}
214
215#[cfg(feature = "baid64")]
216mod _baid4 {
217 use core::fmt::{self, Display, Formatter};
218 use core::str::FromStr;
219
220 use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
221
222 use super::*;
223
224 impl DisplayBaid64 for ContractId {
225 const HRI: &'static str = "contract";
226 const CHUNKING: bool = true;
227 const PREFIX: bool = true;
228 const EMBED_CHECKSUM: bool = false;
229 const MNEMONIC: bool = false;
230 fn to_baid64_payload(&self) -> [u8; 32] { self.to_byte_array() }
231 }
232 impl FromBaid64Str for ContractId {}
233 impl FromStr for ContractId {
234 type Err = Baid64ParseError;
235 fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
236 }
237 impl Display for ContractId {
238 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
239 }
240}
241
242#[cfg(test)]
243mod test {
244 #![cfg_attr(coverage_nightly, coverage(off))]
245
246 use amplify::ByteArray;
247 use commit_verify::Digest;
248
249 use super::*;
250
251 #[test]
252 fn contract_id_display() {
253 let id = ContractId::from_byte_array(Sha256::digest(b"test"));
254 assert_eq!(format!("{id}"), "contract:n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg");
255 assert_eq!(format!("{id:-}"), "n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg");
256 assert_eq!(
257 format!("{id:#}"),
258 "contract:n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg#fractal-fashion-capsule"
259 );
260 }
261
262 #[test]
263 fn contract_id_from_str() {
264 let id = ContractId::from_byte_array(Sha256::digest(b"test"));
265 assert_eq!(
266 ContractId::from_str("contract:n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg")
267 .unwrap(),
268 id
269 );
270 assert_eq!(
271 ContractId::from_str("n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg").unwrap(),
272 id
273 );
274 assert_eq!(
275 ContractId::from_str(
276 "n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg#fractal-fashion-capsule"
277 )
278 .unwrap(),
279 id
280 );
281 assert_eq!(
282 ContractId::from_str(
283 "contract:n4bQgYhM-fWWaL_q-gxVrQFa-O~TxsrC-4Is0V1s-FbDwCgg#fractal-fashion-capsule"
284 )
285 .unwrap(),
286 id
287 );
288 }
289
290 #[test]
291 #[cfg(all(feature = "serde", feature = "baid64"))]
292 fn contract_id_serde() {
293 let val = ContractId::strict_dumb();
294 test_serde_str_bin_wrapper!(
295 val,
296 "contract:AAAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA",
297 &[
298 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
299 0, 0, 0, 0
300 ]
301 );
302 }
303}