Skip to content

Instantly share code, notes, and snippets.

@roninjin10
Last active June 8, 2025 10:09
Show Gist options
  • Save roninjin10/d25788afa1a0b1c910026e1d7625aeb9 to your computer and use it in GitHub Desktop.
Save roninjin10/d25788afa1a0b1c910026e1d7625aeb9 to your computer and use it in GitHub Desktop.
// evm.ts
// —— Helpers ———————————————————————————————————————————————
type U256 = bigint;
// bytes → bigint via hex string
function fromBytes(bytes: Uint8Array): U256 {
const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');
return BigInt('0x' + hex);
}
// bigint → 32-byte Uint8Array via hex string
function toBytes(x: U256): Uint8Array {
const hex = x.toString(16).padStart(64, '0');
const pairs = hex.match(/.{2}/g)!;
return Uint8Array.from(pairs.map(h => parseInt(h, 16)));
}
// —— Opcode enum ———————————————————————————————————————————
enum Opcode {
STOP = 0x00,
ADD = 0x01,
MUL = 0x02,
SUB = 0x03,
DIV = 0x04,
POP = 0x50,
MLOAD = 0x51,
MSTORE = 0x52,
SLOAD = 0x54,
SSTORE = 0x55,
JUMP = 0x56,
JUMPI = 0x57,
PC = 0x58,
MSIZE = 0x59,
JUMPDEST = 0x5b,
PUSH1 = 0x60, // … up to PUSH32 = 0x7f
}
// —— Stack —————————————————————————————————————————————————
class Stack {
items: U256[] = [];
limit: number;
constructor(limit = 1024) { this.limit = limit; }
push(x: U256) {
if (this.items.length >= this.limit) throw new Error('StackOverflow');
this.items.push(x);
}
pop(): U256 {
const v = this.items.pop();
if (v === undefined) throw new Error('StackUnderflow');
return v;
}
}
// —— VM ———————————————————————————————————————————————————
class EVM {
pc = 0;
stack = new Stack();
// word-aligned memory & storage
memory = new Map<number, U256>();
storage = new Map<bigint, U256>();
stopped = false;
gasUsed = 0;
gasLimit: number;
constructor(
public bytecode: Uint8Array,
gasLimit = 1_000_000
) {
this.gasLimit = gasLimit;
}
private charge(amount = 1) {
if (this.gasUsed + amount > this.gasLimit) {
throw new Error('OutOfGas');
}
this.gasUsed += amount;
}
step() {
const op = this.bytecode[this.pc++];
this.charge();
// PUSH1..PUSH32
if (op >= Opcode.PUSH1 && op <= Opcode.PUSH1 + 31) {
const len = op - Opcode.PUSH1 + 1;
const data = this.bytecode.slice(this.pc, this.pc + len);
this.pc += len;
// pad to 32 bytes
const padded = new Uint8Array(32);
padded.set(data, 32 - data.length);
this.stack.push(fromBytes(padded));
return;
}
switch (op) {
case Opcode.STOP:
this.stopped = true;
return;
case Opcode.ADD: {
const b = this.stack.pop(), a = this.stack.pop();
this.stack.push(a + b);
return;
}
case Opcode.MUL: {
const b = this.stack.pop(), a = this.stack.pop();
this.stack.push(a * b);
return;
}
case Opcode.SUB: {
const b = this.stack.pop(), a = this.stack.pop();
this.stack.push(a - b);
return;
}
case Opcode.DIV: {
const b = this.stack.pop(), a = this.stack.pop();
this.stack.push(b === 0n ? 0n : a / b);
return;
}
case Opcode.POP:
this.stack.pop();
return;
case Opcode.MLOAD: {
const offset = Number(this.stack.pop());
const val = this.memory.get(offset) ?? 0n;
this.stack.push(val);
return;
}
case Opcode.MSTORE: {
const offset = Number(this.stack.pop());
const val = this.stack.pop();
this.memory.set(offset, val);
return;
}
case Opcode.SLOAD: {
const slot = this.stack.pop();
const val = this.storage.get(slot) ?? 0n;
this.stack.push(val);
return;
}
case Opcode.SSTORE: {
const slot = this.stack.pop();
const val = this.stack.pop();
this.storage.set(slot, val);
return;
}
case Opcode.PC:
this.stack.push(BigInt(this.pc - 1));
return;
case Opcode.MSIZE:
// memory size in bytes = number of words * 32
this.stack.push(BigInt(this.memory.size * 32));
return;
case Opcode.JUMP: {
const dest = Number(this.stack.pop());
if (this.bytecode[dest] !== Opcode.JUMPDEST) {
throw new Error('InvalidJump');
}
this.pc = dest;
return;
}
case Opcode.JUMPI: {
const dest = Number(this.stack.pop());
const cond = this.stack.pop();
if (cond !== 0n) {
if (this.bytecode[dest] !== Opcode.JUMPDEST) {
throw new Error('InvalidJump');
}
this.pc = dest;
}
return;
}
case Opcode.JUMPDEST:
return; // no-op
default:
throw new Error(`Unsupported opcode 0x${op.toString(16)}`);
}
}
run() {
while (!this.stopped) this.step();
}
}
// —— Example —————————————————————————————————————————————
const code = Uint8Array.from([
Opcode.PUSH1, 5,
Opcode.PUSH1, 3,
Opcode.ADD,
Opcode.PUSH1, 0,
Opcode.MSTORE,
Opcode.PUSH1, 0,
Opcode.MLOAD,
Opcode.PUSH1, 0,
Opcode.PUSH1, 32,
Opcode.SSTORE,
Opcode.STOP,
]);
const evm = new EVM(code);
evm.run();
console.log('Gas used:', evm.gasUsed);
console.log('Stack:', evm.stack.items);
console.log('Memory words:', evm.memory);
console.log('Storage:', evm.storage);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment