sonicapi/adaptors/
embedded.rs

1// SONIC: Standard library for formally-verifiable distributed contracts
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland.
9// Copyright (C) 2024-2025 Laboratories for Ubiquitous Deterministic Computing (UBIDECO),
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2019-2025 Dr Maxim Orlovsky.
12// All rights under the above copyrights are reserved.
13//
14// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
15// in compliance with the License. You may obtain a copy of the License at
16//
17//        http://www.apache.org/licenses/LICENSE-2.0
18//
19// Unless required by applicable law or agreed to in writing, software distributed under the License
20// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
21// or implied. See the License for the specific language governing permissions and limitations under
22// the License.
23
24use core::cmp::Ordering;
25use core::str::FromStr;
26
27use amplify::confinement::ConfinedBlob;
28use amplify::num::u256;
29use strict_encoding::{StreamReader, StrictDecode, StrictEncode};
30use strict_types::typify::TypedVal;
31use strict_types::value::StrictNum;
32use strict_types::{SemId, StrictVal, TypeSystem};
33use ultrasonic::{StateData, StateValue};
34
35use crate::api::{TOTAL_BYTES, USED_FIEL_BYTES};
36use crate::{
37    ApiVm, StateAdaptor, StateArithm, StateAtom, StateCalc, StateCalcError, StateName, StateReader, StateTy, VmType,
38    LIB_NAME_SONIC,
39};
40
41#[derive(Clone, Debug)]
42pub struct EmbeddedProc;
43
44impl ApiVm for EmbeddedProc {
45    type Arithm = EmbeddedArithm;
46    type Reader = EmbeddedReaders;
47    type Adaptor = EmbeddedImmutable;
48
49    fn vm_type(&self) -> VmType { VmType::Embedded }
50}
51
52#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
53#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
54#[strict_type(lib = LIB_NAME_SONIC, tags = custom, dumb = Self::Count(strict_dumb!()))]
55#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
56pub enum EmbeddedReaders {
57    // #[strict_type(tag = 0)]
58    // Const(StrictVal),
59    #[strict_type(tag = 1)]
60    Count(StateName),
61
62    /// Sum over verifiable field-element based part of state.
63    #[strict_type(tag = 2)]
64    SumV(StateName),
65
66    /*
67    /// Count values which verifiable field-element part binary representation is prefixed with a
68    /// given byte string.
69    #[strict_type(tag = 0x10)]
70    CountPrefixedV(StateName, TinyBlob),
71     */
72    /// Convert verified state under the same state type into a vector.
73    #[strict_type(tag = 0x20)]
74    ListV(StateName),
75
76    /// Convert verified state under the same state type into a sorted set.
77    #[strict_type(tag = 0x22)]
78    SetV(StateName),
79
80    /// Map from field-based element state to a non-verifiable structured state
81    #[strict_type(tag = 0x30)]
82    MapV2U(StateName),
83}
84
85impl StateReader for EmbeddedReaders {
86    fn read<'s, I: IntoIterator<Item = &'s StateAtom>>(&self, state: impl Fn(&StateName) -> I) -> StrictVal {
87        match self {
88            //EmbeddedReaders::Const(val) => val.clone(),
89            EmbeddedReaders::Count(name) => {
90                let count = state(name).into_iter().count();
91                svnum!(count as u64)
92            }
93            EmbeddedReaders::SumV(name) => {
94                let sum = state(name)
95                    .into_iter()
96                    .map(|atom| match &atom.verified {
97                        StrictVal::Number(StrictNum::Uint(val)) => *val,
98                        _ => panic!("invalid type of state for sum aggregator"),
99                    })
100                    .sum::<u64>();
101                svnum!(sum)
102            }
103            EmbeddedReaders::ListV(name) => StrictVal::List(
104                state(name)
105                    .into_iter()
106                    .map(|atom| atom.verified.clone())
107                    .collect(),
108            ),
109            EmbeddedReaders::SetV(name) => StrictVal::Set(
110                state(name)
111                    .into_iter()
112                    .map(|atom| atom.verified.clone())
113                    .collect(),
114            ),
115            EmbeddedReaders::MapV2U(name) => StrictVal::Map(
116                state(name)
117                    .into_iter()
118                    .filter_map(|atom| atom.unverified.clone().map(|u| (atom.verified.clone(), u)))
119                    .collect(),
120            ),
121        }
122    }
123}
124
125#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
126#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
127#[strict_type(lib = LIB_NAME_SONIC)]
128#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
129pub struct EmbeddedImmutable(pub StateTy);
130
131impl EmbeddedImmutable {
132    fn convert_value(&self, sem_id: SemId, value: StateValue, sys: &TypeSystem) -> Option<StrictVal> {
133        // State type doesn't match
134        let ty = value.get(0)?.to_u256();
135        if ty != self.0 {
136            return None;
137        }
138
139        let mut buf = [0u8; TOTAL_BYTES];
140        let mut i = 1u8;
141        while let Some(el) = value.get(i) {
142            let from = USED_FIEL_BYTES * (i - 1) as usize;
143            let to = USED_FIEL_BYTES * i as usize;
144            buf[from..to].copy_from_slice(&el.to_u256().to_le_bytes()[..USED_FIEL_BYTES]);
145            i += 1;
146        }
147        debug_assert!(i <= 4);
148
149        let mut cursor = StreamReader::cursor::<TOTAL_BYTES>(buf);
150        // We do not check here that we have reached the end of the buffer, since it may be filled with
151        // zeros up to the field element length.
152        let mut val = sys.strict_read_type(sem_id, &mut cursor).ok()?.unbox();
153
154        loop {
155            if let StrictVal::Tuple(ref mut vec) = val {
156                if vec.len() == 1 {
157                    val = vec.remove(0);
158                    continue;
159                }
160            }
161            break;
162        }
163
164        Some(val)
165    }
166
167    fn build_value(&self, ser: ConfinedBlob<0, TOTAL_BYTES>) -> StateValue {
168        let mut elems = Vec::with_capacity(4);
169        elems.push(self.0);
170        for chunk in ser.chunks(USED_FIEL_BYTES) {
171            let mut buf = [0u8; u256::BYTES as usize];
172            buf[..chunk.len()].copy_from_slice(chunk);
173            elems.push(u256::from_le_bytes(buf));
174        }
175
176        StateValue::from_iter(elems)
177    }
178}
179
180impl StateAdaptor for EmbeddedImmutable {
181    fn convert_immutable(
182        &self,
183        sem_id: SemId,
184        raw_sem_id: SemId,
185        data: &StateData,
186        sys: &TypeSystem,
187    ) -> Option<StateAtom> {
188        let verified = self.convert_value(sem_id, data.value, sys)?;
189        let unverified = data
190            .raw
191            .as_ref()
192            .and_then(|raw| sys.strict_deserialize_type(raw_sem_id, raw.as_ref()).ok())
193            .map(TypedVal::unbox);
194        Some(StateAtom { verified, unverified })
195    }
196
197    fn convert_destructible(&self, sem_id: SemId, value: StateValue, sys: &TypeSystem) -> Option<StrictVal> {
198        self.convert_value(sem_id, value, sys)
199    }
200
201    fn build_immutable(&self, value: ConfinedBlob<0, TOTAL_BYTES>) -> StateValue { self.build_value(value) }
202
203    fn build_destructible(&self, value: ConfinedBlob<0, TOTAL_BYTES>) -> StateValue { self.build_value(value) }
204}
205
206#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
207#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
208#[strict_type(lib = LIB_NAME_SONIC, tags = repr, try_from_u8, into_u8)]
209#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
210#[repr(u8)]
211pub enum EmbeddedArithm {
212    #[strict_type(dumb)]
213    NonFungible = 0,
214    Fungible = 1,
215}
216
217impl StateArithm for EmbeddedArithm {
218    fn calculator(&self) -> Box<dyn StateCalc> {
219        match self {
220            EmbeddedArithm::NonFungible => Box::new(EmbeddedCalc::NonFungible(empty!())),
221            EmbeddedArithm::Fungible => Box::new(EmbeddedCalc::Fungible(StrictVal::Number(StrictNum::Uint(0)))),
222        }
223    }
224}
225
226#[derive(Clone, Eq, PartialEq, Debug)]
227pub enum EmbeddedCalc {
228    NonFungible(Vec<StrictVal>),
229    Fungible(StrictVal),
230}
231
232impl StateCalc for EmbeddedCalc {
233    fn compare(&self, state: &StrictVal, target: &StrictVal) -> Option<Ordering> {
234        match (state, target) {
235            (val, tgt) if val == tgt => Some(Ordering::Equal),
236            // TODO: Remove unsafe once rust supports `if let` guards
237            (StrictVal::Number(StrictNum::Uint(val)), StrictVal::String(s)) if u64::from_str(s).is_ok() => {
238                Some(val.cmp(&unsafe { u64::from_str(s).unwrap_unchecked() }))
239            }
240            (StrictVal::Number(StrictNum::Uint(val)), StrictVal::Number(StrictNum::Uint(tgt))) => Some(val.cmp(tgt)),
241            _ => None,
242        }
243    }
244
245    fn accumulate(&mut self, state: &StrictVal) -> Result<(), StateCalcError> {
246        match self {
247            EmbeddedCalc::NonFungible(states) => {
248                states.push(state.clone());
249                Ok(())
250            }
251            EmbeddedCalc::Fungible(value) => {
252                let (val, add) = match (state, value) {
253                    // TODO: Remove unsafe once rust supports `if let` guards
254                    (StrictVal::String(s), StrictVal::Number(StrictNum::Uint(val))) if u64::from_str(s).is_ok() => {
255                        let add = unsafe { u64::from_str(s).unwrap_unchecked() };
256                        (val, add)
257                    }
258                    (StrictVal::Number(StrictNum::Uint(add)), StrictVal::Number(StrictNum::Uint(val))) => (val, *add),
259                    _ => return Err(StateCalcError::UncountableState),
260                };
261                *val = val.checked_add(add).ok_or(StateCalcError::Overflow)?;
262                Ok(())
263            }
264        }
265    }
266
267    fn lessen(&mut self, state: &StrictVal) -> Result<(), StateCalcError> {
268        match self {
269            EmbeddedCalc::NonFungible(states) => {
270                if let Some(pos) = states.iter().position(|s| s == state) {
271                    states.remove(pos);
272                    Ok(())
273                } else {
274                    Err(StateCalcError::UncountableState)
275                }
276            }
277            EmbeddedCalc::Fungible(value) => {
278                let (val, dec) = match (state, value) {
279                    // TODO: Remove unsafe once rust supports `if let` guards
280                    (StrictVal::String(s), StrictVal::Number(StrictNum::Uint(val))) if u64::from_str(s).is_ok() => {
281                        let dec = unsafe { u64::from_str(s).unwrap_unchecked() };
282                        (val, dec)
283                    }
284                    (StrictVal::Number(StrictNum::Uint(dec)), StrictVal::Number(StrictNum::Uint(val))) => (val, *dec),
285                    _ => return Err(StateCalcError::UncountableState),
286                };
287                if dec > *val {
288                    return Err(StateCalcError::Overflow);
289                }
290                *val -= dec;
291                Ok(())
292            }
293        }
294    }
295
296    fn diff(&self) -> Result<Vec<StrictVal>, StateCalcError> {
297        Ok(match self {
298            EmbeddedCalc::NonFungible(items) => items.clone(),
299            EmbeddedCalc::Fungible(value) => match value {
300                StrictVal::Number(StrictNum::Uint(val)) => {
301                    if val.eq(&u64::MIN) {
302                        vec![]
303                    } else {
304                        vec![value.clone()]
305                    }
306                }
307                _ => return Err(StateCalcError::UncountableState),
308            },
309        })
310    }
311
312    fn is_satisfied(&self, target: &StrictVal) -> bool {
313        match self {
314            EmbeddedCalc::NonFungible(items) => items.contains(target),
315            EmbeddedCalc::Fungible(value) => {
316                if value == target {
317                    true
318                } else if let StrictVal::Number(StrictNum::Uint(val)) = value {
319                    if let StrictVal::Number(StrictNum::Uint(tgt)) = target {
320                        val >= tgt
321                    } else {
322                        false
323                    }
324                } else {
325                    false
326                }
327            }
328        }
329    }
330}