Skip to main content

midnight_onchain_vm/
ops.rs

1// This file is part of midnight-ledger.
2// Copyright (C) Midnight Foundation
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// You may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14use crate::result_mode::{ResultMode, ResultModeVerify};
15use base_crypto::fab::AlignedValue;
16use base_crypto::repr::MemWrite;
17use derive_where::derive_where;
18#[cfg(feature = "proptest")]
19use proptest::prelude::*;
20#[cfg(feature = "proptest")]
21use proptest_derive::Arbitrary;
22use runtime_state::state::StateValue;
23use serde::{Deserialize, Serialize};
24#[cfg(feature = "proptest")]
25use serialize::randomised_serialization_test;
26use serialize::{Deserializable, Serializable, Tagged, tag_enforcement_test};
27use std::fmt::Debug;
28use storage::db::DB;
29#[cfg(feature = "proptest")]
30use storage::db::InMemoryDB;
31use storage::storable::Loader;
32use storage::storage::Array;
33use storage::{DefaultDB, Storable, arena::ArenaKey};
34use transient_crypto::curve::Fr;
35use transient_crypto::repr::FieldRepr;
36
37#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Serializable, Storable)]
38#[storable(base)]
39#[cfg_attr(feature = "proptest", derive(Arbitrary))]
40#[serde(tag = "tag", content = "value", rename_all = "camelCase")]
41#[tag = "impact-idx-key"]
42pub enum Key {
43    Value(AlignedValue),
44    Stack,
45}
46tag_enforcement_test!(Key);
47
48impl Debug for Key {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            Key::Value(v) => v.fmt(f),
52            Key::Stack => write!(f, "STK"),
53        }
54    }
55}
56
57impl TryFrom<Key> for AlignedValue {
58    type Error = ();
59    fn try_from(value: Key) -> Result<Self, Self::Error> {
60        match value {
61            Key::Value(v) => Ok(v),
62            Key::Stack => Err(()),
63        }
64    }
65}
66
67impl FieldRepr for Key {
68    fn field_repr<W: MemWrite<Fr>>(&self, writer: &mut W) {
69        match self {
70            Key::Stack => writer.write(&[(-1).into()]),
71            Key::Value(v) => v.field_repr(writer),
72        }
73    }
74
75    fn field_size(&self) -> usize {
76        match self {
77            Key::Stack => 1,
78            Key::Value(v) => v.field_size(),
79        }
80    }
81}
82
83#[non_exhaustive]
84#[derive_where(Clone, Eq, PartialEq; M)]
85#[derive(Serialize, Deserialize, Storable)]
86#[serde(bound(
87    serialize = "M::ReadResult : Serialize",
88    deserialize = "M::ReadResult : Deserialize<'de>"
89))]
90#[serde(rename_all = "lowercase", expecting = "operation")]
91#[cfg_attr(feature = "proptest", derive(Arbitrary))]
92#[storable(db = D)]
93#[tag = "impact-op[v1]"]
94#[phantom(M)]
95pub enum Op<M: ResultMode<D>, D: DB = DefaultDB> {
96    Noop {
97        #[cfg_attr(
98            feature = "proptest",
99            proptest(strategy = "any::<u32>().prop_map(|x| x % 0x1FFFFF)")
100        )]
101        n: u32,
102    },
103    Lt,
104    Eq,
105    Type,
106    Size,
107    New,
108    And,
109    Or,
110    Neg,
111    Log,
112    Root,
113    Pop,
114    Popeq {
115        cached: bool,
116        result: M::ReadResult,
117    },
118    Addi {
119        #[cfg_attr(
120            feature = "proptest",
121            proptest(strategy = "any::<u32>().prop_map(|x| x % 0x1FFFFF)")
122        )]
123        immediate: u32,
124    },
125    Subi {
126        #[cfg_attr(
127            feature = "proptest",
128            proptest(strategy = "any::<u32>().prop_map(|x| x % 0x1FFFFF)")
129        )]
130        immediate: u32,
131    },
132    Push {
133        storage: bool,
134        value: StateValue<D>,
135    },
136    Branch {
137        #[cfg_attr(
138            feature = "proptest",
139            proptest(strategy = "any::<u32>().prop_map(|x| x % 0x1FFFFF)")
140        )]
141        skip: u32,
142    },
143    Jmp {
144        #[cfg_attr(
145            feature = "proptest",
146            proptest(strategy = "any::<u32>().prop_map(|x| x % 0x1FFFFF)")
147        )]
148        skip: u32,
149    },
150    Add,
151    Sub,
152    Concat {
153        cached: bool,
154        #[cfg_attr(
155            feature = "proptest",
156            proptest(strategy = "any::<u32>().prop_map(|x| x % 0x1FFFFF)")
157        )]
158        n: u32,
159    },
160    Member,
161    Rem {
162        cached: bool,
163    },
164    Dup {
165        #[cfg_attr(
166            feature = "proptest",
167            proptest(strategy = "any::<u8>().prop_map(|x| x % 16)")
168        )]
169        n: u8,
170    },
171    Swap {
172        #[cfg_attr(
173            feature = "proptest",
174            proptest(strategy = "any::<u8>().prop_map(|x| x % 16)")
175        )]
176        n: u8,
177    },
178    Idx {
179        cached: bool,
180        #[serde(rename = "pushPath")]
181        push_path: bool,
182        #[cfg_attr(
183            feature = "proptest",
184            proptest(
185                strategy = "proptest::collection::vec(Key::arbitrary(), 1..2).prop_map_into()"
186            )
187        )]
188        path: Array<Key, D>,
189    },
190    Ins {
191        cached: bool,
192        #[cfg_attr(
193            feature = "proptest",
194            proptest(strategy = "any::<u8>().prop_map(|x| x % 16)", filter = "|v| *v != 0")
195        )]
196        n: u8,
197    },
198    Ckpt,
199}
200tag_enforcement_test!(Op<ResultModeVerify>);
201
202#[macro_export]
203macro_rules! key {
204    (stack) => {
205        Key::Stack
206    };
207    ($val:expr_2021) => {
208        Key::Value($val.into())
209    };
210}
211
212#[macro_export]
213macro_rules! op {
214    (noop $val:expr_2021) => { Op::Noop { n: $val } };
215    (lt) => { Op::Lt };
216    (eq) => { Op::Eq };
217    (type) => { Op::Type };
218    (size) => { Op::Size };
219    (new) => { Op::New };
220    (and) => { Op::And };
221    (or) => { Op::Or };
222    (neg) => { Op::Neg };
223    (log) => { Op::Log };
224    (root) => { Op::Root };
225    (pop) => { Op::Pop };
226    (popeq $res:expr_2021) => { Op::Popeq { cached: false, result: $res } };
227    (popeqc $res:expr_2021) => { Op::Popeq { cached: true, result: $res } };
228    (addi $imm:expr_2021) => { Op::Addi { immediate: $imm } };
229    (subi $imm:expr_2021) => { Op::Subi { immediate: $imm } };
230    (push $val:tt) => { Op::Push { storage: false, value: stval! $val } };
231    (pushs $val:tt) => { Op::Push { storage: true, value: stval! $val } };
232    (branch $skip:expr_2021) => { Op::Branch { skip: $skip } };
233    (jmp $skip:expr_2021) => { Op::Jmp { skip: $skip } };
234    (add) => { Op::Add };
235    (sub) => { Op::Sub };
236    (concat $n:expr_2021) => { Op::Concat { cached: false, n: $n } };
237    (concatc $n:expr_2021) => { Op::Concat { cached: true, n: $n } };
238    (member) => { Op::Member };
239    (rem) => { Op::Rem { cached: false } };
240    (remc) => { Op::Rem { cached: true } };
241    (dup $n:expr_2021) => { Op::Dup { n: $n } };
242    (swap $n:expr_2021) => { Op::Swap { n: $n } };
243    (idx [$($key:tt),*]) => { Op::Idx { cached: false, push_path: false, path: vec![$(key!($key)),*].into_iter().collect() }};
244    (idxc [$($key:tt),*]) => { Op::Idx { cached: true, push_path: false, path: vec![$(key!($key)),*].into_iter().collect() }};
245    (idxp [$($key:tt),*]) => { Op::Idx { cached: false, push_path: true, path: vec![$(key!($key)),*].into_iter().collect() }};
246    (idxpc [$($key:tt),*]) => { Op::Idx { cached: true, push_path: true, path: vec![$(key!($key)),*].into_iter().collect() }};
247    (ins $n:expr_2021) => { Op::Ins { cached: false, n: $n } };
248    (insc $n:expr_2021) => { Op::Ins { cached: true, n: $n } };
249    (ckpt) => { Op::Ckpt };
250}
251
252#[macro_export]
253macro_rules! ops_int {
254    [] => { std::iter::empty() };
255    [;] => { std::iter::empty() };
256    [$op0:tt ; $($ops:tt)*] => { std::iter::once(op!($op0)).chain(ops_int!($($ops)*)) };
257    [$op0:tt $op1:tt ; $($ops:tt)*] => { std::iter::once(op!($op0 $op1)).chain(ops_int!($($ops)*)) };
258    [$op0:tt $op1:tt $op2:tt ; $($ops:tt)*] => { std::iter::once(op!($op0 $op1 $op2)).chain(ops_int!($($ops)*)) };
259    [$op0:tt $op1:tt $op2:tt $op3:tt ; $($ops:tt)*] => { std::iter::once(op!($op0 $op1 $op2 $op3)).chain(ops_int!($($ops)*)) };
260    [$($ops:tt)*] => { std::iter::once(op!($($ops)*)) };
261}
262
263#[macro_export]
264macro_rules! ops {
265    [$($tts:tt)*] => { ops_int!($($tts)*).collect::<Vec<_>>() };
266}
267
268impl<M: ResultMode<D>, D: DB> Debug for Op<M, D> {
269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270        use Op::*;
271        match self {
272            Noop { n } => write!(f, "noop {n}"),
273            Lt => write!(f, "lt"),
274            Eq => write!(f, "eq"),
275            Type => write!(f, "type"),
276            Size => write!(f, "size"),
277            New => write!(f, "new"),
278            And => write!(f, "and"),
279            Or => write!(f, "or"),
280            Neg => write!(f, "neg"),
281            Log => write!(f, "log"),
282            Root => write!(f, "root"),
283            Pop => write!(f, "pop"),
284            Popeq {
285                cached: false,
286                result,
287            } => write!(f, "popeq {result:?}"),
288            Popeq {
289                cached: true,
290                result,
291            } => write!(f, "popeqc {result:?}"),
292            Addi { immediate } => write!(f, "addi {immediate:?}"),
293            Subi { immediate } => write!(f, "subi {immediate:?}"),
294            Push {
295                storage: false,
296                value,
297            } => write!(f, "push {value:?}"),
298            Push {
299                storage: true,
300                value,
301            } => write!(f, "pushs {value:?}"),
302            Branch { skip } => write!(f, "branch {skip}"),
303            Jmp { skip } => write!(f, "jmp {skip}"),
304            Add => write!(f, "add"),
305            Sub => write!(f, "sub"),
306            Concat { cached: false, n } => write!(f, "concat {n}"),
307            Concat { cached: true, n } => write!(f, "concatc {n}"),
308            Member => write!(f, "member"),
309            Rem { cached: false } => write!(f, "rem"),
310            Rem { cached: true } => write!(f, "remc"),
311            Dup { n } => write!(f, "dup {n}"),
312            Swap { n } => write!(f, "swap {n}"),
313            Idx {
314                cached,
315                push_path,
316                path,
317            } => {
318                write!(f, "idx")?;
319                if *push_path {
320                    write!(f, "p")?;
321                }
322                if *cached {
323                    write!(f, "c")?;
324                }
325                write!(f, " [")?;
326                let mut is_first = true;
327                for key in path.iter() {
328                    if is_first {
329                        is_first = false;
330                    } else {
331                        write!(f, ", ")?;
332                    }
333                    write!(f, "{key:?}")?;
334                }
335                write!(f, "]")
336            }
337            Ins { cached: false, n } => write!(f, "ins {n}"),
338            Ins { cached: true, n } => write!(f, "insc {n}"),
339            Ckpt => write!(f, "ckpt"),
340        }
341    }
342}
343
344impl<M: ResultMode<D>, D: DB> Op<M, D> {
345    pub fn translate<M2: ResultMode<D>, F: FnOnce(M::ReadResult) -> M2::ReadResult>(
346        self,
347        f: F,
348    ) -> Op<M2, D> {
349        match self {
350            Op::Noop { n } => Op::Noop { n },
351            Op::Lt => Op::Lt,
352            Op::Eq => Op::Eq,
353            Op::Type => Op::Type,
354            Op::Size => Op::Size,
355            Op::New => Op::New,
356            Op::And => Op::And,
357            Op::Or => Op::Or,
358            Op::Neg => Op::Neg,
359            Op::Log => Op::Log,
360            Op::Root => Op::Root,
361            Op::Pop => Op::Pop,
362            Op::Popeq { cached, result } => Op::Popeq {
363                cached,
364                result: f(result),
365            },
366            Op::Addi { immediate } => Op::Addi { immediate },
367            Op::Subi { immediate } => Op::Subi { immediate },
368            Op::Push { storage, value } => Op::Push { storage, value },
369            Op::Branch { skip } => Op::Branch { skip },
370            Op::Jmp { skip } => Op::Jmp { skip },
371            Op::Add => Op::Add,
372            Op::Sub => Op::Sub,
373            Op::Concat { cached, n } => Op::Concat { cached, n },
374            Op::Member => Op::Member,
375            Op::Rem { cached } => Op::Rem { cached },
376            Op::Dup { n } => Op::Dup { n },
377            Op::Swap { n } => Op::Swap { n },
378            Op::Idx {
379                cached,
380                push_path,
381                path,
382            } => Op::Idx {
383                cached,
384                push_path,
385                path,
386            },
387            Op::Ins { cached, n } => Op::Ins { cached, n },
388            Op::Ckpt => Op::Ckpt,
389        }
390    }
391}
392
393#[cfg(feature = "proptest")]
394#[allow(dead_code)]
395type SimpleOp = Op<ResultModeVerify, InMemoryDB>;
396#[cfg(feature = "proptest")]
397randomised_serialization_test!(SimpleOp);
398
399impl<D: DB> FieldRepr for Op<ResultModeVerify, D> {
400    fn field_repr<W: MemWrite<Fr>>(&self, writer: &mut W) {
401        use Op::*;
402        match self {
403            Noop { n } => writer.write(&vec![0x00.into(); *n as usize]),
404            Lt => writer.write(&[0x01.into()]),
405            Eq => writer.write(&[0x02.into()]),
406            Type => writer.write(&[0x03.into()]),
407            Size => writer.write(&[0x04.into()]),
408            New => writer.write(&[0x05.into()]),
409            And => writer.write(&[0x06.into()]),
410            Or => writer.write(&[0x07.into()]),
411            Neg => writer.write(&[0x08.into()]),
412            Log => writer.write(&[0x09.into()]),
413            Root => writer.write(&[0x0a.into()]),
414            Pop => writer.write(&[0x0b.into()]),
415            Popeq { cached, result } => {
416                writer.write(&[(0x0c + *cached as u8).into()]);
417                result.field_repr(writer);
418            }
419            Addi { immediate } => {
420                writer.write(&[0x0e.into()]);
421                immediate.field_repr(writer);
422            }
423            Subi { immediate } => {
424                writer.write(&[0x0f.into()]);
425                immediate.field_repr(writer);
426            }
427            Push { storage, value } => {
428                writer.write(&[(0x10 + *storage as u8).into()]);
429                value.field_repr(writer);
430            }
431            Branch { skip } => writer.write(&[0x12.into(), (*skip).into()]),
432            Jmp { skip } => writer.write(&[0x13.into(), (*skip).into()]),
433            Add => writer.write(&[0x14.into()]),
434            Sub => writer.write(&[0x15.into()]),
435            Concat { cached: false, n } => writer.write(&[0x16.into(), (*n).into()]),
436            Concat { cached: true, n } => writer.write(&[0x17.into(), (*n).into()]),
437            Member => writer.write(&[0x18.into()]),
438            Rem { cached: false } => writer.write(&[0x19.into()]),
439            Rem { cached: true } => writer.write(&[0x1a.into()]),
440            Dup { n } => writer.write(&[(0x30 | *n).into()]),
441            Swap { n } => writer.write(&[(0x40 | *n).into()]),
442            Idx {
443                cached,
444                push_path,
445                path,
446            } => {
447                if !path.is_empty() {
448                    let opcode = match (*cached, *push_path) {
449                        (false, false) => 0x50,
450                        (true, false) => 0x60,
451                        (false, true) => 0x70,
452                        (true, true) => 0x80,
453                    } | (path.len() as u8 - 1);
454                    writer.write(&[opcode.into()]);
455                    for entry in path.iter() {
456                        entry.field_repr(writer);
457                    }
458                }
459            }
460            Ins { cached: false, n } => writer.write(&[(0x90 | *n).into()]),
461            Ins { cached: true, n } => writer.write(&[(0xa0 | *n).into()]),
462            Ckpt => writer.write(&[0xff.into()]),
463        }
464    }
465
466    fn field_size(&self) -> usize {
467        use Op::*;
468        match self {
469            Lt
470            | Eq
471            | Type
472            | Size
473            | New
474            | And
475            | Or
476            | Neg
477            | Log
478            | Root
479            | Pop
480            | Add
481            | Sub
482            | Member
483            | Rem { .. }
484            | Dup { .. }
485            | Swap { .. }
486            | Ins { .. }
487            | Ckpt => 1,
488            Noop { n } => *n as usize,
489            Branch { .. } | Jmp { .. } | Concat { .. } => 2,
490            Addi { immediate } | Subi { immediate } => 1 + immediate.field_size(),
491            Popeq { result, .. } => 1 + result.field_size(),
492            Push { value, .. } => 1 + value.field_size(),
493            Idx { path, .. } => 1 + path.iter().map(|item| item.field_size()).sum::<usize>(),
494        }
495    }
496}
497
498pub use {key, op, ops, ops_int};
499
500#[cfg(test)]
501mod tests {
502    use storage::DefaultDB;
503
504    use super::Op;
505    use crate::result_mode::ResultModeGather;
506    use runtime_state::state::StateValue;
507    use storage::storage::HashMap;
508
509    #[test]
510    fn diagnostic_test_map_serialization_stability() {
511        let op: Op<ResultModeGather, DefaultDB> = Op::Push {
512            storage: false,
513            value: StateValue::Map(HashMap::new()),
514        };
515        let mut ser = Vec::new();
516        serialize::Serializable::serialize(&op, &mut ser).unwrap();
517        let op2: Op<ResultModeGather, DefaultDB> =
518            serialize::Deserializable::deserialize(&mut &ser[..], 0).unwrap();
519        assert_eq!(op, op2);
520    }
521}