pub mod asm;
pub mod ast;
pub mod codegen;
pub mod lexer;
pub mod parser;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompiledArtifact {
pub init_code: Vec<u8>,
pub runtime: Vec<u8>,
pub selectors: Vec<[u8; 4]>,
}
pub fn emit_constant_getter(selector: [u8; 4], value_be32: [u8; 32]) -> CompiledArtifact {
codegen::assemble(&[(selector, codegen::BodyValue::Const(value_be32))])
}
#[cfg(feature = "wallet")]
pub fn compile(source: &str) -> Result<CompiledArtifact, CompileError> {
let tokens = lexer::lex(source)?;
let facet = parser::parse(&tokens)?;
codegen::compile(&facet)
}
#[cfg(feature = "wallet")]
use crate::rustlite::CompileError;
#[cfg(test)]
mod tests {
use super::asm::op;
use super::{emit_constant_getter, CompiledArtifact};
const GET_SELECTOR: [u8; 4] = [0x6d, 0x4c, 0xe6, 0x3c];
fn word_42() -> [u8; 32] {
let mut w = [0u8; 32];
w[31] = 42;
w
}
#[test]
fn dispatcher_prelude_matches_design_section_5() {
let CompiledArtifact { runtime, .. } = emit_constant_getter(GET_SELECTOR, word_42());
assert_eq!(&runtime[0..2], &[op::PUSH1, 0x04]);
assert_eq!(runtime[2], op::CALLDATASIZE);
assert_eq!(runtime[3], op::LT);
assert_eq!(runtime[4], op::PUSH2);
assert_eq!(runtime[7], op::JUMPI);
assert_eq!(&runtime[8..10], &[op::PUSH1, 0x00]);
assert_eq!(runtime[10], op::CALLDATALOAD);
assert_eq!(&runtime[11..13], &[op::PUSH1, 0xE0]);
assert_eq!(runtime[13], op::SHR);
assert_eq!(runtime[14], op::DUP1);
assert_eq!(runtime[15], op::PUSH1 + 3); assert_eq!(&runtime[16..20], &GET_SELECTOR);
assert_eq!(runtime[20], op::EQ);
assert_eq!(runtime[21], op::PUSH2);
assert_eq!(runtime[24], op::JUMPI);
let fb = u16::from_be_bytes([runtime[5], runtime[6]]) as usize;
let body = u16::from_be_bytes([runtime[22], runtime[23]]) as usize;
assert_eq!(runtime[fb], op::JUMPDEST, "FB must land on a JUMPDEST");
assert_eq!(runtime[body], op::JUMPDEST, "BODY must land on a JUMPDEST");
assert!(fb < body);
}
#[test]
fn fallback_stub_is_revert_0_0() {
let CompiledArtifact { runtime, .. } = emit_constant_getter(GET_SELECTOR, word_42());
let fb = u16::from_be_bytes([runtime[5], runtime[6]]) as usize;
assert_eq!(
&runtime[fb..fb + 6],
&[op::JUMPDEST, op::PUSH1, 0x00, op::PUSH1, 0x00, op::REVERT]
);
}
#[test]
fn body_returns_the_32_byte_value() {
let value = word_42();
let CompiledArtifact { runtime, .. } = emit_constant_getter(GET_SELECTOR, value);
let body = u16::from_be_bytes([runtime[22], runtime[23]]) as usize;
assert_eq!(runtime[body], op::JUMPDEST);
assert_eq!(runtime[body + 1], op::PUSH1 + 31); assert_eq!(&runtime[body + 2..body + 34], &value);
assert_eq!(
&runtime[body + 34..body + 42],
&[op::PUSH1, 0x00, op::MSTORE, op::PUSH1, 0x20, op::PUSH1, 0x00, op::RETURN]
);
assert_eq!(body + 42, runtime.len());
}
#[test]
fn init_code_wraps_the_runtime() {
let artifact = emit_constant_getter(GET_SELECTOR, word_42());
assert_eq!(
artifact.init_code,
super::asm::init_wrapper(&artifact.runtime)
);
assert_eq!(artifact.init_code.len(), 13 + artifact.runtime.len());
let rt_len = artifact.runtime.len() as u16;
assert_eq!(
&artifact.init_code[0..3],
&[op::PUSH2, rt_len.to_be_bytes()[0], rt_len.to_be_bytes()[1]]
);
assert_eq!(&artifact.init_code[13..], &artifact.runtime[..]);
}
#[cfg(feature = "wallet")]
#[test]
fn get_selector_matches_keccak() {
assert_eq!(crate::registry::selector("get()"), GET_SELECTOR);
}
#[test]
fn init_code_golden_hex_for_get_42() {
let artifact = emit_constant_getter(GET_SELECTOR, word_42());
assert_eq!(artifact.runtime.len(), 73, "runtime length pin");
assert_eq!(artifact.init_code.len(), 13 + 73);
let hex = to_hex(&artifact.init_code);
let expected = "0x\
6100498061000d6000396000f3\
6004361061001957\
60003560e01c\
80636d4ce63c1461001f57\
5b60006000fd\
5b7f000000000000000000000000000000000000000000000000000000000000002a6000526020600 0f3"
.replace([' ', '\n'], ""); assert_eq!(hex, expected, "init_code drifted: {hex}");
}
#[cfg(feature = "wallet")]
#[test]
fn compile_get_42_equals_emit_constant_getter() {
let from_source =
super::compile("facet C { function get() external view returns (uint256) { return 42; } }")
.expect("floor-grammar source must compile");
let from_emitter = emit_constant_getter([0x6d, 0x4c, 0xe6, 0x3c], word_42());
assert_eq!(
from_source.init_code, from_emitter.init_code,
"source-compiled init_code must be byte-identical to the hand-built emitter"
);
assert_eq!(from_source.runtime, from_emitter.runtime);
}
#[cfg(feature = "wallet")]
#[test]
fn compile_two_function_facet() {
let art = super::compile(
"facet Two { function a() external view returns (uint256) { return 1; } \
function b() external view returns (uint256) { return 2; } }",
)
.expect("a 2-function facet must compile");
let rt = &art.runtime;
let sel_a = crate::registry::selector("a()");
let sel_b = crate::registry::selector("b()");
let push4_a: Vec<u8> = std::iter::once(op::PUSH1 + 3).chain(sel_a).collect();
let push4_b: Vec<u8> = std::iter::once(op::PUSH1 + 3).chain(sel_b).collect();
assert!(
rt.windows(push4_a.len()).any(|w| w == push4_a.as_slice()),
"a() selector PUSH4 must be present"
);
assert!(
rt.windows(push4_b.len()).any(|w| w == push4_b.as_slice()),
"b() selector PUSH4 must be present"
);
let mut one = [0u8; 32];
one[31] = 1;
let mut two = [0u8; 32];
two[31] = 2;
assert!(rt.windows(32).any(|w| w == one), "constant 1 must be embedded");
assert!(rt.windows(32).any(|w| w == two), "constant 2 must be embedded");
assert_eq!(art.init_code, super::asm::init_wrapper(rt));
}
#[cfg(feature = "wallet")]
#[test]
fn compile_if_else_and_neq() {
let art = super::compile(
"facet Gate { uint256 v; \
function set(uint256 x) external { \
if (x == 0) { v = 1; } \
else if (x != 5) { require(x < 100, \"hi\"); v = x; } \
else { v = 5; } \
} \
function get() external view returns (uint256) { return v; } }",
)
.expect("if/else/else-if + ==/!= + nested require must compile");
assert_eq!(art.selectors.len(), 2, "set + get");
assert!(art.runtime.contains(&op::JUMP), "an if/else must JUMP over the else branch");
assert!(art.runtime.contains(&op::REVERT), "a require nested in a branch keeps the revert stub");
assert_eq!(art.init_code, super::asm::init_wrapper(&art.runtime));
}
#[cfg(feature = "wallet")]
#[test]
fn compile_string_constant_return() {
let art = super::compile(
"facet Meta { function name() external pure returns (string) { return \"claude\"; } }",
)
.expect("a constant string return must compile");
assert_eq!(art.selectors.len(), 1);
assert_eq!(art.selectors[0], crate::registry::selector("name()"));
let mut word = [0u8; 32];
word[..6].copy_from_slice(b"claude");
assert!(art.runtime.windows(32).any(|w| w == word), "the literal data word must be embedded");
}
#[cfg(feature = "wallet")]
#[test]
fn compile_subtraction() {
let art = super::compile("facet S { uint256 n; function dec() external { n = n - 1; } }")
.expect("subtraction must compile");
assert!(art.runtime.contains(&op::SUB), "`n - 1` must emit SUB");
}
#[cfg(feature = "wallet")]
#[test]
fn compile_multiplicative_tier() {
let art = super::compile(
"facet Math { \
function fee(uint256 amount, uint256 rate) external pure returns (uint256) { return amount * rate / 10000; } \
function slot(uint256 n) external pure returns (uint256) { return n % 3; } \
function poly(uint256 x) external pure returns (uint256) { return x + x * x; } }",
)
.expect("the multiplicative tier must compile");
assert_eq!(art.selectors.len(), 3);
assert!(art.runtime.contains(&op::MUL), "must emit MUL");
assert!(art.runtime.contains(&op::DIV), "must emit DIV");
assert!(art.runtime.contains(&op::MOD), "must emit MOD");
}
#[cfg(feature = "wallet")]
#[test]
fn compile_block_env_reads() {
let art = super::compile(
"facet Clock { \
function ts() external view returns (uint256) { return block.timestamp; } \
function bn() external view returns (uint256) { return block.number; } \
function live(uint256 deadline) external view returns (uint256) { return block.timestamp; } }",
)
.expect("block.timestamp / block.number must compile");
assert!(art.runtime.contains(&op::TIMESTAMP), "block.timestamp must emit TIMESTAMP");
assert!(art.runtime.contains(&op::NUMBER), "block.number must emit NUMBER");
assert!(
super::compile("facet B { function f() external view returns (uint256) { return block.coinbase; } }").is_err(),
"`block.coinbase` must be rejected"
);
}
#[cfg(feature = "wallet")]
#[test]
fn string_is_return_only() {
assert!(
super::compile("facet B { function f(string s) external view returns (uint256) { return 1; } }").is_err(),
"a `string` parameter must be rejected"
);
assert!(
super::compile("facet B { bytes32 v; function f() external { v = \"x\"; } }").is_err(),
"a string literal in an assignment must be rejected"
);
assert!(
super::compile("facet B { function f() external pure returns (string) { return 1; } }").is_err(),
"`returns (string)` with a non-literal body must be rejected"
);
assert!(
super::compile("facet B { function f() external pure returns (uint256) { return \"x\"; } }").is_err(),
"a string literal without `returns (string)` must be rejected"
);
}
#[cfg(feature = "wallet")]
#[test]
fn bad_source_is_a_clean_compile_error() {
let src = "facet C { function get() external view returns (uint256) { return 42 } }";
let err = super::compile(src).expect_err("missing semicolon must fail cleanly");
assert!(err.code.is_some(), "error must carry an LH code");
assert!(err.to_string().starts_with("LH0"), "surfaced: {err}");
assert!(err.render(src).contains("line "), "{}", err.render(src));
assert!(super::compile("facet C { }").is_err());
assert!(super::compile("facet C { @ }").is_err());
}
#[cfg(feature = "wallet")]
#[test]
fn compile_tally_facet_with_a_storage_write() {
let art = super::compile(
"facet Tally { uint256 n; \
function bump() external { n = n + 1; } \
function get() external view returns (uint256) { return n; } }",
)
.expect("the Tally facet (storage write) must compile");
let rt = &art.runtime;
let sel_bump = crate::registry::selector("bump()"); let sel_get = crate::registry::selector("get()");
assert_eq!(sel_bump, [0x68, 0x11, 0x0b, 0x2f], "bump() selector pin");
assert_eq!(sel_get, [0x6d, 0x4c, 0xe6, 0x3c], "get() selector pin");
for sel in [sel_bump, sel_get] {
let push4: Vec<u8> = std::iter::once(op::PUSH1 + 3).chain(sel).collect();
assert!(
rt.windows(push4.len()).any(|w| w == push4.as_slice()),
"selector PUSH4 {sel:02x?} must be dispatched"
);
}
assert!(rt.contains(&op::SSTORE), "Tally must SSTORE (the storage write)");
assert!(rt.contains(&op::SLOAD), "Tally must SLOAD (reads n)");
assert!(rt.contains(&op::ADD), "Tally must ADD (n + 1)");
assert_eq!(art.init_code, super::asm::init_wrapper(rt));
}
#[cfg(feature = "wallet")]
#[test]
fn compile_ledger_target_facet() {
let art = super::compile(
"facet Ledger { mapping(address => uint256) bal; \
function add(uint256 amt) external { bal[msg.sender] = bal[msg.sender] + amt; } \
function balanceOf(address who) external view returns (uint256) { return bal[who]; } }",
)
.expect("the Ledger TARGET facet must compile");
let rt = &art.runtime;
let sel_add = crate::registry::selector("add(uint256)");
let sel_balance_of = crate::registry::selector("balanceOf(address)");
for sel in [sel_add, sel_balance_of] {
let push4: Vec<u8> = std::iter::once(op::PUSH1 + 3).chain(sel).collect();
assert!(
rt.windows(push4.len()).any(|w| w == push4.as_slice()),
"selector PUSH4 {sel:02x?} must be dispatched"
);
}
assert!(rt.contains(&op::CALLDATALOAD), "params decode via CALLDATALOAD");
assert!(rt.contains(&op::CALLER), "msg.sender emits CALLER");
assert!(rt.contains(&op::KECCAK256), "mapping slot derivation uses KECCAK256");
assert!(rt.contains(&op::SSTORE), "add() writes via SSTORE");
assert!(rt.contains(&op::SLOAD), "balanceOf reads via SLOAD");
assert_eq!(art.init_code, super::asm::init_wrapper(rt));
}
#[cfg(feature = "wallet")]
#[test]
fn compile_counter_target_facet() {
const SRC: &str = "facet Counter { mapping(address => uint256) count; uint256 total; \
function increment() external { count[msg.sender] = count[msg.sender] + 1; total = total + 1; } \
function incrementBy(uint256 n) external { require(n > 0, \"zero\"); require(n <= 100, \"too big\"); \
count[msg.sender] = count[msg.sender] + n; total = total + n; } \
function countOf(address who) external view returns (uint256) { return count[who]; } \
function totalCount() external view returns (uint256) { return total; } }";
let art = super::compile(SRC).expect("the CounterFacet TARGET must compile");
let rt = &art.runtime;
for (sig, want) in [
("increment()", [0xd0u8, 0x9d, 0xe0, 0x8a]),
("incrementBy(uint256)", [0x03, 0xdf, 0x17, 0x9c]),
("countOf(address)", [0xf8, 0x97, 0x7e, 0x96]),
("totalCount()", [0x34, 0xea, 0xfb, 0x11]),
] {
assert_eq!(crate::registry::selector(sig), want, "selector pin for {sig}");
let push4: Vec<u8> = std::iter::once(op::PUSH1 + 3).chain(want).collect();
assert!(
rt.windows(push4.len()).any(|w| w == push4.as_slice()),
"{sig} selector must be dispatched"
);
}
assert!(rt.contains(&op::GT), "comparisons emit GT");
assert!(rt.windows(2).any(|w| w == [op::GT, op::ISZERO]), "`<=` → GT ISZERO");
assert!(
rt.windows(5).any(|w| w[0] == op::ISZERO && w[1] == op::PUSH2 && w[4] == op::JUMPI),
"require → ISZERO/JUMPI branch"
);
assert_eq!(art.init_code, super::asm::init_wrapper(rt));
}
#[cfg(feature = "wallet")]
#[test]
fn compile_full_counter_facet_with_event() {
const SRC: &str = "facet CounterFacet { mapping(address => uint256) count; uint256 total; \
event Incremented(address indexed who, uint256 newCount, uint256 newTotal); \
function increment() external { count[msg.sender] = count[msg.sender] + 1; total = total + 1; \
emit Incremented(msg.sender, count[msg.sender], total); } \
function incrementBy(uint256 n) external { require(n > 0, \"zero\"); require(n <= 100, \"too big\"); \
count[msg.sender] = count[msg.sender] + n; total = total + n; \
emit Incremented(msg.sender, count[msg.sender], total); } \
function countOf(address who) external view returns (uint256) { return count[who]; } \
function totalCount() external view returns (uint256) { return total; } }";
let art = super::compile(SRC).expect("the FULL CounterFacet (with events) must compile");
let rt = &art.runtime;
for (sig, want) in [
("increment()", [0xd0u8, 0x9d, 0xe0, 0x8a]),
("incrementBy(uint256)", [0x03, 0xdf, 0x17, 0x9c]),
("countOf(address)", [0xf8, 0x97, 0x7e, 0x96]),
("totalCount()", [0x34, 0xea, 0xfb, 0x11]),
] {
assert_eq!(crate::registry::selector(sig), want, "selector pin for {sig}");
let push4: Vec<u8> = std::iter::once(op::PUSH1 + 3).chain(want).collect();
assert!(
rt.windows(push4.len()).any(|w| w == push4.as_slice()),
"{sig} selector must be dispatched"
);
}
let topic0 = super::codegen::event_topic0("Incremented(address,uint256,uint256)");
let mut push32: Vec<u8> = vec![op::PUSH1 + 31];
push32.extend_from_slice(&topic0);
assert!(
rt.windows(33).any(|w| w == push32.as_slice()),
"the Incremented event topic0 must be PUSH32'd"
);
assert_eq!(art.init_code, super::asm::init_wrapper(rt));
}
#[cfg(feature = "wallet")]
#[test]
fn compile_require_true_constant_is_well_formed() {
let art = super::compile(
"facet C { function f() external { require(1 == 1, \"never\"); } }",
)
.expect("require(true) must compile");
assert_eq!(art.init_code, super::asm::init_wrapper(&art.runtime));
}
#[cfg(feature = "wallet")]
#[test]
fn breadth_does_not_trip_the_depth_guard() {
let mut src = String::from("facet Many {");
for i in 0..300 {
src.push_str(&format!(
" function f{i}() external view returns (uint256) {{ return {i}; }}"
));
}
src.push('}');
assert!(super::compile(&src).is_ok(), "breadth (many fns) must not trip the depth guard");
}
fn to_hex(bytes: &[u8]) -> String {
use core::fmt::Write;
let mut s = String::with_capacity(2 + bytes.len() * 2);
s.push_str("0x");
for b in bytes {
let _ = write!(s, "{b:02x}");
}
s
}
}