@tokamak-zk-evm/synthesizer
Version:
Tokamak zk-EVM Synthesizer - Processes Ethereum transactions into wire maps for Tokamak zk-SNARK proof generation
447 lines • 14.1 kB
JavaScript
import { Hardfork } from '@synthesizer-libs/common';
import { handlers } from './functions.js';
import { dynamicGasHandlers } from './gas.js';
import { getFullname } from './util.js';
// import { SynthesizerHandler } from './synthesizer/types.js'
// import { synthesizerHandlers } from './synthesizer/handlers.js'
export class Opcode {
constructor({ code, name, fullName, fee, isAsync, dynamicGas, }) {
this.code = code;
this.name = name;
this.fullName = fullName;
this.fee = fee;
this.feeBigInt = BigInt(fee);
this.isAsync = isAsync;
this.dynamicGas = dynamicGas;
this.isInvalid = this.name === 'INVALID';
// Opcode isn't subject to change, thus all further modifications are prevented.
Object.freeze(this);
}
}
// Default: sync and no dynamic gas
const defaultOp = (name) => {
return { name, isAsync: false, dynamicGas: false };
};
const dynamicGasOp = (name) => {
return { name, isAsync: false, dynamicGas: true };
};
const asyncOp = (name) => {
return { name, isAsync: true, dynamicGas: false };
};
const asyncAndDynamicGasOp = (name) => {
return { name, isAsync: true, dynamicGas: true };
};
// Base opcode list. The opcode list is extended in future hardforks
const opcodes = {
// 0x0 range - arithmetic ops
// name, async
0x00: defaultOp('STOP'),
0x01: defaultOp('ADD'),
0x02: defaultOp('MUL'),
0x03: defaultOp('SUB'),
0x04: defaultOp('DIV'),
0x05: defaultOp('SDIV'),
0x06: defaultOp('MOD'),
0x07: defaultOp('SMOD'),
0x08: defaultOp('ADDMOD'),
0x09: defaultOp('MULMOD'),
0x0a: dynamicGasOp('EXP'),
0x0b: defaultOp('SIGNEXTEND'),
// 0x10 range - bit ops
0x10: defaultOp('LT'),
0x11: defaultOp('GT'),
0x12: defaultOp('SLT'),
0x13: defaultOp('SGT'),
0x14: defaultOp('EQ'),
0x15: defaultOp('ISZERO'),
0x16: defaultOp('AND'),
0x17: defaultOp('OR'),
0x18: defaultOp('XOR'),
0x19: defaultOp('NOT'),
0x1a: defaultOp('BYTE'),
// 0x20 range - crypto
0x20: dynamicGasOp('KECCAK256'),
// 0x30 range - closure state
0x30: asyncOp('ADDRESS'),
0x31: asyncAndDynamicGasOp('BALANCE'),
0x32: asyncOp('ORIGIN'),
0x33: asyncOp('CALLER'),
0x34: asyncOp('CALLVALUE'),
0x35: asyncOp('CALLDATALOAD'),
0x36: asyncOp('CALLDATASIZE'),
0x37: asyncAndDynamicGasOp('CALLDATACOPY'),
0x38: defaultOp('CODESIZE'),
0x39: dynamicGasOp('CODECOPY'),
0x3a: defaultOp('GASPRICE'),
0x3b: asyncAndDynamicGasOp('EXTCODESIZE'),
0x3c: asyncAndDynamicGasOp('EXTCODECOPY'),
// '0x40' range - block operations
0x40: asyncOp('BLOCKHASH'),
0x41: asyncOp('COINBASE'),
0x42: asyncOp('TIMESTAMP'),
0x43: asyncOp('NUMBER'),
0x44: asyncOp('DIFFICULTY'),
0x45: asyncOp('GASLIMIT'),
// 0x50 range - 'storage' and execution
0x50: defaultOp('POP'),
0x51: dynamicGasOp('MLOAD'),
0x52: dynamicGasOp('MSTORE'),
0x53: dynamicGasOp('MSTORE8'),
0x54: asyncAndDynamicGasOp('SLOAD'),
0x55: asyncAndDynamicGasOp('SSTORE'),
0x56: defaultOp('JUMP'),
0x57: defaultOp('JUMPI'),
0x58: defaultOp('PC'),
0x59: defaultOp('MSIZE'),
0x5a: defaultOp('GAS'),
0x5b: defaultOp('JUMPDEST'),
// 0x60, range
0x60: defaultOp('PUSH'),
0x61: defaultOp('PUSH'),
0x62: defaultOp('PUSH'),
0x63: defaultOp('PUSH'),
0x64: defaultOp('PUSH'),
0x65: defaultOp('PUSH'),
0x66: defaultOp('PUSH'),
0x67: defaultOp('PUSH'),
0x68: defaultOp('PUSH'),
0x69: defaultOp('PUSH'),
0x6a: defaultOp('PUSH'),
0x6b: defaultOp('PUSH'),
0x6c: defaultOp('PUSH'),
0x6d: defaultOp('PUSH'),
0x6e: defaultOp('PUSH'),
0x6f: defaultOp('PUSH'),
0x70: defaultOp('PUSH'),
0x71: defaultOp('PUSH'),
0x72: defaultOp('PUSH'),
0x73: defaultOp('PUSH'),
0x74: defaultOp('PUSH'),
0x75: defaultOp('PUSH'),
0x76: defaultOp('PUSH'),
0x77: defaultOp('PUSH'),
0x78: defaultOp('PUSH'),
0x79: defaultOp('PUSH'),
0x7a: defaultOp('PUSH'),
0x7b: defaultOp('PUSH'),
0x7c: defaultOp('PUSH'),
0x7d: defaultOp('PUSH'),
0x7e: defaultOp('PUSH'),
0x7f: defaultOp('PUSH'),
0x80: defaultOp('DUP'),
0x81: defaultOp('DUP'),
0x82: defaultOp('DUP'),
0x83: defaultOp('DUP'),
0x84: defaultOp('DUP'),
0x85: defaultOp('DUP'),
0x86: defaultOp('DUP'),
0x87: defaultOp('DUP'),
0x88: defaultOp('DUP'),
0x89: defaultOp('DUP'),
0x8a: defaultOp('DUP'),
0x8b: defaultOp('DUP'),
0x8c: defaultOp('DUP'),
0x8d: defaultOp('DUP'),
0x8e: defaultOp('DUP'),
0x8f: defaultOp('DUP'),
0x90: defaultOp('SWAP'),
0x91: defaultOp('SWAP'),
0x92: defaultOp('SWAP'),
0x93: defaultOp('SWAP'),
0x94: defaultOp('SWAP'),
0x95: defaultOp('SWAP'),
0x96: defaultOp('SWAP'),
0x97: defaultOp('SWAP'),
0x98: defaultOp('SWAP'),
0x99: defaultOp('SWAP'),
0x9a: defaultOp('SWAP'),
0x9b: defaultOp('SWAP'),
0x9c: defaultOp('SWAP'),
0x9d: defaultOp('SWAP'),
0x9e: defaultOp('SWAP'),
0x9f: defaultOp('SWAP'),
0xa0: dynamicGasOp('LOG'),
0xa1: dynamicGasOp('LOG'),
0xa2: dynamicGasOp('LOG'),
0xa3: dynamicGasOp('LOG'),
0xa4: dynamicGasOp('LOG'),
// '0xf0' range - closures
0xf0: asyncAndDynamicGasOp('CREATE'),
0xf1: asyncAndDynamicGasOp('CALL'),
0xf2: asyncAndDynamicGasOp('CALLCODE'),
0xf3: dynamicGasOp('RETURN'),
// '0x70', range - other
0xfe: defaultOp('INVALID'),
0xff: asyncAndDynamicGasOp('SELFDESTRUCT'),
};
// Array of hard forks in order. These changes are repeatedly applied to `opcodes` until the hard fork is in the future based upon the common
// TODO: All gas price changes should be moved to common
// If the base gas cost of any of the operations change, then these should also be added to this list.
// If there are context variables changed (such as "warm slot reads") which are not the base gas fees,
// Then this does not have to be added.
const hardforkOpcodes = [
{
hardfork: Hardfork.Homestead,
opcodes: {
0xf4: asyncAndDynamicGasOp('DELEGATECALL'), // EIP-7
},
},
{
hardfork: Hardfork.TangerineWhistle,
opcodes: {
0x54: asyncAndDynamicGasOp('SLOAD'),
0xf1: asyncAndDynamicGasOp('CALL'),
0xf2: asyncAndDynamicGasOp('CALLCODE'),
0x3b: asyncAndDynamicGasOp('EXTCODESIZE'),
0x3c: asyncAndDynamicGasOp('EXTCODECOPY'),
0xf4: asyncAndDynamicGasOp('DELEGATECALL'),
0xff: asyncAndDynamicGasOp('SELFDESTRUCT'),
0x31: asyncAndDynamicGasOp('BALANCE'),
},
},
{
hardfork: Hardfork.Byzantium,
opcodes: {
0xfd: dynamicGasOp('REVERT'),
0xfa: asyncAndDynamicGasOp('STATICCALL'),
0x3d: asyncOp('RETURNDATASIZE'),
0x3e: asyncAndDynamicGasOp('RETURNDATACOPY'), // EIP-211
},
},
{
hardfork: Hardfork.Constantinople,
opcodes: {
0x1b: defaultOp('SHL'),
0x1c: defaultOp('SHR'),
0x1d: defaultOp('SAR'),
0x3f: asyncAndDynamicGasOp('EXTCODEHASH'),
0xf5: asyncAndDynamicGasOp('CREATE2'), // EIP-1014
},
},
{
hardfork: Hardfork.Istanbul,
opcodes: {
0x46: defaultOp('CHAINID'),
0x47: defaultOp('SELFBALANCE'), // EIP-1884
},
},
{
hardfork: Hardfork.Paris,
opcodes: {
0x44: asyncOp('PREVRANDAO'), // EIP-4399
},
},
];
const eipOpcodes = [
{
eip: 663,
opcodes: {
0xe6: defaultOp('DUPN'),
0xe7: defaultOp('SWAPN'),
0xe8: defaultOp('EXCHANGE'),
},
},
{
eip: 1153,
opcodes: {
0x5c: defaultOp('TLOAD'),
0x5d: defaultOp('TSTORE'),
},
},
{
eip: 3198,
opcodes: {
0x48: defaultOp('BASEFEE'),
},
},
{
eip: 3855,
opcodes: {
0x5f: defaultOp('PUSH0'),
},
},
{
eip: 4200,
opcodes: {
0xe0: defaultOp('RJUMP'),
0xe1: defaultOp('RJUMPI'),
0xe2: defaultOp('RJUMPV'),
},
},
{
eip: 4750,
opcodes: {
0xe3: defaultOp('CALLF'),
0xe4: defaultOp('RETF'),
},
},
{
eip: 4844,
opcodes: {
0x49: defaultOp('BLOBHASH'),
},
},
{
eip: 5656,
opcodes: {
0x5e: dynamicGasOp('MCOPY'),
},
},
{
eip: 6206,
opcodes: {
0xe5: defaultOp('JUMPF'),
},
},
{
eip: 7069,
opcodes: {
0xf7: defaultOp('RETURNDATALOAD'),
0xf8: asyncAndDynamicGasOp('EXTCALL'),
0xf9: asyncAndDynamicGasOp('EXTDELEGATECALL'),
0xfb: asyncAndDynamicGasOp('EXTSTATICCALL'),
},
},
{
eip: 7480,
opcodes: {
0xd0: defaultOp('DATALOAD'),
0xd1: defaultOp('DATALOADN'),
0xd2: defaultOp('DATASIZE'),
0xd3: dynamicGasOp('DATACOPY'),
},
},
{
eip: 7516,
opcodes: {
0x4a: defaultOp('BLOBBASEFEE'),
},
},
{
eip: 7620,
opcodes: {
0xec: asyncAndDynamicGasOp('EOFCREATE'),
0xee: asyncAndDynamicGasOp('RETURNCONTRACT'),
},
},
];
/**
* Convert basic opcode info dictionary into complete OpcodeList instance.
*
* @param opcodes {Object} Receive basic opcodes info dictionary.
* @returns {OpcodeList} Complete Opcode list
*/
function createOpcodes(opcodes) {
const result = new Map();
for (const [key, value] of Object.entries(opcodes)) {
const code = parseInt(key, 10);
if (isNaN(value.fee))
value.fee = 0;
result.set(code, new Opcode({
code,
fullName: getFullname(code, value.name),
...value,
}));
}
return result;
}
/**
* Get suitable opcodes for the required hardfork.
*
* @param common {Common} Ethereumjs Common metadata object.
* @param customOpcodes List with custom opcodes (see EVM `customOpcodes` option description).
* @returns {OpcodeList} Opcodes dictionary object.
*/
export function getOpcodesForHF(common, customOpcodes) {
let opcodeBuilder = { ...opcodes };
const handlersCopy = new Map(handlers);
const dynamicGasHandlersCopy = new Map(dynamicGasHandlers);
for (let fork = 0; fork < hardforkOpcodes.length; fork++) {
if (common.gteHardfork(hardforkOpcodes[fork].hardfork)) {
opcodeBuilder = { ...opcodeBuilder, ...hardforkOpcodes[fork].opcodes };
}
}
for (const eipOps of eipOpcodes) {
if (common.isActivatedEIP(eipOps.eip)) {
opcodeBuilder = { ...opcodeBuilder, ...eipOps.opcodes };
}
}
for (const key in opcodeBuilder) {
const baseFee = Number(common.param(`${opcodeBuilder[key].name.toLowerCase()}Gas`));
// explicitly verify that we have defined a base fee
if (baseFee === undefined) {
throw new Error(`base fee not defined for: ${opcodeBuilder[key].name}`);
}
opcodeBuilder[key].fee = baseFee;
}
if (customOpcodes) {
for (const _code of customOpcodes) {
const code = _code;
if (code.logicFunction === undefined) {
delete opcodeBuilder[code.opcode];
continue;
}
// Sanity checks
if (code.opcodeName === undefined || code.baseFee === undefined) {
throw new Error(`Custom opcode ${code.opcode} does not have the required values: opcodeName and baseFee are required`);
}
const entry = {
[code.opcode]: {
name: code.opcodeName,
isAsync: true,
dynamicGas: code.gasFunction !== undefined,
fee: code.baseFee,
feeBigInt: BigInt(code.baseFee),
},
};
opcodeBuilder = { ...opcodeBuilder, ...entry };
if (code.gasFunction !== undefined) {
dynamicGasHandlersCopy.set(code.opcode, code.gasFunction);
}
// logicFunction is never undefined
handlersCopy.set(code.opcode, code.logicFunction);
}
}
//const dynamicGasHandlers = dynamicGasHandlersCopy
//const handlers = handlersCopy
const ops = createOpcodes(opcodeBuilder);
const opcodeMap = [];
for (const [opNumber, op] of ops) {
const dynamicGas = dynamicGasHandlersCopy.get(opNumber);
const handler = handlersCopy.get(opNumber);
/**
* @todo: Need to add synthesizerHandler
*/
// const synthesizerHandler = synthesizerHandlers.get(opNumber)!
// const subcircuitCode = subcircuits.find((entry) => parseInt(entry.opcode, 16) === opNumber)
opcodeMap[opNumber] = {
opcodeInfo: op,
opHandler: handler,
gasHandler: dynamicGas,
// synthesizerHandler,
// subcircuit: subcircuitCode
// ? {
// subcircuitId: subcircuitCode.id,
// nWire: subcircuitCode.Nwires,
// outIdx: subcircuitCode.Out_idx[0],
// nOut: subcircuitCode.Out_idx[1],
// inIdx: subcircuitCode.In_idx[0],
// nIn: subcircuitCode.In_idx[1],
// }
// : undefined,
};
}
const INVALID = opcodeMap[0xfe];
for (let i = 0x0; i <= 0xff; i++) {
if (opcodeMap[i] === undefined) {
opcodeMap[i] = INVALID;
}
}
return {
dynamicGasHandlers: dynamicGasHandlersCopy,
handlers: handlersCopy,
opcodes: ops,
opcodeMap,
};
}
//# sourceMappingURL=codes.js.map