tycho_asm/
lib.rs

1use tycho_types::prelude::*;
2
3pub use crate::asm::{ArgType, AsmError, ExpectedArgType};
4pub use crate::ast::{ParserError, Span};
5
6mod asm;
7mod ast;
8mod util;
9
10pub struct Code<'a> {
11    text: &'a str,
12    ast: Option<ast::Code<'a>>,
13    parser_errors: Vec<ast::ParserError>,
14}
15
16impl<'a> Code<'a> {
17    pub fn assemble(text: &'a str) -> anyhow::Result<Cell> {
18        let cell = Self::parse(text).try_into_valid()?.assemble()?;
19        Ok(cell)
20    }
21
22    pub fn parse(text: &'a str) -> Self {
23        let (ast, parser_errors) = ast::parse(text).into_output_errors();
24
25        Self {
26            text,
27            ast,
28            parser_errors,
29        }
30    }
31
32    pub fn check(&self) -> Vec<AsmError> {
33        if let Some(ast::Code { items, span }) = &self.ast {
34            asm::check(items, *span)
35        } else {
36            Vec::new()
37        }
38    }
39
40    pub fn try_into_valid(self) -> Result<ValidCode<'a>, ast::ParserError> {
41        if self.parser_errors.is_empty() {
42            if let Some(ast::Code { items, span }) = self.ast {
43                return Ok(ValidCode {
44                    _text: self.text,
45                    span,
46                    ast: items,
47                });
48            }
49        }
50
51        Err(self
52            .parser_errors
53            .into_iter()
54            .next()
55            .unwrap_or(ast::ParserError::UnknownError))
56    }
57
58    pub fn parser_errors(&self) -> &[ast::ParserError] {
59        &self.parser_errors
60    }
61}
62
63pub struct ValidCode<'a> {
64    _text: &'a str,
65    span: ast::Span,
66    ast: Vec<ast::Stmt<'a>>,
67}
68
69impl ValidCode<'_> {
70    pub fn assemble(&self) -> Result<Cell, asm::AsmError> {
71        asm::assemble(&self.ast, self.span)
72    }
73
74    pub fn check(self) -> Vec<asm::AsmError> {
75        asm::check(&self.ast, self.span)
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn preprocessed_wallet() -> anyhow::Result<()> {
85        let cell = Code::assemble(
86            r##"
87            SETCP0 IFNOTRET                 // msg
88            LDREF SWAP DUP HASHCU           // sign msg' hash
89            SWAP CTOS LDU 64 LDU 16 PLDREF  // sign hash valid_until msg_seqno actions
90            PUSH c4 CTOS                    // sign hash valid_until msg_seqno actions c4s
91            LDU 256 PLDU 16                 // sign hash valid_until msg_seqno actions key seqno
92            DUP INC PUSHPOW2 16 MOD PUSH s2 // sign hash valid_until msg_seqno actions key seqno new_seqno key
93            NEWC STU 256 STU 16 ENDC POP c4 // sign hash valid_until msg_seqno actions key seqno
94            XCHG3 s4, s3, s0                // sign hash actions key valid_until seqno
95            XCHG s4, s6                     // actions hash sign key valid_until seqno
96            EQUAL THROWIFNOT 33             // actions hash sign key valid_until
97            NOW GEQ THROWIFNOT 34           // actions hash sign key
98            CHKSIGNU THROWIFNOT 35          // actions
99            ACCEPT                          // actions
100            POP c5
101            "##,
102        )?;
103
104        assert_eq!(
105            cell.repr_hash(),
106            &"45ebbce9b5d235886cb6bfe1c3ad93b708de058244892365c9ee0dfe439cb7b5"
107                .parse::<HashBytes>()
108                .unwrap()
109        );
110
111        println!("{}", cell.display_tree());
112
113        Ok(())
114    }
115
116    #[test]
117    fn stack_ops() -> anyhow::Result<()> {
118        let cell = Code::assemble(
119            r##"
120            XCHG s1, s2
121            NOP
122            SWAP
123            XCHG3 s1, s2, s3
124            "##,
125        )?;
126
127        assert_eq!(
128            cell.repr_hash(),
129            &"5f099122adde2ed3712374da4cd4e04e3214f0ddd7f155ffea923f1f2ab42d2b"
130                .parse::<HashBytes>()
131                .unwrap()
132        );
133
134        println!("{}", cell.display_tree());
135
136        Ok(())
137    }
138
139    #[test]
140    fn pushint() -> anyhow::Result<()> {
141        let cell_tiny = Code::assemble("INT 7")?;
142        assert_eq!(cell_tiny.data(), &[0x77]);
143
144        let cell_byte = Code::assemble("INT 120")?;
145        assert_eq!(cell_byte.data(), &[0x80, 120]);
146
147        let cell_short = Code::assemble("INT 16000")?;
148        assert_eq!(cell_short.data(), &[
149            0x81,
150            ((16000 >> 8) & 0xff) as u8,
151            ((16000) & 0xff) as u8
152        ]);
153
154        let cell_big = Code::assemble("INT 123123123123123123")?;
155        assert_eq!(cell_big.data(), hex::decode("8229b56bd40163f3b3")?);
156
157        let cell_max = Code::assemble(
158            "INT 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
159        )?;
160        assert_eq!(
161            cell_max.data(),
162            hex::decode("82f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")?
163        );
164
165        let cell_neg_max = Code::assemble(
166            "INT -0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
167        )?;
168        assert_eq!(
169            cell_neg_max.data(),
170            hex::decode("82f70000000000000000000000000000000000000000000000000000000000000001")?
171        );
172
173        Ok(())
174    }
175
176    #[test]
177    fn pushintx() -> anyhow::Result<()> {
178        let cell_tiny = Code::assemble("INTX 7")?;
179        assert_eq!(cell_tiny.data(), &[0x77]);
180
181        let cell_byte = Code::assemble("INTX 120")?;
182        assert_eq!(cell_byte.data(), &[0x80, 120]);
183
184        let cell_short = Code::assemble("INTX 16000")?;
185        assert_eq!(cell_short.data(), &[
186            0x81,
187            ((16000 >> 8) & 0xff) as u8,
188            ((16000) & 0xff) as u8
189        ]);
190
191        let cell_big = Code::assemble("INTX 123123123123123123")?;
192        assert_eq!(cell_big.data(), hex::decode("8229b56bd40163f3b3")?);
193
194        let cell_big = Code::assemble("INTX 90596966400")?;
195        assert_eq!(cell_big.data(), hex::decode("8102a3aa1a")?);
196
197        let cell_max = Code::assemble(
198            "INTX 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
199        )?;
200        assert_eq!(cell_max.data(), hex::decode("84ff")?);
201
202        let cell_neg_max = Code::assemble(
203            "INTX -0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
204        )?;
205        assert_eq!(
206            cell_neg_max.data(),
207            hex::decode("82f70000000000000000000000000000000000000000000000000000000000000001")?
208        );
209
210        Ok(())
211    }
212
213    #[test]
214    fn display() -> anyhow::Result<()> {
215        let code = Code::assemble("PUSHSLICE x{6_}")?;
216        println!("{}", code.display_tree());
217        Ok(())
218    }
219
220    #[test]
221    fn complex_asm() -> anyhow::Result<()> {
222        const CODE: &str = include_str!("tests/walletv3.tvm");
223
224        let output = Code::assemble(CODE).unwrap();
225        assert_eq!(
226            output.repr_hash(),
227            &"84dafa449f98a6987789ba232358072bc0f76dc4524002a5d0918b9a75d2d599"
228                .parse::<HashBytes>()?
229        );
230        Ok(())
231    }
232
233    #[test]
234    fn contops() -> anyhow::Result<()> {
235        let code = Code::assemble(
236            r##"
237            CALLXARGS 0, 2
238            CALLXARGS 15, 14
239            CALLXARGS 1, -1
240
241            CALLCCARGS 0, 2
242            CALLCCARGS 15, 14
243            CALLCCARGS 1, -1
244
245            SETCONTARGS 0, 2
246            SETCONTARGS 15, 14
247            SETCONTARGS 1, -1
248
249            BLESSARGS 0, 2
250            BLESSARGS 15, 14
251            BLESSARGS 1, -1
252            "##,
253        )?;
254        assert_eq!(
255            code.repr_hash(),
256            &"edb0041119c9381c6426a99abf45236c8192383e14c368775e77aa13e0c5fa79"
257                .parse::<HashBytes>()?
258        );
259
260        let code1 = Code::assemble(
261            r##"
262            SETCONTARGS 0, 2
263            BLESSARGS 0, 3
264            "##,
265        )?;
266        let code2 = Code::assemble(
267            r##"
268            SETNUMARGS 2
269            BLESSNUMARGS 3
270            "##,
271        )?;
272        assert_eq!(code1, code2);
273        Ok(())
274    }
275
276    #[test]
277    fn stsliceconst() -> anyhow::Result<()> {
278        let code = Code::assemble("STSLICECONST x{cf_}")?;
279        assert_eq!(code.as_slice_allow_exotic().load_uint(24)?, 0xcf873c);
280        Ok(())
281    }
282
283    #[test]
284    fn runvm() -> anyhow::Result<()> {
285        let code = Code::assemble("RUNVM 128")?;
286        assert_eq!(code.as_slice_allow_exotic().load_uint(24)?, 0xdb4000 | 128);
287        Ok(())
288    }
289
290    #[test]
291    fn raw_cell() -> anyhow::Result<()> {
292        let child_code = "te6ccgEBBAEAHgABFP8A9KQT9LzyyAsBAgLOAwIABaNUQAAJ0IPAWpI=";
293        let child_cell = Boc::decode_base64(child_code)?;
294
295        let code = Code::assemble(&format!("PUSHREF {child_code}"))?;
296        assert_eq!(code.reference(0).unwrap(), child_cell.as_ref());
297        Ok(())
298    }
299
300    #[test]
301    fn new_cell() -> anyhow::Result<()> {
302        let code = Code::assemble("NOP @newcell NOP")?;
303        let child_cell = Code::assemble("NOP")?;
304        assert_eq!(code.reference(0).unwrap(), child_cell.as_ref());
305        Ok(())
306    }
307
308    #[test]
309    fn dictpushconst() -> anyhow::Result<()> {
310        let code = Code::assemble(
311            r#"
312            SETCP 0
313            DICTPUSHCONST 19, [
314                0 => {
315                    DUP
316                    CALLDICT 22
317                    INC
318                }
319                22 => {
320                    MUL
321                }
322            ]
323            DICTIGETJMPZ
324            THROWARG 11
325            "#,
326        )?;
327
328        let expected =
329            Boc::decode_base64("te6ccgEBBAEAHgABFP8A9KQT9LzyyAsBAgLOAwIABaNUQAAJ0IPAWpI=")?;
330        assert_eq!(code, expected);
331        Ok(())
332    }
333
334    #[test]
335    fn only_comments() {
336        let code = Code::assemble(
337            r#"
338              // TEST
339              // ASD
340              "#,
341        )
342        .unwrap();
343
344        assert_eq!(code, Cell::empty_cell());
345    }
346
347    #[test]
348    fn invalid_input_numbers() {
349        Code::assemble(
350            r#"
351              ccf21d3ec00b
352
353
354              CHKNAN_BOUNCE   CHKBIT         0                                ///////////////////////OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO//                                              0                                                                              //////////////////// //////////                    OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO//                                              0                                                                              //////////////////// //////////                                               /////////////                                                                                         CAL                           /////////////                                                                                         CALDILC    T        / LDILE7
355              "#,
356        ).unwrap_err();
357    }
358
359    #[test]
360    fn invalid_input_large_ints() {
361        Code::assemble(
362            r#"
363              INT 0x000000000000000000000000000000000000000000-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000INT  454  INC   //./0;}0,{07t @{BBBBBBB/./0        4  /  //./0;}%0,{0 % x{BBBB,{0 % x/./0;}%0,{0 % x{BBB IBBBB/../0;}%0,{0 % x{BBBB`B  INC   /  //./`B  INC   /  //./0;}%0,{0 % 8{BBB IBBBB/../0;}%0,{0 % x{BBBB`B  INC   /  //./0;}%0,{0 /0~}0,0t{ ;}0,   BBBB//{BBBB`B  INC   /  //./0;}%0,{0 % 1{BBB IBBBB/../0;}%0,{0 % x{BBBB`B  INC   /  //./`B  INC   /  //./0;}%0,{0 % 8{BBB IBBBB/../0;}%0,{0 % x{BBBB`B  INC   /  //./0;}%0,{0 /0~}0,0t{ ;}0,   BBBB///  N    ////     X0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
364              "#,
365        ).unwrap_err();
366    }
367
368    #[test]
369    fn invalid_input_lib_refs() {
370        Code::assemble(
371            r#"
372              IN                      PQOc         ,00/ T;}{0t   0x+ INT /f2f +       O PQOc         ,00/ T;}{0t   0x+ INT /f2f +               O PQOc     0t$@{ INC  s  //0PO0;}IN0T,{0t$@{ INC  s  //0POO PQOc         ,00/ T;}{0t   0x+ INT /f2f +      IN                      IINT  445  INC   //.0x+  /f2f     /./0;}IN0T,{0t$@{ INC  so/.,00/ T;}{0t   0x+m /f2f +        }0,{0t   0x+  /f2f     /./0;}IN0T,{0t$@{ INC  s  //0PO0;}IN0T,{0t$@{ INC  s  //0;}IN0T,{0t$@{ INC  so/./TNT 001844674407370951010712 MUL  0;  INC   //./0;}IN0T,{0t$@{ INC  so/./T 0;}0,{0t   0x+  /f2f     /./0;}IN0T,{0t$@{ INC  so/.,00/ T;}{0t   0x+m /f2f +        }0,{0t   0x+  /f2f     /./0;}IN0T,{0t$@{ INC  s  //0PO0;}IN0T,{0t$@{ INC  s  //0POO PQOc         ,00/ T;}{0t   0x+ INT /f2f +       O PQOc         ,00/ T;}{0t   0x+ INT /f2f +               O PQOc         ,00/ T;}{0t   0x+ INT /f2f +              x0
373              "#,
374        ).unwrap_err();
375    }
376}