essential_constraint_asm/
lib.rs#![doc = essential_asm_gen::gen_constraint_ops_docs_table!()]
#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
use core::fmt;
#[doc(inline)]
pub use essential_types::Word;
#[doc(inline)]
pub use op::{Constraint as Op, *};
#[doc(inline)]
pub use opcode::{Constraint as Opcode, InvalidOpcodeError, NotEnoughBytesError};
mod op {
    pub trait ToBytes {
        type Bytes: IntoIterator<Item = u8>;
        fn to_bytes(&self) -> Self::Bytes;
    }
    pub trait ToOpcode {
        type Opcode;
        fn to_opcode(&self) -> Self::Opcode;
    }
    pub trait TryFromBytes: Sized {
        type Error: core::fmt::Debug + core::fmt::Display;
        fn try_from_bytes(
            bytes: &mut impl Iterator<Item = u8>,
        ) -> Option<Result<Self, Self::Error>>;
    }
    essential_asm_gen::gen_constraint_op_decls!();
    essential_asm_gen::gen_constraint_op_impls!();
    pub mod bytes_iter {
        essential_asm_gen::gen_constraint_op_bytes_iter!();
    }
    pub mod short {
        use super::{Constraint as Op, *};
        essential_asm_gen::gen_constraint_op_consts!();
    }
}
pub mod opcode {
    use core::fmt;
    pub trait ParseOp {
        type Op;
        type Error: core::fmt::Debug + core::fmt::Display;
        fn parse_op(&self, bytes: &mut impl Iterator<Item = u8>) -> Result<Self::Op, Self::Error>;
    }
    #[derive(Debug)]
    pub struct InvalidOpcodeError(pub u8);
    #[derive(Debug)]
    pub struct NotEnoughBytesError;
    impl fmt::Display for InvalidOpcodeError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "Invalid Opcode 0x{:02X}", self.0)
        }
    }
    impl fmt::Display for NotEnoughBytesError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "Provided iterator did not yield enough bytes")
        }
    }
    #[cfg(feature = "std")]
    impl std::error::Error for InvalidOpcodeError {}
    #[cfg(feature = "std")]
    impl std::error::Error for NotEnoughBytesError {}
    essential_asm_gen::gen_constraint_opcode_decls!();
    essential_asm_gen::gen_constraint_opcode_impls!();
}
#[derive(Debug)]
pub enum FromBytesError {
    InvalidOpcode(InvalidOpcodeError),
    NotEnoughBytes(NotEnoughBytesError),
}
impl fmt::Display for FromBytesError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("failed to parse ops from bytes: ")?;
        match self {
            Self::InvalidOpcode(err) => err.fmt(f),
            Self::NotEnoughBytes(err) => err.fmt(f),
        }
    }
}
#[cfg(feature = "std")]
impl std::error::Error for FromBytesError {}
impl From<InvalidOpcodeError> for FromBytesError {
    fn from(err: InvalidOpcodeError) -> Self {
        Self::InvalidOpcode(err)
    }
}
impl From<NotEnoughBytesError> for FromBytesError {
    fn from(err: NotEnoughBytesError) -> Self {
        Self::NotEnoughBytes(err)
    }
}
pub fn from_bytes(
    bytes: impl IntoIterator<Item = u8>,
) -> impl Iterator<Item = Result<Op, FromBytesError>> {
    let mut iter = bytes.into_iter();
    core::iter::from_fn(move || Op::try_from_bytes(&mut iter))
}
pub fn to_bytes(ops: impl IntoIterator<Item = Op>) -> impl Iterator<Item = u8> {
    ops.into_iter().flat_map(|op| op.to_bytes())
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn opcode_roundtrip_u8() {
        for byte in 0..=u8::MAX {
            if let Ok(opcode) = Opcode::try_from(byte) {
                println!("{byte:02X}: {opcode:?}");
                assert_eq!(u8::from(opcode), byte);
            }
        }
    }
    fn roundtrip(ops: Vec<Op>) {
        assert!(!ops.is_empty());
        let bytes: Vec<_> = to_bytes(ops.iter().cloned()).collect();
        assert_eq!(
            ops,
            from_bytes(bytes).collect::<Result<Vec<_>, _>>().unwrap()
        );
    }
    #[test]
    fn roundtrip_args_start() {
        let ops: Vec<Op> = vec![
            Stack::Push(0x1234567812345678).into(),
            Stack::Push(0x0F0F0F0F0F0F0F0F).into(),
            Stack::Swap.into(),
            Stack::Dup.into(),
        ];
        roundtrip(ops);
    }
    #[test]
    fn roundtrip_args_end() {
        let ops: Vec<Op> = vec![
            Stack::Swap.into(),
            Stack::Dup.into(),
            Stack::Push(0x0F0F0F0F0F0F0F0F).into(),
        ];
        roundtrip(ops);
    }
    #[test]
    fn roundtrip_args_interspersed() {
        let ops: Vec<Op> = vec![
            Stack::Push(0x1234567812345678).into(),
            Stack::Swap.into(),
            Stack::Push(0x0F0F0F0F0F0F0F0F).into(),
            Stack::Dup.into(),
            Stack::Push(0x1234567812345678).into(),
        ];
        roundtrip(ops);
    }
    #[test]
    fn roundtrip_no_args() {
        let ops: Vec<Op> = vec![
            Access::ThisAddress.into(),
            Access::ThisContractAddress.into(),
            Stack::Swap.into(),
            Stack::Dup.into(),
            Crypto::Sha256.into(),
        ];
        roundtrip(ops);
    }
    fn expect_invalid_opcode(opcode_byte: u8) {
        let bytes = vec![opcode_byte];
        let err = from_bytes(bytes)
            .collect::<Result<Vec<_>, _>>()
            .unwrap_err();
        match err {
            FromBytesError::InvalidOpcode(InvalidOpcodeError(byte)) => {
                assert_eq!(byte, opcode_byte)
            }
            _ => panic!("unexpected error variant"),
        }
    }
    #[test]
    fn invalid_opcode_0x00() {
        let opcode_byte = 0x00;
        expect_invalid_opcode(opcode_byte);
    }
    #[test]
    fn invalid_opcode_0xff() {
        let opcode_byte = 0xFF;
        expect_invalid_opcode(opcode_byte);
    }
    #[test]
    fn not_enough_bytes() {
        let opcode_byte = opcode::Stack::Push as u8;
        let bytes = vec![opcode_byte];
        let err = from_bytes(bytes)
            .collect::<Result<Vec<_>, _>>()
            .unwrap_err();
        match err {
            FromBytesError::NotEnoughBytes(_) => (),
            _ => panic!("unexpected error variant"),
        }
    }
}