use tycho_types::prelude::*;
pub use crate::asm::{ArgType, AsmError, ExpectedArgType};
pub use crate::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(())
}
#[test]
fn contops() -> anyhow::Result<()> {
let code = Code::assemble(
r##"
CALLXARGS 0, 2
CALLXARGS 15, 14
CALLXARGS 1, -1
CALLCCARGS 0, 2
CALLCCARGS 15, 14
CALLCCARGS 1, -1
SETCONTARGS 0, 2
SETCONTARGS 15, 14
SETCONTARGS 1, -1
BLESSARGS 0, 2
BLESSARGS 15, 14
BLESSARGS 1, -1
"##,
)?;
assert_eq!(
code.repr_hash(),
&"edb0041119c9381c6426a99abf45236c8192383e14c368775e77aa13e0c5fa79"
.parse::<HashBytes>()?
);
let code1 = Code::assemble(
r##"
SETCONTARGS 0, 2
BLESSARGS 0, 3
"##,
)?;
let code2 = Code::assemble(
r##"
SETNUMARGS 2
BLESSNUMARGS 3
"##,
)?;
assert_eq!(code1, code2);
Ok(())
}
#[test]
fn stsliceconst() -> anyhow::Result<()> {
let code = Code::assemble("STSLICECONST x{cf_}")?;
assert_eq!(code.as_slice_allow_exotic().load_uint(24)?, 0xcf873c);
Ok(())
}
#[test]
fn runvm() -> anyhow::Result<()> {
let code = Code::assemble("RUNVM 128")?;
assert_eq!(code.as_slice_allow_exotic().load_uint(24)?, 0xdb4000 | 128);
Ok(())
}
#[test]
fn raw_cell() -> anyhow::Result<()> {
let child_code = "te6ccgEBBAEAHgABFP8A9KQT9LzyyAsBAgLOAwIABaNUQAAJ0IPAWpI=";
let child_cell = Boc::decode_base64(child_code)?;
let code = Code::assemble(&format!("PUSHREF {child_code}"))?;
assert_eq!(code.reference(0).unwrap(), child_cell.as_ref());
Ok(())
}
#[test]
fn new_cell() -> anyhow::Result<()> {
let code = Code::assemble("NOP @newcell NOP")?;
let child_cell = Code::assemble("NOP")?;
assert_eq!(code.reference(0).unwrap(), child_cell.as_ref());
Ok(())
}
#[test]
fn dictpushconst() -> anyhow::Result<()> {
let code = Code::assemble(
r#"
SETCP 0
DICTPUSHCONST 19, [
0 => {
DUP
CALLDICT 22
INC
}
22 => {
MUL
}
]
DICTIGETJMPZ
THROWARG 11
"#,
)?;
let expected =
Boc::decode_base64("te6ccgEBBAEAHgABFP8A9KQT9LzyyAsBAgLOAwIABaNUQAAJ0IPAWpI=")?;
assert_eq!(code, expected);
Ok(())
}
#[test]
fn only_comments() {
let code = Code::assemble(
r#"
// TEST
// ASD
"#,
)
.unwrap();
assert_eq!(code, Cell::empty_cell());
}
#[test]
fn invalid_input_numbers() {
Code::assemble(
r#"
ccf21d3ec00b
CHKNAN_BOUNCE CHKBIT 0 ///////////////////////OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO// 0 //////////////////// ////////// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO// 0 //////////////////// ////////// ///////////// CAL ///////////// CALDILC T / LDILE7
"#,
).unwrap_err();
}
#[test]
fn invalid_input_large_ints() {
Code::assemble(
r#"
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
"#,
).unwrap_err();
}
#[test]
fn invalid_input_lib_refs() {
Code::assemble(
r#"
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
"#,
).unwrap_err();
}
}