pub mod op {
pub const REVERT: u8 = 0xFD;
pub const RETURN: u8 = 0xF3;
pub const CODECOPY: u8 = 0x39;
pub const MSTORE: u8 = 0x52;
pub const SLOAD: u8 = 0x54;
pub const SSTORE: u8 = 0x55;
pub const CALLDATASIZE: u8 = 0x36;
pub const CALLDATALOAD: u8 = 0x35;
pub const CALLDATACOPY: u8 = 0x37;
pub const LT: u8 = 0x10;
pub const GT: u8 = 0x11;
pub const EQ: u8 = 0x14;
pub const ISZERO: u8 = 0x15;
pub const SHR: u8 = 0x1C;
pub const AND: u8 = 0x16;
pub const ADD: u8 = 0x01;
pub const SUB: u8 = 0x03;
pub const MUL: u8 = 0x02;
pub const DIV: u8 = 0x04;
pub const MOD: u8 = 0x06;
pub const KECCAK256: u8 = 0x20;
pub const CALLER: u8 = 0x33;
pub const TIMESTAMP: u8 = 0x42;
pub const NUMBER: u8 = 0x43;
pub const DUP1: u8 = 0x80;
pub const DUP2: u8 = 0x81;
pub const DUP3: u8 = 0x82;
pub const SWAP1: u8 = 0x90;
pub const POP: u8 = 0x50;
pub const JUMP: u8 = 0x56;
pub const JUMPI: u8 = 0x57;
pub const JUMPDEST: u8 = 0x5B;
pub const LOG0: u8 = 0xA0;
pub const LOG1: u8 = 0xA1;
pub const LOG2: u8 = 0xA2;
pub const LOG3: u8 = 0xA3;
pub const LOG4: u8 = 0xA4;
pub const PUSH1: u8 = 0x60;
pub const PUSH2: u8 = 0x61;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Label(usize);
#[derive(Debug, Default)]
pub struct Asm {
code: Vec<u8>,
dests: Vec<Option<usize>>,
refs: Vec<(usize, Label)>,
}
impl Asm {
pub fn new() -> Self {
Self::default()
}
pub fn here(&self) -> usize {
self.code.len()
}
pub fn emit(&mut self, opcode: u8) -> &mut Self {
self.code.push(opcode);
self
}
pub fn emit_all(&mut self, opcodes: &[u8]) -> &mut Self {
self.code.extend_from_slice(opcodes);
self
}
pub fn push(&mut self, bytes: &[u8]) -> &mut Self {
let first = bytes.iter().position(|&b| b != 0);
let sig: &[u8] = match first {
None => &[0u8], Some(i) => &bytes[i..],
};
debug_assert!(sig.len() <= 32, "EVM PUSH operand exceeds 32 bytes");
let sig = if sig.len() > 32 { &sig[sig.len() - 32..] } else { sig };
let n = sig.len() as u8; self.code.push(op::PUSH1 + (n - 1));
self.code.extend_from_slice(sig);
self
}
pub fn push_u64(&mut self, value: u64) -> &mut Self {
self.push(&value.to_be_bytes())
}
pub fn push32(&mut self, word: &[u8; 32]) -> &mut Self {
self.code.push(op::PUSH1 + 31); self.code.extend_from_slice(word);
self
}
pub fn new_label(&mut self) -> Label {
let id = self.dests.len();
self.dests.push(None);
Label(id)
}
pub fn jumpdest(&mut self, label: Label) -> &mut Self {
debug_assert!(self.dests[label.0].is_none(), "label placed twice");
self.dests[label.0] = Some(self.code.len());
self.code.push(op::JUMPDEST);
self
}
pub fn push_label(&mut self, label: Label) -> &mut Self {
self.code.push(op::PUSH2);
self.refs.push((self.code.len(), label));
self.code.push(0x00);
self.code.push(0x00);
self
}
pub fn finish(mut self) -> Vec<u8> {
for (operand_off, label) in &self.refs {
let dest = self.dests[label.0]
.expect("referenced label was never placed (missing jumpdest)");
let dest =
u16::try_from(dest).expect("jump target exceeds 64KB (u16) — program too large");
let be = dest.to_be_bytes();
self.code[*operand_off] = be[0];
self.code[*operand_off + 1] = be[1];
}
self.code
}
}
pub fn init_wrapper(runtime: &[u8]) -> Vec<u8> {
let rt_len =
u16::try_from(runtime.len()).expect("runtime exceeds 64KB (u16) — far past EIP-170");
const PRELUDE_LEN: u16 = 13;
let len_be = rt_len.to_be_bytes();
let off_be = PRELUDE_LEN.to_be_bytes();
let mut out = Vec::with_capacity(PRELUDE_LEN as usize + runtime.len());
out.extend_from_slice(&[op::PUSH2, len_be[0], len_be[1]]);
out.push(op::DUP1);
out.extend_from_slice(&[op::PUSH2, off_be[0], off_be[1]]);
out.extend_from_slice(&[op::PUSH1, 0x00]);
out.push(op::CODECOPY);
out.extend_from_slice(&[op::PUSH1, 0x00]);
out.push(op::RETURN);
debug_assert_eq!(out.len(), PRELUDE_LEN as usize);
out.extend_from_slice(runtime);
out
}
#[cfg(test)]
mod tests {
use super::op;
use super::{init_wrapper, Asm};
#[test]
fn push_then_add_emits_exact_bytes() {
let mut asm = Asm::new();
asm.push(&[1]).push(&[2]).emit(op::ADD);
assert_eq!(asm.finish(), vec![0x60, 0x01, 0x60, 0x02, 0x01]);
}
#[test]
fn push_size_boundaries() {
let mut a = Asm::new();
a.push(&[0]);
assert_eq!(a.finish(), vec![op::PUSH1, 0x00]);
let mut a = Asm::new();
a.push(&[]);
assert_eq!(a.finish(), vec![op::PUSH1, 0x00]);
let mut a = Asm::new();
a.push(&[0, 0, 0, 0]);
assert_eq!(a.finish(), vec![op::PUSH1, 0x00]);
let mut a = Asm::new();
a.push(&[0xff]);
assert_eq!(a.finish(), vec![op::PUSH1, 0xff]);
let mut a = Asm::new();
a.push(&0x0100u16.to_be_bytes());
assert_eq!(a.finish(), vec![op::PUSH2, 0x01, 0x00]);
let mut a = Asm::new();
a.push(&0x0000_00ffu32.to_be_bytes());
assert_eq!(a.finish(), vec![op::PUSH1, 0xff]);
let mut val = [0u8; 32];
val[0] = 0x12;
val[31] = 0x34;
let mut a = Asm::new();
a.push(&val);
let out = a.finish();
assert_eq!(out[0], op::PUSH1 + 31); assert_eq!(out.len(), 33);
assert_eq!(&out[1..], &val);
}
#[test]
fn push32_never_strips_and_always_emits_full_width() {
let mut word = [0u8; 32];
word[31] = 0x2a; let mut a = Asm::new();
a.push32(&word);
let out = a.finish();
assert_eq!(out[0], op::PUSH1 + 31); assert_eq!(out.len(), 33);
assert_eq!(&out[1..], &word);
}
#[test]
fn forward_jump_resolves_to_correct_push2_operand_and_jumpdest() {
let mut a = Asm::new();
let l = a.new_label();
a.push_label(l).emit(op::JUMPI).emit(op::POP);
a.jumpdest(l);
let out = a.finish();
assert_eq!(
out,
vec![op::PUSH2, 0x00, 0x05, op::JUMPI, op::POP, op::JUMPDEST]
);
assert_eq!(out[5], op::JUMPDEST);
let operand = u16::from_be_bytes([out[1], out[2]]) as usize;
assert_eq!(operand, 5);
assert_eq!(out[operand], op::JUMPDEST);
}
#[test]
fn back_jump_resolves_to_an_earlier_jumpdest() {
let mut a = Asm::new();
let l = a.new_label();
a.jumpdest(l); a.push_label(l).emit(op::JUMP);
let out = a.finish();
assert_eq!(out, vec![op::JUMPDEST, op::PUSH2, 0x00, 0x00, op::JUMP]);
let operand = u16::from_be_bytes([out[2], out[3]]) as usize;
assert_eq!(operand, 0);
}
#[test]
fn multiple_refs_to_one_label_all_patch() {
let mut a = Asm::new();
let l = a.new_label();
a.push_label(l).emit(op::POP); a.push_label(l).emit(op::POP); a.jumpdest(l); let out = a.finish();
assert_eq!(out.len(), 9);
assert_eq!(u16::from_be_bytes([out[1], out[2]]), 8);
assert_eq!(u16::from_be_bytes([out[5], out[6]]), 8);
assert_eq!(out[8], op::JUMPDEST);
}
#[test]
fn init_wrapper_has_correct_codecopy_return_prelude() {
let runtime = [0xAA, 0xBB, 0xCC];
let init = init_wrapper(&runtime);
let expected_prelude = [
op::PUSH2, 0x00, 0x03, op::DUP1, op::PUSH2, 0x00, 0x0D, op::PUSH1, 0x00, op::CODECOPY, op::PUSH1, 0x00, op::RETURN, ];
assert_eq!(&init[..13], &expected_prelude);
assert_eq!(&init[13..], &runtime);
assert_eq!(init.len(), 13 + 3);
}
#[test]
fn init_wrapper_encodes_a_larger_runtime_length() {
let runtime = vec![0x5Bu8; 300];
let init = init_wrapper(&runtime);
assert_eq!(&init[..3], &[op::PUSH2, 0x01, 0x2C]); assert_eq!(&init[4..7], &[op::PUSH2, 0x00, 0x0D]); assert_eq!(init.len(), 13 + 300);
}
}