use crate::types::{Blake3Hash, OperationEvent, Receipt};
use wasm_encoder::{
CodeSection, ConstExpr, DataSection, ExportSection, Function, FunctionSection, Instruction,
MemorySection, MemoryType, Module, TypeSection, ValType,
};
pub struct ReceiptWasmCompiler {
receipt: Receipt,
}
impl ReceiptWasmCompiler {
pub fn new(receipt: Receipt) -> Self {
Self { receipt }
}
pub fn compile(&self) -> Vec<u8> {
let mut module = Module::new();
let mut types = TypeSection::new();
types.ty().function(vec![], vec![ValType::I32]); types.ty().function(vec![], vec![ValType::I32]); types.ty().function(vec![ValType::I32], vec![]); module.section(&types);
let mut functions = FunctionSection::new();
functions.function(0); functions.function(1); functions.function(2); module.section(&functions);
let mut memories = MemorySection::new();
memories.memory(MemoryType {
minimum: 20, maximum: Some(20),
shared: false,
memory64: false,
page_size_log2: None,
});
module.section(&memories);
let mut exports = ExportSection::new();
exports.export("verify", wasm_encoder::ExportKind::Func, 0);
exports.export("get_event_count", wasm_encoder::ExportKind::Func, 1);
exports.export("get_chain_hash", wasm_encoder::ExportKind::Func, 2);
exports.export("memory", wasm_encoder::ExportKind::Memory, 0);
module.section(&exports);
let mut data = DataSection::new();
let encoded_receipt = self.encode_receipt_binary();
let mut offset = ConstExpr::new();
offset.instruction(&Instruction::I32Const(0));
data.active(0, &offset, encoded_receipt);
module.section(&data);
let mut code = CodeSection::new();
let mut verify_func = Function::new(vec![]); self.emit_verify_logic(&mut verify_func);
code.function(&verify_func);
let mut count_func = Function::new(vec![]);
count_func.instruction(&Instruction::I32Const(self.receipt.events.len() as i32));
count_func.instruction(&Instruction::End);
code.function(&count_func);
let mut hash_func = Function::new(vec![]);
self.emit_get_hash_logic(&mut hash_func);
code.function(&hash_func);
module.section(&code);
module.finish()
}
fn encode_receipt_binary(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&self.hash_to_bytes(self.receipt.chain_hash.as_hex()));
buf.extend_from_slice(&(self.receipt.events.len() as u32).to_le_bytes());
let fmt_hash = blake3::hash(self.receipt.format_version.as_bytes());
buf.extend_from_slice(&fmt_hash.as_bytes()[0..4]);
for ev in &self.receipt.events {
buf.extend_from_slice(&ev.seq.to_le_bytes());
buf.extend_from_slice(
&self.hash_to_bytes(blake3::hash(ev.event_type.as_bytes()).to_hex().as_str()),
);
buf.extend_from_slice(&self.hash_to_bytes(ev.payload_commitment.as_hex()));
buf.extend_from_slice(
&self.hash_to_bytes(blake3::hash(ev.id.as_bytes()).to_hex().as_str()),
);
}
buf
}
fn hash_to_bytes(&self, hex: &str) -> [u8; 32] {
let mut bytes = [0u8; 32];
for i in 0..32 {
bytes[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).unwrap_or(0);
}
bytes
}
fn emit_verify_logic(&self, f: &mut Function) {
f.local(3, ValType::I32);
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(0));
f.instruction(&Instruction::I32Const(40));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::I32Const(self.receipt.events.len() as i32));
f.instruction(&Instruction::I32LtS);
f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I64Load(wasm_encoder::MemArg {
offset: 0,
align: 3,
memory_index: 0,
}));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::I64ExtendI32S);
f.instruction(&Instruction::I64Eq);
f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::Else);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I32Const(104)); f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Br(1)); f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::End);
}
fn emit_get_hash_logic(&self, f: &mut Function) {
f.local_declaration(1, ValType::I32);
f.instruction(&Instruction::I32Const(0));
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I32Const(32));
f.instruction(&Instruction::I32LtS);
f.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I32Load8U(wasm_encoder::MemArg {
offset: 0,
align: 0,
memory_index: 0,
}));
f.instruction(&Instruction::I32Store8(wasm_encoder::MemArg {
offset: 0,
align: 0,
memory_index: 0,
}));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I32Const(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::Br(1));
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
f.instruction(&Instruction::End);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Blake3Hash, OperationEvent, Receipt};
#[test]
fn test_compile_empty_receipt() {
let receipt = Receipt::sealed(
"core/v1".to_string(),
vec![],
Blake3Hash::from_hex(
"0000000000000000000000000000000000000000000000000000000000000000",
),
);
let compiler = ReceiptWasmCompiler::new(receipt);
let wasm = compiler.compile();
assert!(!wasm.is_empty());
assert_eq!(&wasm[0..4], b"\0asm");
println!("Compiled WASM size: {} bytes", wasm.len());
}
#[test]
fn test_compile_10k_events() {
let mut events = Vec::new();
for i in 0..10000 {
events.push(OperationEvent {
id: format!("ev_{}", i),
seq: i as u64,
event_type: "test".to_string(),
objects: vec![],
payload_commitment: Blake3Hash::from_hex(
"1111111111111111111111111111111111111111111111111111111111111111",
),
});
}
let receipt = Receipt::sealed(
"core/v1".to_string(),
events,
Blake3Hash::from_hex(
"2222222222222222222222222222222222222222222222222222222222222222",
),
);
let compiler = ReceiptWasmCompiler::new(receipt);
let wasm = compiler.compile();
println!("10,000 event WASM size: {} bytes", wasm.len());
assert!(wasm.len() > 1_000_000);
}
}