1use std::io;
25
26use aluvm::LibSite;
27use amplify::confinement::{Confined, ConfinedBlob};
28use amplify::num::u256;
29use sonic_callreq::StateName;
30use strict_encoding::{SerializeError, StreamReader};
31use strict_types::value::{EnumTag, StrictNum};
32use strict_types::{decode, typify, Cls, SemId, StrictVal, Ty, TypeSystem};
33use ultrasonic::StateValue;
34
35use crate::{fe256, StateTy, LIB_NAME_SONIC};
36
37pub(super) const USED_FIEL_BYTES: usize = u256::BYTES as usize - 2;
38pub(super) const MAX_BYTES: usize = USED_FIEL_BYTES * 3;
39
40#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
41#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
42#[strict_type(lib = LIB_NAME_SONIC, tags = custom, dumb = Self::TypedEncoder(strict_dumb!()))]
43#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
44pub enum StateConvertor {
45 #[strict_type(tag = 0x00)]
46 Unit,
47
48 #[strict_type(tag = 0x10)]
49 TypedEncoder(StateTy),
50
51 #[strict_type(tag = 0x11)]
52 TypedFieldEncoder(StateTy),
53 #[strict_type(tag = 0xFF)]
60 AluVM(
61 LibSite,
64 ),
65}
66
67impl StateConvertor {
68 pub fn convert(
69 &self,
70 sem_id: SemId,
71 value: StateValue,
72 sys: &TypeSystem,
73 ) -> Result<Option<StrictVal>, StateConvertError> {
74 match self {
75 Self::Unit if StateValue::None == value => Ok(Some(StrictVal::Unit)),
76 Self::Unit => Err(StateConvertError::UnitState),
77 Self::TypedEncoder(ty) => typed_convert(*ty, sem_id, value, sys),
78 Self::TypedFieldEncoder(ty) => typed_field_convert(*ty, sem_id, value, sys),
79 Self::AluVM(_) => Err(StateConvertError::Unsupported),
80 }
81 }
82}
83
84#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
85#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
86#[strict_type(lib = LIB_NAME_SONIC, tags = custom, dumb = Self::TypedEncoder(strict_dumb!()))]
87#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
88pub enum StateBuilder {
89 #[strict_type(tag = 0x00)]
90 Unit,
91
92 #[strict_type(tag = 0x10)]
93 TypedEncoder(StateTy),
94
95 #[strict_type(tag = 0x11)]
96 TypedFieldEncoder(StateTy),
97 #[strict_type(tag = 0xFF)]
102 AluVM(
103 LibSite,
106 ),
107}
108
109impl StateBuilder {
110 #[allow(clippy::result_large_err)]
111 pub fn build(&self, sem_id: SemId, value: StrictVal, sys: &TypeSystem) -> Result<StateValue, StateBuildError> {
112 let typed = sys.typify(value.clone(), sem_id)?;
113 Ok(match self {
114 Self::Unit if typed.as_val() == &StrictVal::Unit => StateValue::None,
115 Self::Unit => return Err(StateBuildError::InvalidUnit),
116 Self::TypedEncoder(ty) => {
117 let ser = sys.strict_serialize_value::<MAX_BYTES>(&typed)?;
118 typed_build(*ty, ser)
119 }
120 Self::TypedFieldEncoder(ty) => typed_field_build(*ty, value)?,
121 Self::AluVM(_) => return Err(StateBuildError::Unsupported),
122 })
123 }
124}
125
126#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
127#[display(inner)]
128pub enum StateBuildError {
129 #[display("unknown state name '{0}'")]
130 UnknownStateName(StateName),
131
132 #[from]
133 Typify(typify::Error),
134
135 #[from(io::Error)]
136 #[display("state data is too large to be encoded")]
137 TooLarge,
138
139 #[display("state data ({0:?}) have an unsupported type for the encoding")]
140 UnsupportedValue(StrictVal),
141
142 #[from]
143 Serialize(SerializeError),
144
145 #[display("the provided value doesn't match the required unit type")]
146 InvalidUnit,
147
148 #[display("AluVM is not yet supported for a state builder.")]
149 Unsupported,
150}
151
152#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
153pub enum StateConvertError {
154 #[display("unknown state name '{0}'")]
155 UnknownStateName(StateName),
156
157 #[from]
158 #[display(inner)]
159 Decode(decode::Error),
160
161 #[display("state value is not fully consumed")]
162 NotEntirelyConsumed,
163
164 #[display("state has no data")]
165 UnitState,
166
167 #[display("unknown type {0}")]
168 TypeUnknown(SemId),
169
170 #[display("type of class {0} is not supported by field-based convertor")]
171 TypeClassUnsupported(Cls),
172
173 #[display("number of fields doesn't match the number of fields in the type")]
174 TypeFieldCountMismatch,
175
176 #[display("AluVM is not yet supported for a state conversion.")]
177 Unsupported,
178}
179
180fn reduce_tuples(mut val: StrictVal) -> StrictVal {
182 loop {
183 if let StrictVal::Tuple(ref mut vec) = val {
184 if vec.len() == 1 {
185 val = vec.remove(0);
186 continue;
187 }
188 }
189 return val;
190 }
191}
192
193fn typed_convert(
194 ty: StateTy,
195 sem_id: SemId,
196 value: StateValue,
197 sys: &TypeSystem,
198) -> Result<Option<StrictVal>, StateConvertError> {
199 let from_ty = value.get(0).ok_or(StateConvertError::UnitState)?.to_u256();
200 if from_ty != ty {
202 return Ok(None);
203 }
204
205 let mut buf = [0u8; MAX_BYTES];
206 let mut i = 1u8;
207 while let Some(el) = value.get(i) {
208 let from = USED_FIEL_BYTES * (i - 1) as usize;
209 let to = USED_FIEL_BYTES * i as usize;
210 buf[from..to].copy_from_slice(&el.to_u256().to_le_bytes()[..USED_FIEL_BYTES]);
211 i += 1;
212 }
213 let used_bytes = USED_FIEL_BYTES * (i - 1) as usize;
214 debug_assert!(i <= 4);
215 debug_assert!(used_bytes <= MAX_BYTES);
216
217 let mut cursor = StreamReader::cursor::<MAX_BYTES>(&buf[..used_bytes]);
218 let mut val = sys.strict_read_type(sem_id, &mut cursor)?.unbox();
219
220 let cursor = cursor.unconfine();
223 let position = cursor.position() as usize;
224 let data = cursor.into_inner();
225 for item in data.iter().take(used_bytes).skip(position) {
226 if *item != 0 {
227 return Err(StateConvertError::NotEntirelyConsumed);
228 }
229 }
230
231 val = reduce_tuples(val);
232
233 Ok(Some(val))
234}
235
236fn typed_field_convert(
237 ty: StateTy,
238 sem_id: SemId,
239 value: StateValue,
240 sys: &TypeSystem,
241) -> Result<Option<StrictVal>, StateConvertError> {
242 let from_ty = value.get(0).ok_or(StateConvertError::UnitState)?.to_u256();
243 if from_ty != ty {
245 return Ok(None);
246 }
247
248 let ty = sys
249 .get(sem_id)
250 .ok_or(StateConvertError::TypeUnknown(sem_id))?;
251 let fields = match ty {
252 Ty::Tuple(fields) => fields.iter().copied().collect::<Vec<SemId>>(),
253 Ty::Struct(fields) => fields.iter().map(|f| f.ty).collect::<Vec<SemId>>(),
254 _ => return Err(StateConvertError::TypeClassUnsupported(ty.cls())),
255 };
256
257 if fields.len() != value.into_iter().count() - 1 {
258 return Err(StateConvertError::TypeFieldCountMismatch);
259 }
260
261 let mut items = vec![];
262 for (el, sem_id) in value.into_iter().skip(1).zip(fields.into_iter()) {
263 let mut cursor = StreamReader::cursor::<MAX_BYTES>(el.to_u256().to_le_bytes());
264 let val = sys.strict_read_type(sem_id, &mut cursor)?.unbox();
265 items.push(val);
266 }
267
268 let mut val = match ty {
269 Ty::Tuple(_) => StrictVal::Tuple(items),
270 Ty::Struct(fields) => StrictVal::Struct(
271 fields
272 .iter()
273 .zip(items)
274 .map(|(f, val)| (f.name.clone(), reduce_tuples(val)))
275 .collect(),
276 ),
277 _ => unreachable!(),
278 };
279
280 val = reduce_tuples(val);
282
283 Ok(Some(val))
284}
285
286fn typed_build(ty: StateTy, ser: ConfinedBlob<0, MAX_BYTES>) -> StateValue {
287 let mut elems = Vec::with_capacity(4);
288 elems.push(ty);
289 for chunk in ser.chunks(USED_FIEL_BYTES) {
290 let mut buf = [0u8; u256::BYTES as usize];
291 buf[..chunk.len()].copy_from_slice(chunk);
292 elems.push(u256::from_le_bytes(buf));
293 }
294
295 StateValue::from_iter(elems)
296}
297
298#[allow(clippy::result_large_err)]
299fn typed_field_build(ty: StateTy, val: StrictVal) -> Result<StateValue, StateBuildError> {
300 let mut elems = Vec::with_capacity(4);
301 elems.push(ty);
302
303 Ok(match val {
304 StrictVal::Unit => StateValue::Single { first: fe256::from(ty) },
305 StrictVal::Number(StrictNum::Uint(i)) => StateValue::Double { first: fe256::from(ty), second: fe256::from(i) },
306 StrictVal::String(s) if s.len() < MAX_BYTES => {
307 typed_build(ty, Confined::from_iter_checked(s.as_bytes().iter().cloned()))
308 }
309 StrictVal::Bytes(b) if b.len() < MAX_BYTES => typed_build(ty, Confined::from_checked(b.0)),
310 StrictVal::Struct(fields) if fields.len() <= 3 => typed_field_build_items(ty, fields.into_values())?,
311 StrictVal::Enum(EnumTag::Ord(tag)) => StateValue::Double { first: fe256::from(ty), second: fe256::from(tag) },
312 StrictVal::List(items) | StrictVal::Set(items) | StrictVal::Tuple(items) if items.len() <= 3 => {
313 typed_field_build_items(ty, items)?
314 }
315 _ => return Err(StateBuildError::UnsupportedValue(val)),
316 })
317}
318
319#[allow(clippy::result_large_err)]
320fn typed_field_build_items(
321 ty: StateTy,
322 vals: impl IntoIterator<Item = StrictVal>,
323) -> Result<StateValue, StateBuildError> {
324 let mut items = Vec::with_capacity(4);
325 items.push(ty);
326 for val in vals {
327 if let Some(val) = typed_field_build_item(val)? {
328 items.push(val);
329 }
330 }
331 Ok(StateValue::from_iter(items))
332}
333
334#[allow(clippy::result_large_err)]
335fn typed_field_build_item(val: StrictVal) -> Result<Option<u256>, StateBuildError> {
336 Ok(match val {
337 StrictVal::Unit => None,
338 StrictVal::Tuple(items) if items.len() == 1 => typed_field_build_item(items[0].clone())?,
339 StrictVal::Number(StrictNum::Uint(i)) => Some(u256::from(i)),
340 StrictVal::String(s) if s.len() < USED_FIEL_BYTES => {
341 let mut buf = [0u8; u256::BYTES as usize];
342 buf[..s.len()].copy_from_slice(s.as_bytes());
343 Some(u256::from_le_bytes(buf))
344 }
345 StrictVal::Bytes(b) if b.len() < USED_FIEL_BYTES => {
346 let mut buf = [0u8; u256::BYTES as usize];
347 buf[..b.len()].copy_from_slice(&b.0);
348 Some(u256::from_le_bytes(buf))
349 }
350 StrictVal::Enum(EnumTag::Ord(tag)) => Some(u256::from(tag)),
351 _ => return Err(StateBuildError::UnsupportedValue(val)),
352 })
353}
354
355#[cfg(test)]
356mod tests {
357 #![cfg_attr(coverage_nightly, coverage(off))]
358
359 use strict_types::stl::std_stl;
360 use strict_types::{LibBuilder, SymbolicSys, SystemBuilder, TypeLib};
361
362 use super::*;
363
364 pub const LIB_NAME_TEST: &str = "Test";
365
366 #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
367 #[display(lowercase)]
368 #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
369 #[strict_type(lib = LIB_NAME_TEST, tags = repr, try_from_u8, into_u8)]
370 #[repr(u8)]
371 pub enum Vote {
372 #[strict_type(dumb)]
373 Contra = 0,
374 Pro = 1,
375 }
376
377 #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
378 #[display(inner)]
379 #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
380 #[strict_type(lib = LIB_NAME_TEST)]
381 pub struct VoteId(u64);
382
383 #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
384 #[display(inner)]
385 #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
386 #[strict_type(lib = LIB_NAME_TEST)]
387 pub struct PartyId(u64);
388
389 #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Display)]
390 #[display("Participant #{party_id} voted {vote} in voting #{vote_id}")]
391 #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
392 #[strict_type(lib = LIB_NAME_TEST)]
393 pub struct CastVote {
394 pub vote_id: VoteId,
395 pub vote: Vote,
396 pub party_id: PartyId,
397 }
398
399 pub fn stl() -> TypeLib {
400 LibBuilder::with(libname!(LIB_NAME_TEST), [std_stl().to_dependency_types()])
401 .transpile::<CastVote>()
402 .compile()
403 .expect("invalid Test type library")
404 }
405
406 #[derive(Debug)]
407 pub struct Types(SymbolicSys);
408
409 impl Types {
410 pub fn new() -> Self {
411 Self(
412 SystemBuilder::new()
413 .import(std_stl())
414 .unwrap()
415 .import(stl())
416 .unwrap()
417 .finalize()
418 .unwrap(),
419 )
420 }
421
422 pub fn type_system(&self) -> TypeSystem {
423 let stdtypes = std_stl().types;
424 let types = stl().types;
425 let types = stdtypes
426 .into_iter()
427 .chain(types)
428 .map(|(tn, ty)| ty.sem_id_named(&tn));
429 self.0.as_types().extract(types).unwrap()
430 }
431
432 pub fn get(&self, name: &'static str) -> SemId {
433 *self
434 .0
435 .resolve(name)
436 .unwrap_or_else(|| panic!("type '{name}' is absent in RGB21 type library"))
437 }
438 }
439
440 fn typed_roundtrip(name: &'static str, src: StateValue, dst: StrictVal) {
441 let types = Types::new();
442
443 let ty = types.get(name);
444 let val = StateConvertor::TypedEncoder(u256::ONE)
445 .convert(ty, src, &types.type_system())
446 .unwrap()
447 .unwrap();
448 assert_eq!(val, dst);
449
450 let res = StateBuilder::TypedEncoder(u256::ONE)
451 .build(ty, dst, &types.type_system())
452 .unwrap();
453 assert_eq!(res, src);
454 }
455
456 fn typed_field_roundtrip(name: &'static str, src1: StateValue, dst: StrictVal, src2: StrictVal) {
457 let types = Types::new();
458
459 let ty = types.get(name);
460 let val = StateConvertor::TypedFieldEncoder(u256::ONE)
461 .convert(ty, src1, &types.type_system())
462 .unwrap()
463 .unwrap();
464 assert_eq!(val, dst);
465
466 let res = StateBuilder::TypedFieldEncoder(u256::ONE)
467 .build(ty, src2, &types.type_system())
468 .unwrap();
469 assert_eq!(res, src1);
470 }
471
472 #[test]
473 fn typed() {
474 typed_roundtrip(
475 "Std.Bool",
476 StateValue::Double { first: fe256::from(1u8), second: fe256::from(1u8) },
477 svenum!("true"),
478 );
479 }
480
481 #[test]
482 #[should_panic(expected = "Decode(Decode(Io(Kind(UnexpectedEof))))")]
483 fn typed_convert_lack() {
484 let types = Types::new();
485 StateConvertor::TypedEncoder(u256::ONE)
486 .convert(types.get("Std.Bool"), StateValue::Single { first: fe256::from(1u8) }, &types.type_system())
487 .unwrap();
488 }
489
490 #[test]
491 #[should_panic(expected = "NotEntirelyConsumed")]
492 fn typed_convert_excess() {
493 let types = Types::new();
494 StateConvertor::TypedEncoder(u256::ONE)
495 .convert(
496 types.get("Std.Bool"),
497 StateValue::Triple {
498 first: fe256::from(1u8),
499 second: fe256::from(1u8),
500 third: fe256::from(1u8),
501 },
502 &types.type_system(),
503 )
504 .unwrap();
505 }
506
507 #[test]
508 fn typed_field() {
509 typed_field_roundtrip(
510 "Test.CastVote",
511 StateValue::Quadruple {
512 first: fe256::from(1u8),
513 second: fe256::from(3u8),
514 third: fe256::from(1u8),
515 fourth: fe256::from(5u8),
516 },
517 ston!(voteId 3u8, vote svenum!("pro"), partyId 5u8),
518 ston!(voteId 3u8, vote svenum!(1), partyId 5u8),
519 );
520 }
521
522 #[test]
523 #[should_panic(expected = "TypeClassUnsupported(Enum)")]
524 fn typed_field_convert_enum() {
525 let types = Types::new();
526 let val = StateConvertor::TypedFieldEncoder(u256::ONE)
527 .convert(
528 types.get("Std.Bool"),
529 StateValue::Double { first: fe256::from(1u8), second: fe256::from(1u8) },
530 &types.type_system(),
531 )
532 .unwrap();
533 assert_eq!(val, Some(svenum!("true")));
534 }
535
536 #[test]
537 #[should_panic(expected = "TypeFieldCountMismatch")]
538 fn typed_field_convert_lack() {
539 let types = Types::new();
540 StateConvertor::TypedFieldEncoder(u256::ONE)
541 .convert(types.get("Test.CastVote"), StateValue::Single { first: fe256::from(1u8) }, &types.type_system())
542 .unwrap();
543 }
544
545 #[test]
546 #[should_panic(expected = "TypeFieldCountMismatch")]
547 fn typed_field_convert_excess() {
548 let types = Types::new();
549 StateConvertor::TypedFieldEncoder(u256::ONE)
550 .convert(
551 types.get("Test.PartyId"),
552 StateValue::Triple {
553 first: fe256::from(1u8),
554 second: fe256::from(1u8),
555 third: fe256::from(1u8),
556 },
557 &types.type_system(),
558 )
559 .unwrap();
560 }
561
562 #[test]
563 #[should_panic(
564 expected = r#"Decode(Decode(EnumTagNotKnown("semid:kr1DHi~j-YSw4n54-o9KnZ9Q-Dlo0pWP-_V9U5oh-Wlzfemk#break-secret-delphi", 5)))"#
565 )]
566 fn typed_field_convert_invalid() {
567 let types = Types::new();
568 StateConvertor::TypedFieldEncoder(u256::ONE)
569 .convert(
570 types.get("Test.CastVote"),
571 StateValue::Quadruple {
572 first: fe256::from(1u8),
573 second: fe256::from(1u8),
574 third: fe256::from(5u8),
575 fourth: fe256::from(1u8),
576 },
577 &types.type_system(),
578 )
579 .unwrap();
580 }
581}