essential_constraint_asm/
lib.rs

1//! Assembly for checking constraints.
2//!
3//! # Op Table
4#![doc = essential_asm_gen::gen_constraint_ops_docs_table!()]
5#![cfg_attr(not(feature = "std"), no_std)]
6#![forbid(unsafe_code)]
7#![warn(missing_docs)]
8
9use core::fmt;
10#[doc(inline)]
11pub use essential_types::Word;
12#[doc(inline)]
13pub use op::{Constraint as Op, *};
14#[doc(inline)]
15pub use opcode::{Constraint as Opcode, InvalidOpcodeError, NotEnoughBytesError};
16
17/// Typed representation of an operation its associated data.
18mod op {
19    /// Operation types that may be converted to their serialized form in bytes.
20    pub trait ToBytes {
21        /// The iterator yielding bytes.
22        type Bytes: IntoIterator<Item = u8>;
23        /// Convert the operation to its serialized form in bytes.
24        fn to_bytes(&self) -> Self::Bytes;
25    }
26
27    /// Allows for converting an `Op` into its associated `Opcode`.
28    pub trait ToOpcode {
29        /// The associated `Opcode` type.
30        type Opcode;
31        /// The `opcode` associated with this operation.
32        fn to_opcode(&self) -> Self::Opcode;
33    }
34
35    /// Operation types that may be parsed from a bytecode representation.
36    pub trait TryFromBytes: Sized {
37        /// Represents any error that might occur while parsing an op from bytes.
38        type Error: core::fmt::Debug + core::fmt::Display;
39        /// Parse a single operation from the given iterator yielding bytes.
40        ///
41        /// Returns `None` in the case that the given iterator is empty.
42        fn try_from_bytes(
43            bytes: &mut impl Iterator<Item = u8>,
44        ) -> Option<Result<Self, Self::Error>>;
45    }
46
47    essential_asm_gen::gen_constraint_op_decls!();
48    essential_asm_gen::gen_constraint_op_impls!();
49
50    /// Provides the operation type bytes iterators.
51    pub mod bytes_iter {
52        essential_asm_gen::gen_constraint_op_bytes_iter!();
53    }
54
55    /// Short hand names for the operations.
56    pub mod short {
57        use super::{Constraint as Op, *};
58        essential_asm_gen::gen_constraint_op_consts!();
59    }
60}
61
62/// Typed representation of the opcode, without any associated data.
63pub mod opcode {
64    use core::fmt;
65
66    /// Parse the operation associated with the opcode.
67    pub trait ParseOp {
68        /// The operation associated with the opcode.
69        type Op;
70        /// Any error that might occur while parsing.
71        type Error: core::fmt::Debug + core::fmt::Display;
72        /// Attempt to parse the operation associated with the opcode from the given bytes.
73        ///
74        /// Only consumes the bytes necessary to construct any associated data.
75        ///
76        /// Returns an error in the case that the given `bytes` iterator
77        /// contains insufficient bytes to parse the op.
78        fn parse_op(&self, bytes: &mut impl Iterator<Item = u8>) -> Result<Self::Op, Self::Error>;
79    }
80
81    /// An attempt to parse a byte as an opcode failed.
82    #[derive(Debug)]
83    pub struct InvalidOpcodeError(pub u8);
84
85    /// An error occurring within `Opcode::parse_op` in the case that the
86    /// provided bytes iterator contains insufficient bytes for the expected
87    /// associated operation data.
88    #[derive(Debug)]
89    pub struct NotEnoughBytesError;
90
91    impl fmt::Display for InvalidOpcodeError {
92        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93            write!(f, "Invalid Opcode 0x{:02X}", self.0)
94        }
95    }
96
97    impl fmt::Display for NotEnoughBytesError {
98        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99            write!(f, "Provided iterator did not yield enough bytes")
100        }
101    }
102
103    #[cfg(feature = "std")]
104    impl std::error::Error for InvalidOpcodeError {}
105
106    #[cfg(feature = "std")]
107    impl std::error::Error for NotEnoughBytesError {}
108
109    essential_asm_gen::gen_constraint_opcode_decls!();
110    essential_asm_gen::gen_constraint_opcode_impls!();
111}
112
113/// Errors that can occur while parsing ops from bytes.
114#[derive(Debug)]
115pub enum FromBytesError {
116    /// An invalid opcode was encountered.
117    InvalidOpcode(InvalidOpcodeError),
118    /// The bytes iterator did not contain enough bytes for a particular operation.
119    NotEnoughBytes(NotEnoughBytesError),
120}
121
122impl fmt::Display for FromBytesError {
123    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124        f.write_str("failed to parse ops from bytes: ")?;
125        match self {
126            Self::InvalidOpcode(err) => err.fmt(f),
127            Self::NotEnoughBytes(err) => err.fmt(f),
128        }
129    }
130}
131
132#[cfg(feature = "std")]
133impl std::error::Error for FromBytesError {}
134
135impl From<InvalidOpcodeError> for FromBytesError {
136    fn from(err: InvalidOpcodeError) -> Self {
137        Self::InvalidOpcode(err)
138    }
139}
140
141impl From<NotEnoughBytesError> for FromBytesError {
142    fn from(err: NotEnoughBytesError) -> Self {
143        Self::NotEnoughBytes(err)
144    }
145}
146
147/// Parse operations from the given iterator yielding bytes.
148///
149/// Returns an iterator yielding `Op` results, erroring in the case that an
150/// invalid opcode is encountered or the iterator contains insufficient bytes
151/// for an operation.
152pub fn from_bytes(
153    bytes: impl IntoIterator<Item = u8>,
154) -> impl Iterator<Item = Result<Op, FromBytesError>> {
155    let mut iter = bytes.into_iter();
156    core::iter::from_fn(move || Op::try_from_bytes(&mut iter))
157}
158
159/// Convert the given iterator yielding operations into and iterator yielding
160/// the serialized form in bytes.
161pub fn to_bytes(ops: impl IntoIterator<Item = Op>) -> impl Iterator<Item = u8> {
162    ops.into_iter().flat_map(|op| op.to_bytes())
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn opcode_roundtrip_u8() {
171        for byte in 0..=u8::MAX {
172            if let Ok(opcode) = Opcode::try_from(byte) {
173                println!("{byte:02X}: {opcode:?}");
174                assert_eq!(u8::from(opcode), byte);
175            }
176        }
177    }
178
179    fn roundtrip(ops: Vec<Op>) {
180        assert!(!ops.is_empty());
181        let bytes: Vec<_> = to_bytes(ops.iter().cloned()).collect();
182        assert_eq!(
183            ops,
184            from_bytes(bytes).collect::<Result<Vec<_>, _>>().unwrap()
185        );
186    }
187
188    #[test]
189    fn roundtrip_args_start() {
190        let ops: Vec<Op> = vec![
191            Stack::Push(0x1234567812345678).into(),
192            Stack::Push(0x0F0F0F0F0F0F0F0F).into(),
193            Stack::Swap.into(),
194            Stack::Dup.into(),
195        ];
196        roundtrip(ops);
197    }
198
199    #[test]
200    fn roundtrip_args_end() {
201        let ops: Vec<Op> = vec![
202            Stack::Swap.into(),
203            Stack::Dup.into(),
204            Stack::Push(0x0F0F0F0F0F0F0F0F).into(),
205        ];
206        roundtrip(ops);
207    }
208
209    #[test]
210    fn roundtrip_args_interspersed() {
211        let ops: Vec<Op> = vec![
212            Stack::Push(0x1234567812345678).into(),
213            Stack::Swap.into(),
214            Stack::Push(0x0F0F0F0F0F0F0F0F).into(),
215            Stack::Dup.into(),
216            Stack::Push(0x1234567812345678).into(),
217        ];
218        roundtrip(ops);
219    }
220
221    #[test]
222    fn roundtrip_no_args() {
223        let ops: Vec<Op> = vec![
224            Access::ThisAddress.into(),
225            Access::ThisContractAddress.into(),
226            Stack::Swap.into(),
227            Stack::Dup.into(),
228            Crypto::Sha256.into(),
229        ];
230        roundtrip(ops);
231    }
232
233    fn expect_invalid_opcode(opcode_byte: u8) {
234        let bytes = vec![opcode_byte];
235        let err = from_bytes(bytes)
236            .collect::<Result<Vec<_>, _>>()
237            .unwrap_err();
238        match err {
239            FromBytesError::InvalidOpcode(InvalidOpcodeError(byte)) => {
240                assert_eq!(byte, opcode_byte)
241            }
242            _ => panic!("unexpected error variant"),
243        }
244    }
245
246    #[test]
247    fn invalid_opcode_0x00() {
248        let opcode_byte = 0x00;
249        expect_invalid_opcode(opcode_byte);
250    }
251
252    #[test]
253    fn invalid_opcode_0xff() {
254        let opcode_byte = 0xFF;
255        expect_invalid_opcode(opcode_byte);
256    }
257
258    #[test]
259    fn not_enough_bytes() {
260        let opcode_byte = opcode::Stack::Push as u8;
261        let bytes = vec![opcode_byte];
262        let err = from_bytes(bytes)
263            .collect::<Result<Vec<_>, _>>()
264            .unwrap_err();
265        match err {
266            FromBytesError::NotEnoughBytes(_) => (),
267            _ => panic!("unexpected error variant"),
268        }
269    }
270}