everscale-asm 0.0.9

Rust implementation of TVM Assembler
Documentation
use everscale_types::prelude::*;

pub use asm::{ArgType, AsmError, ExpectedArgType};
pub use ast::{ParserError, Span};

mod asm;
mod ast;
mod util;

pub struct Code<'a> {
    text: &'a str,
    ast: Option<ast::Code<'a>>,
    parser_errors: Vec<ast::ParserError>,
}

impl<'a> Code<'a> {
    pub fn assemble(text: &'a str) -> anyhow::Result<Cell> {
        let cell = Self::parse(text).try_into_valid()?.assemble()?;
        Ok(cell)
    }

    pub fn parse(text: &'a str) -> Self {
        let (ast, parser_errors) = ast::parse(text).into_output_errors();

        Self {
            text,
            ast,
            parser_errors,
        }
    }

    pub fn check(&self) -> Vec<AsmError> {
        if let Some(ast::Code { items, span }) = &self.ast {
            asm::check(items, *span)
        } else {
            Vec::new()
        }
    }

    pub fn try_into_valid(self) -> Result<ValidCode<'a>, ast::ParserError> {
        if self.parser_errors.is_empty() {
            if let Some(ast::Code { items, span }) = self.ast {
                return Ok(ValidCode {
                    _text: self.text,
                    span,
                    ast: items,
                });
            }
        }

        Err(self
            .parser_errors
            .into_iter()
            .next()
            .unwrap_or(ast::ParserError::UnknownError))
    }

    pub fn parser_errors(&self) -> &[ast::ParserError] {
        &self.parser_errors
    }
}

pub struct ValidCode<'a> {
    _text: &'a str,
    span: ast::Span,
    ast: Vec<ast::Stmt<'a>>,
}

impl ValidCode<'_> {
    pub fn assemble(&self) -> Result<Cell, asm::AsmError> {
        asm::assemble(&self.ast, self.span)
    }

    pub fn check(self) -> Vec<asm::AsmError> {
        asm::check(&self.ast, self.span)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn preprocessed_wallet() -> anyhow::Result<()> {
        let cell = Code::assemble(
            r##"
            SETCP0 IFNOTRET                 // msg
            LDREF SWAP DUP HASHCU           // sign msg' hash
            SWAP CTOS LDU 64 LDU 16 PLDREF  // sign hash valid_until msg_seqno actions
            PUSH c4 CTOS                    // sign hash valid_until msg_seqno actions c4s
            LDU 256 PLDU 16                 // sign hash valid_until msg_seqno actions key seqno
            DUP INC PUSHPOW2 16 MOD PUSH s2 // sign hash valid_until msg_seqno actions key seqno new_seqno key
            NEWC STU 256 STU 16 ENDC POP c4 // sign hash valid_until msg_seqno actions key seqno
            XCHG3 s4, s3, s0                // sign hash actions key valid_until seqno
            XCHG s4, s6                     // actions hash sign key valid_until seqno
            EQUAL THROWIFNOT 33             // actions hash sign key valid_until
            NOW GEQ THROWIFNOT 34           // actions hash sign key
            CHKSIGNU THROWIFNOT 35          // actions
            ACCEPT                          // actions
            POP c5
            "##,
        )?;

        assert_eq!(
            cell.repr_hash(),
            &"45ebbce9b5d235886cb6bfe1c3ad93b708de058244892365c9ee0dfe439cb7b5"
                .parse::<HashBytes>()
                .unwrap()
        );

        println!("{}", cell.display_tree());

        Ok(())
    }

    #[test]
    fn stack_ops() -> anyhow::Result<()> {
        let cell = Code::assemble(
            r##"
            XCHG s1, s2
            NOP
            SWAP
            XCHG3 s1, s2, s3
            "##,
        )?;

        assert_eq!(
            cell.repr_hash(),
            &"5f099122adde2ed3712374da4cd4e04e3214f0ddd7f155ffea923f1f2ab42d2b"
                .parse::<HashBytes>()
                .unwrap()
        );

        println!("{}", cell.display_tree());

        Ok(())
    }

    #[test]
    fn pushint() -> anyhow::Result<()> {
        let cell_tiny = Code::assemble("INT 7")?;
        assert_eq!(cell_tiny.data(), &[0x77]);

        let cell_byte = Code::assemble("INT 120")?;
        assert_eq!(cell_byte.data(), &[0x80, 120]);

        let cell_short = Code::assemble("INT 16000")?;
        assert_eq!(
            cell_short.data(),
            &[0x81, ((16000 >> 8) & 0xff) as u8, ((16000) & 0xff) as u8]
        );

        let cell_big = Code::assemble("INT 123123123123123123")?;
        assert_eq!(cell_big.data(), hex::decode("8229b56bd40163f3b3")?);

        let cell_max = Code::assemble(
            "INT 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
        )?;
        assert_eq!(
            cell_max.data(),
            hex::decode("82f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")?
        );

        let cell_neg_max = Code::assemble(
            "INT -0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
        )?;
        assert_eq!(
            cell_neg_max.data(),
            hex::decode("82f70000000000000000000000000000000000000000000000000000000000000001")?
        );

        Ok(())
    }

    #[test]
    fn pushintx() -> anyhow::Result<()> {
        let cell_tiny = Code::assemble("INTX 7")?;
        assert_eq!(cell_tiny.data(), &[0x77]);

        let cell_byte = Code::assemble("INTX 120")?;
        assert_eq!(cell_byte.data(), &[0x80, 120]);

        let cell_short = Code::assemble("INTX 16000")?;
        assert_eq!(
            cell_short.data(),
            &[0x81, ((16000 >> 8) & 0xff) as u8, ((16000) & 0xff) as u8]
        );

        let cell_big = Code::assemble("INTX 123123123123123123")?;
        assert_eq!(cell_big.data(), hex::decode("8229b56bd40163f3b3")?);

        let cell_big = Code::assemble("INTX 90596966400")?;
        assert_eq!(cell_big.data(), hex::decode("8102a3aa1a")?);

        let cell_max = Code::assemble(
            "INTX 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
        )?;
        assert_eq!(cell_max.data(), hex::decode("84ff")?);

        let cell_neg_max = Code::assemble(
            "INTX -0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
        )?;
        assert_eq!(
            cell_neg_max.data(),
            hex::decode("82f70000000000000000000000000000000000000000000000000000000000000001")?
        );

        Ok(())
    }

    #[test]
    fn display() -> anyhow::Result<()> {
        let code = Code::assemble("PUSHSLICE x{6_}")?;
        println!("{}", code.display_tree());
        Ok(())
    }

    #[test]
    fn complex_asm() -> anyhow::Result<()> {
        const CODE: &str = include_str!("tests/walletv3.tvm");

        let output = Code::assemble(CODE).unwrap();
        assert_eq!(
            output.repr_hash(),
            &"84dafa449f98a6987789ba232358072bc0f76dc4524002a5d0918b9a75d2d599"
                .parse::<HashBytes>()?
        );
        Ok(())
    }
}