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}