Skip to content

Commit

Permalink
🗂️ evm mini-compiler ilerlet
Browse files Browse the repository at this point in the history
  • Loading branch information
KimlikDAO-bot committed May 8, 2024
1 parent e2ee04e commit 5e64eda
Show file tree
Hide file tree
Showing 9 changed files with 373 additions and 127 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ include crypto/bench/Makefile
include crypto/test/Makefile
include did/test/Makefile
include ethereum/test/Makefile
include ethereum/evm/test/Makefile
include node/test/Makefile
include util/test/Makefile

.PHONY: test test.bun test.node

BIRIMLER := api cloudflare crosschain crypto did ethereum node util
BIRIMLER := api cloudflare crosschain crypto did ethereum node util ethereum/evm

test: test.bun test.node
bench: bench.bun bench.node
Expand Down
4 changes: 2 additions & 2 deletions ethereum/evm/batchSend.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Op, pushNumber } from "./opcodes";
import { evm, opsToBytes } from "./types";
import { evm, toOpData } from "./types";

/** @type {{ addr: evm.address, kpass: boolean }} */
const AddressWithKPass = {};
Expand All @@ -19,7 +19,7 @@ const batchSendFixedAmount = (recipients, amountSzabos) => {
for (const recipient of recipients)
ops.push(Op.PUSH0, Op.PUSH0, Op.PUSH0, Op.PUSH0, Op.DUP5,
Op.PUSH20, address(recipient), Op.PUSH0, Op.CALL, Op.POP);
return opsToBytes(ops.slice(0, -1));
return toOpData(ops.slice(0, -1));
}

/**
Expand Down
159 changes: 106 additions & 53 deletions ethereum/evm/opcodes.js
Original file line number Diff line number Diff line change
@@ -1,75 +1,128 @@
/** @enum {string} */
/** @enum {number} */
const Op = {
STOP: "00",
ADD: "01",
SHA3: "20",
CALLDATASIZE: "36",
CALLDATACOPY: "37",
CODECOPY: "39", // destOffset, offset, length, memory[destOffset:destOffset+length] = code[offset:offset+length]
RETURNDATASIZE: "3D",
RETURNDATACOPY: "3E",
DIFFICULTY: "44",
GASLIMIT: "45",
CHAINID: "46",
POP: "50",
MSTORE: "52",
SLOAD: "54",
SSTORE: "55",
JUMP: "56",
JUMPI: "57",
GAS: "5A",
JUMPDEST: "5B",
PUSH0: "5F",
PUSH1: "60",
PUSH20: "73",
DUP1: "80",
DUP2: "81",
DUP3: "82",
DUP4: "83",
DUP5: "84",
DUP6: "85",
DUP7: "86",
DUP16: "8F",
CREATE: "FO", // CREATE(value, offset, length)
CALL: "F1", // CALL(gas, addr, value, argsOffset, argsLength, retOffset, retLength)
RETURN: "F3", // RETURN(offset, length) return memory[offset : offset + length]
DELEGATECALL: "F4", // DELEGATECALL(gas, addr, argsOffset, argsLength, retOffset, retLength)
REVERT: "FD"
STOP: 0x00,
ADD: 0x01,
MUL: 0x02,
SUB: 0x03,
DIV: 0x04,
SDIV: 0x05,
MOD: 0x06,
SMOD: 0x07,
ADDMOD: 0x08,
MULMOD: 0x09,
SIGNEXTEND: 0x0B,
LT: 0x10,
GT: 0x11,
SLT: 0x12,
SGT: 0x13,
EQ: 0x14,
ISZERO: 0x15,
AND: 0x16,
OR: 0x17,
XOR: 0x18,
NOT: 0x19,
BYTE: 0x1A,
SHL: 0x1B,
SHR: 0x1C,
SAR: 0x1D,
SHA3: 0x20,
ADDRESS: 0x30,
BALANCE: 0x31,
ORIGIN: 0x32,
CALLER: 0x33,
CALLVALUE: 0x34,
CALLDATALOAD: 0x35,
CALLDATASIZE: 0x36,
CALLDATACOPY: 0x37,
CODESIZE: 0x38,
CODECOPY: 0x39, // destOffset, offset, length, memory[destOffset:destOffset+length] = code[offset:offset+length]
GASPRICE: 0x3A,
EXTCODESIZE: 0x3B,
EXTCODECOPY: 0x3C,
RETURNDATASIZE: 0x3D,
RETURNDATACOPY: 0x3E,
EXTCODEHASH: 0x3F,
BLOCKHASH: 0x40,
COINBASE: 0x41,
TIMESTAMP: 0x42,
NUMBER: 0x43,
DIFFICULTY: 0x44,
GASLIMIT: 0x45,
CHAINID: 0x46,
SELFBALANCE: 0x47,
BASEFEE: 0x48,
BLOBHASH: 0x49,
BLOBBASEFEE: 0x4A,
POP: 0x50,
MLOAD: 0x51,
MSTORE: 0x52,
MSTORE8: 0x53,
SLOAD: 0x54,
SSTORE: 0x55,
JUMP: 0x56,
JUMPI: 0x57,
PC: 0x58,
MSIZE: 0x59,
GAS: 0x5A,
JUMPDEST: 0x5B,
TLOAD: 0x5C,
TSTORE: 0x5D,
MCOPY: 0x5E,
PUSH0: 0x5F,
PUSH1: 0x60,
PUSH2: 0x61,
PUSH3: 0x62,
PUSH20: 0x73,
PUSH32: 0x7F,
DUP1: 0x80,
DUP2: 0x81,
DUP3: 0x82,
DUP4: 0x83,
DUP5: 0x84,
DUP6: 0x85,
DUP7: 0x86,
DUP16: 0x8F,
SWAP1: 0x90,
SWAP16: 0x9F,
LOG0: 0xA0,
LOG1: 0xA1,
LOG2: 0xA2,
LOG3: 0xA3,
LOG4: 0xA4,
CREATE: 0xF0, // CREATE(value, offset, length)
CALL: 0xF1, // CALL(gas, addr, value, argsOffset, argsLength, retOffset, retLength)
RETURN: 0xF3, // RETURN(offset, length) return memory[offset : offset + length]
DELEGATECALL: 0xF4, // DELEGATECALL(gas, addr, argsOffset, argsLength, retOffset, retLength)
CREATE2: 0xF5,
REVERT: 0xFD
}

/**
* @typedef {string}
* @typedef {!Uint8Array}
*/
const OpData = {};
const OpData = Uint8Array;

/**
* @param {number}
* @return {Op}
* @typedef {!Array<Op|OpData>}
*/
const dupN = (n) => /** @type {Op} */((127 + n).toString(16));
const Ops = {};

/**
* @param {number} n number of bytes to push to the stack as a word
* @param {number} n
* @return {Op}
*/
const pushN = (n) => /** @type {Op} */((95 + n).toString(16));
const dupN = (n) => /** @type {Op} */(127 + n);

/**
* @param {!bigint} n
* @return {!Array<Op|OpData>}
* @param {number} n number of bytes to push to the stack as a word
* @return {Op}
*/
const pushNumber = (n) => {
if (n == 0) return [Op.PUSH0];
/** @type {string} */
let ser = n.toString(16);
if (ser.length & 1) ser = "0" + ser;
return [pushN(ser.length / 2), ser];
}
const pushN = (n) => /** @type {Op} */(95 + n);

export {
Op,
Ops,
OpData,
dupN,
pushN,
pushNumber,
};
88 changes: 50 additions & 38 deletions ethereum/evm/proxies.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,66 @@
import { Op, pushNumber } from "./opcodes";
import { address, concat, evm, opsToBytes } from "./types.js";
import { Op, OpData } from "./opcodes";
import { concat, evm, pushAddress, pushBytes, pushNumber, toOpData } from "./types.js";

/**
* @param {number} offset
* @param {evm.bytes} slot
* @return {OpData}
*/
const delegateCall = (offset) => opsToBytes([
Op.CALLDATASIZE,
Op.PUSH0,
Op.PUSH0,
Op.CALLDATACOPY,
Op.PUSH0,
Op.PUSH0,
Op.CALLDATASIZE,
Op.PUSH0,
Op.PUSH0,
Op.SLOAD,
Op.GAS,
Op.DELEGATECALL,
Op.RETURNDATASIZE,
Op.PUSH0,
Op.PUSH0,
Op.RETURNDATACOPY,
Op.RETURNDATASIZE,
Op.PUSH0,
Op.DUP3,
...pushNumber(offset + 23),
Op.JUMPI,
Op.REVERT,
Op.JUMPDEST,
Op.RETURN
]);
const delegateCall = (offset, slot) => {
/**
* @const
* @type {OpData}
*/
const pushSlot = pushBytes(slot);
return toOpData([
Op.CALLDATASIZE,
Op.PUSH0,
Op.PUSH0,
Op.CALLDATACOPY,
Op.PUSH0,
Op.PUSH0,
Op.CALLDATASIZE,
Op.PUSH0,
pushSlot,
Op.SLOAD,
Op.GAS,
Op.DELEGATECALL,
Op.RETURNDATASIZE,
Op.PUSH0,
Op.PUSH0,
Op.RETURNDATACOPY,
Op.RETURNDATASIZE,
Op.PUSH0,
Op.DUP3,
pushNumber(offset + pushSlot.length + 22),
Op.JUMPI,
Op.REVERT,
Op.JUMPDEST,
Op.RETURN
])
};

/**
* @param {evm.address} codeAddress
* @return {evm.bytes}
* @param {evm.address} implAddress
* @param {evm.bytes} implSlot
* @return {OpData}
*/
const upgradableProxy = (codeAddress) => {
/** @const {evm.bytes} */
const dc = delegateCall(0);
const upgradableProxy = (implAddress, implSlot) => {
/** @const {OpData} */
const call = delegateCall(0, implSlot);
/** @const {OpData} */
const pushImplSlot = pushBytes(implSlot);

return concat(opsToBytes([
Op.PUSH20, address(codeAddress), Op.PUSH0, Op.SSTORE, // storage[0] = codeAddress
...pushNumber(dc.length),
return concat(toOpData([
pushAddress(implAddress), pushImplSlot, Op.SSTORE, // storage[0] = codeAddress
pushNumber(call.length),
Op.DUP1, // len len
...pushNumber(32), // 32 len len
pushNumber(21 + pushImplSlot.length + 10), // 32 len len
Op.PUSH0, // 0 32 len len
Op.CODECOPY, // len
Op.PUSH0, // 0 len
Op.RETURN
]), dc);
]), call);
}

export {
Expand Down
36 changes: 36 additions & 0 deletions ethereum/evm/test/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
build/ethereum/evm/test/types.compiled-test.js: ethereum/evm/test/types.compiled-test.js \
ethereum/evm/opcodes.js ethereum/evm/types.js \
util/çevir.js \
testing/assert.js testing/nodejs.d.js
mkdir -p $(dir $@)
bun google-closure-compiler -W VERBOSE -O ADVANCED --charset UTF-8 \
--jscomp_error=strictCheckTypes \
--jscomp_error=missingProperties \
--jscomp_error=unusedLocalVariables \
--module_resolution NODE \
--assume_function_wrapper \
--dependency_mode PRUNE \
--entry_point $< \
--js $^ \
--js_output_file $@
bun uglifyjs $@ -m -c toplevel,unsafe -o $@
wc $@

build/ethereum/evm/test/proxies.compiled-test.js: ethereum/evm/test/proxies.compiled-test.js \
ethereum/evm/proxies.js ethereum/evm/opcodes.js \
ethereum/evm/types.js \
util/çevir.js \
testing/assert.js testing/nodejs.d.js
mkdir -p $(dir $@)
bun google-closure-compiler -W VERBOSE -O ADVANCED --charset UTF-8 \
--jscomp_error=strictCheckTypes \
--jscomp_error=missingProperties \
--jscomp_error=unusedLocalVariables \
--module_resolution NODE \
--assume_function_wrapper \
--dependency_mode PRUNE \
--entry_point $< \
--js $^ \
--js_output_file $@
bun uglifyjs $@ -m -c toplevel,unsafe -o $@
wc $@
35 changes: 35 additions & 0 deletions ethereum/evm/test/proxies.compiled-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { assert, assertEq } from "../../../testing/assert";
import { hexten } from "../../../util/çevir";
import { Op, OpData } from "../opcodes";
import { delegateCall, upgradableProxy } from "../proxies";
import { evm } from "../types";

const testDelegateCall = () => {
const dc1 = delegateCall(0, evm.bytes.from([]));

assertEq(dc1[dc1[dc1.length - 5]], Op.JUMPDEST);

const dc2 = delegateCall(0, evm.bytes.from(["012301231231"]));

assertEq(dc2[dc2[dc2.length - 5]], Op.JUMPDEST);

}

const testUpgradableProxy = () => {
const erc1967 = hexten("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
/**
* @const
* @type {OpData}
*/
const up = upgradableProxy("0x1234567890123456789012345678901234567890", erc1967);
/** @const {OpData} */
const dc = delegateCall(0, erc1967);

const copyIdx = up.indexOf(up.length - dc.length);

assert(copyIdx != -1);
assertEq(up[copyIdx - 3], dc.length);
}

testDelegateCall();
testUpgradableProxy();
Loading

0 comments on commit 5e64eda

Please sign in to comment.