sonicapi/state/
arithmetics.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::str::FromStr;
25
26use aluvm::LibSite;
27use strict_types::value::StrictNum;
28use strict_types::StrictVal;
29
30use crate::LIB_NAME_SONIC;
31
32#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
33#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
34#[strict_type(lib = LIB_NAME_SONIC, tags = custom)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
36pub enum StateArithm {
37    #[strict_type(tag = 0x00, dumb)]
38    Fungible,
39
40    #[strict_type(tag = 0x01)]
41    NonFungible,
42    // In the future more arithmetics can be added.
43    /// Execute a custom function.
44    #[strict_type(tag = 0xFF)]
45    AluVM(
46        /// The entry point to the script (virtual machine uses libraries from
47        /// [`crate::Semantics`]).
48        LibSite,
49    ),
50}
51
52impl StateArithm {
53    pub fn calculator(&self) -> StateCalc {
54        match self {
55            Self::Fungible => StateCalc::Fungible(StrictVal::Number(StrictNum::Uint(0))),
56            Self::NonFungible => StateCalc::NonFungible(vec![]),
57            Self::AluVM(_) => StateCalc::AluVM,
58        }
59    }
60}
61
62#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)]
63#[display(doc_comments)]
64pub enum StateCalcError {
65    /// integer overflow during state computation.
66    Overflow,
67
68    /// state cannot be computed.
69    UncountableState,
70
71    /// AluVM is not yet supported for the state arithmetics.
72    Unsupported,
73}
74
75#[derive(Clone, Eq, PartialEq, Debug)]
76pub enum StateCalc {
77    NonFungible(Vec<StrictVal>),
78    Fungible(StrictVal),
79    // AluVM is reserved for the future. We need it here to avoid breaking changes.
80    AluVM,
81}
82
83impl StateCalc {
84    pub fn accumulate(&mut self, state: &StrictVal) -> Result<(), StateCalcError> {
85        match self {
86            Self::NonFungible(states) => {
87                states.push(state.clone());
88                Ok(())
89            }
90            Self::Fungible(value) => {
91                let (val, add) = match (state, value) {
92                    // TODO: Use `if let` guards to avoid `unwrap` once rust supports them
93                    (StrictVal::String(s), StrictVal::Number(StrictNum::Uint(val))) if u64::from_str(s).is_ok() => {
94                        let add = u64::from_str(s).unwrap();
95                        (val, add)
96                    }
97                    (StrictVal::Number(StrictNum::Uint(add)), StrictVal::Number(StrictNum::Uint(val))) => (val, *add),
98                    _ => return Err(StateCalcError::UncountableState),
99                };
100                *val = val.checked_add(add).ok_or(StateCalcError::Overflow)?;
101                Ok(())
102            }
103            Self::AluVM => Err(StateCalcError::Unsupported),
104        }
105    }
106
107    pub fn lessen(&mut self, state: &StrictVal) -> Result<(), StateCalcError> {
108        match self {
109            Self::NonFungible(states) => {
110                if let Some(pos) = states.iter().position(|s| s == state) {
111                    states.remove(pos);
112                    Ok(())
113                } else {
114                    Err(StateCalcError::UncountableState)
115                }
116            }
117            Self::Fungible(value) => {
118                let (val, dec) = match (state, value) {
119                    // TODO: Use `if let` guards to avoid `unwrap` once rust supports them
120                    (StrictVal::String(s), StrictVal::Number(StrictNum::Uint(val))) if u64::from_str(s).is_ok() => {
121                        let dec = u64::from_str(s).unwrap();
122                        (val, dec)
123                    }
124                    (StrictVal::Number(StrictNum::Uint(dec)), StrictVal::Number(StrictNum::Uint(val))) => (val, *dec),
125                    _ => return Err(StateCalcError::UncountableState),
126                };
127                if dec > *val {
128                    return Err(StateCalcError::Overflow);
129                }
130                *val -= dec;
131                Ok(())
132            }
133            Self::AluVM => Err(StateCalcError::Unsupported),
134        }
135    }
136
137    pub fn diff(&self) -> Result<Vec<StrictVal>, StateCalcError> {
138        Ok(match self {
139            Self::NonFungible(items) => items.clone(),
140            Self::Fungible(value) => match value {
141                StrictVal::Number(StrictNum::Uint(val)) => {
142                    if val.eq(&u64::MIN) {
143                        vec![]
144                    } else {
145                        vec![value.clone()]
146                    }
147                }
148                _ => return Err(StateCalcError::UncountableState),
149            },
150            Self::AluVM => return Err(StateCalcError::Unsupported),
151        })
152    }
153
154    pub fn is_satisfied(&self, target: &StrictVal) -> bool {
155        match self {
156            Self::NonFungible(items) => items.contains(target),
157            Self::Fungible(value) => {
158                if value == target {
159                    true
160                } else if let StrictVal::Number(StrictNum::Uint(val)) = value {
161                    if let StrictVal::Number(StrictNum::Uint(tgt)) = target {
162                        val >= tgt
163                    } else {
164                        false
165                    }
166                } else {
167                    false
168                }
169            }
170            Self::AluVM => false,
171        }
172    }
173}
174
175#[cfg(test)]
176mod test {
177    #![cfg_attr(coverage_nightly, coverage(off))]
178    use super::*;
179
180    #[test]
181    fn arithm_fungible() {
182        let mut calc = StateArithm::Fungible.calculator();
183        let mut acc = 0u64;
184        for n in 0..5u64 {
185            calc.accumulate(&svnum!(n)).unwrap();
186            acc += n;
187        }
188        assert_eq!(calc.diff().unwrap(), [svnum!(acc)]);
189        assert!(calc.is_satisfied(&svnum!(acc)));
190        assert!(calc.is_satisfied(&svnum!(acc - 1)));
191        assert!(!calc.is_satisfied(&svnum!(acc + 1)));
192
193        for n in 0..2u64 {
194            calc.lessen(&svnum!(n)).unwrap();
195            acc -= n;
196        }
197
198        assert_eq!(calc.diff().unwrap(), [svnum!(acc)]);
199        assert!(calc.is_satisfied(&svnum!(acc)));
200        assert!(calc.is_satisfied(&svnum!(acc - 1)));
201        assert!(!calc.is_satisfied(&svnum!(acc + 1)));
202    }
203
204    #[test]
205    fn arithm_nonfungible() {
206        let mut calc = StateArithm::NonFungible.calculator();
207        for n in 0..5u64 {
208            calc.accumulate(&svnum!(n)).unwrap();
209        }
210        assert_eq!(calc.diff().unwrap(), [svnum!(0u64), svnum!(1u64), svnum!(2u64), svnum!(3u64), svnum!(4u64)]);
211        assert!(calc.is_satisfied(&svnum!(0u64)));
212        assert!(calc.is_satisfied(&svnum!(1u64)));
213        assert!(calc.is_satisfied(&svnum!(2u64)));
214        assert!(calc.is_satisfied(&svnum!(3u64)));
215        assert!(calc.is_satisfied(&svnum!(4u64)));
216        assert!(!calc.is_satisfied(&svnum!(5u64)));
217
218        for n in 0..2u64 {
219            calc.lessen(&svnum!(n)).unwrap();
220        }
221
222        assert_eq!(calc.diff().unwrap(), [svnum!(2u64), svnum!(3u64), svnum!(4u64)]);
223        assert!(!calc.is_satisfied(&svnum!(0u64)));
224        assert!(!calc.is_satisfied(&svnum!(1u64)));
225        assert!(calc.is_satisfied(&svnum!(2u64)));
226        assert!(calc.is_satisfied(&svnum!(3u64)));
227        assert!(calc.is_satisfied(&svnum!(4u64)));
228        assert!(!calc.is_satisfied(&svnum!(5u64)));
229    }
230}