UNPKG

@tokamak-zk-evm/synthesizer

Version:

Tokamak zk-EVM Synthesizer - Processes Ethereum transactions into wire maps for Tokamak zk-SNARK proof generation

1,688 lines (1,534 loc) 78.6 kB
import { Address, BIGINT_0, BIGINT_1, BIGINT_160, BIGINT_2, BIGINT_224, BIGINT_255, BIGINT_256, BIGINT_2EXP160, BIGINT_2EXP224, BIGINT_2EXP96, BIGINT_31, BIGINT_32, BIGINT_7, BIGINT_8, BIGINT_96, MAX_INTEGER_BIGINT, TWO_POW256, bigIntToAddressBytes, bigIntToBytes, bytesToBigInt, bytesToHex, bytesToInt, concatBytes, equalsBytes, getVerkleTreeIndicesForStorageSlot, setLengthLeft, } from "@synthesizer-libs/util" import { keccak256 } from 'ethereum-cryptography/keccak.js' import { EOFContainer, EOFContainerMode } from '../eof/container.js' import { EOFError } from '../eof/errors.js' import { EOFBYTES, EOFHASH, isEOF } from '../eof/util.js' import { ERROR } from '../exceptions.js' import { prepareEXTCodePt, synthesizerArith, synthesizerBlkInf, synthesizerEnvInf, } from '../tokamak/core/synthesizer.js' import { copyMemoryRegion, simulateMemoryPt } from '../tokamak/pointers/index.js' import { DELEGATION_7702_FLAG } from '../types.js' import { createAddressFromStackBigInt, describeLocation, exponentiation, fromTwos, getDataSlice, jumpIsValid, mod, toTwos, trap, writeCallOutput, } from './util.js' import type { RunState } from '../interpreter.js' import type { MemoryPtEntry, MemoryPts } from '../tokamak/pointers/index.js' import type { Common } from '@synthesizer-libs/common' import { DEFAULT_SOURCE_SIZE } from "../tokamak/constant/constants.js" import { SynthesizerValidator, } from '../tokamak/validation/index.js' export interface SyncOpHandler { (runState: RunState, common: Common): void } export interface AsyncOpHandler { (runState: RunState, common: Common): Promise<void> } export type OpHandler = SyncOpHandler | AsyncOpHandler function getEIP7702DelegatedAddress(code: Uint8Array) { if (equalsBytes(code.slice(0, 3), DELEGATION_7702_FLAG)) { return new Address(code.slice(3, 24)) } } async function eip7702CodeCheck(runState: RunState, code: Uint8Array) { const address = getEIP7702DelegatedAddress(code) if (address !== undefined) { return runState.stateManager.getCode(address) } return code } // the opcode functions export const handlers: Map<number, OpHandler> = new Map([ // 0x00: STOP [ 0x00, function () { trap(ERROR.STOP) }, ], // 0x01: ADD [ 0x01, function (runState) { const [a, b] = runState.stack.popN(2) const r = mod(a + b, TWO_POW256) runState.stack.push(r) // For Synthesizer // synthesizerArith('ADD', [a, b], r, runState) }, ], // 0x02: MUL [ 0x02, function (runState) { const [a, b] = runState.stack.popN(2) const r = mod(a * b, TWO_POW256) runState.stack.push(r) // For Synthesizer // synthesizerArith('MUL', [a, b], r, runState) }, ], // 0x03: SUB [ 0x03, function (runState) { const [a, b] = runState.stack.popN(2) const r = mod(a - b, TWO_POW256) runState.stack.push(r) // For Synthesizer // synthesizerArith('SUB', [a, b], r, runState) }, ], // 0x04: DIV [ 0x04, function (runState) { const [a, b] = runState.stack.popN(2) let r if (b === BIGINT_0) { r = BIGINT_0 } else { r = mod(a / b, TWO_POW256) } runState.stack.push(r) // For Synthesizer // synthesizerArith('DIV', [a, b], r, runState) }, ], // 0x05: SDIV [ 0x05, function (runState) { const [a, b] = runState.stack.popN(2) let r if (b === BIGINT_0) { r = BIGINT_0 } else { r = toTwos(fromTwos(a) / fromTwos(b)) } runState.stack.push(r) // For Synthesizer // synthesizerArith('SDIV', [a, b], r, runState) }, ], // 0x06: MOD [ 0x06, function (runState) { const [a, b] = runState.stack.popN(2) let r if (b === BIGINT_0) { r = b } else { r = mod(a, b) } runState.stack.push(r) // For Synthesizer // synthesizerArith('MOD', [a, b], r, runState) }, ], // 0x07: SMOD [ 0x07, function (runState) { const [a, b] = runState.stack.popN(2) let r if (b === BIGINT_0) { r = b } else { r = fromTwos(a) % fromTwos(b) } runState.stack.push(toTwos(r)) // For Synthesizer // synthesizerArith( 'SMOD', [a, b], /* * Convert SMOD operation result to two's complement representation for EVM's 256-bit unsigned integer. * Required because EVM processes all values as unsigned 256-bit integers, so negative results need proper conversion. * Using toTwos(r) to convert negative results to two's complement ensures SMOD operation works as expected. */ toTwos(r), runState, ) }, ], // 0x08: ADDMOD [ 0x08, function (runState) { const [a, b, c] = runState.stack.popN(3) let r if (c === BIGINT_0) { r = BIGINT_0 } else { r = mod(a + b, c) } runState.stack.push(r) // For Synthesizer // synthesizerArith('ADDMOD', [a, b, c], r, runState) }, ], // 0x09: MULMOD [ 0x09, function (runState) { const [a, b, c] = runState.stack.popN(3) let r if (c === BIGINT_0) { r = BIGINT_0 } else { r = mod(a * b, c) } runState.stack.push(r) // For Synthesizer // synthesizerArith('MULMOD', [a, b, c], r, runState) }, ], // 0x0a: EXP [ 0x0a, function (runState) { const [base, exponent] = runState.stack.popN(2) if (base === BIGINT_2) { switch (exponent) { case BIGINT_96: runState.stack.push(BIGINT_2EXP96) // For Synthesizer // synthesizerArith('EXP', [base, exponent], BIGINT_2EXP96, runState) return case BIGINT_160: runState.stack.push(BIGINT_2EXP160) // For Synthesizer // synthesizerArith('EXP', [base, exponent], BIGINT_2EXP160, runState) return case BIGINT_224: runState.stack.push(BIGINT_2EXP224) // For Synthesizer // synthesizerArith('EXP', [base, exponent], BIGINT_2EXP224, runState) return } } if (exponent === BIGINT_0) { runState.stack.push(BIGINT_1) // For Synthesizer // synthesizerArith('EXP', [base, exponent], BIGINT_1, runState) return } if (base === BIGINT_0) { runState.stack.push(base) // For Synthesizer // synthesizerArith('EXP', [base, exponent], base, runState) return } const r = exponentiation(base, exponent) runState.stack.push(r) // For Synthesizer // synthesizerArith('EXP', [base, exponent], r, runState) }, ], // 0x0b: SIGNEXTEND [ 0x0b, function (runState) { /* eslint-disable-next-line prefer-const */ const [k, _val] = runState.stack.popN(2) let val = _val if (k < BIGINT_31) { const signBit = k * BIGINT_8 + BIGINT_7 const mask = (BIGINT_1 << signBit) - BIGINT_1 if ((_val >> signBit) & BIGINT_1) { val = _val | BigInt.asUintN(256, ~mask) } else { val = _val & mask } } runState.stack.push(val) // For Synthesizer // synthesizerArith('SIGNEXTEND', [k, _val], val, runState) }, ], // 0x10 range - bit ops // 0x10: LT [ 0x10, function (runState) { const [a, b] = runState.stack.popN(2) const r = a < b ? BIGINT_1 : BIGINT_0 runState.stack.push(r) // For Synthesizer // synthesizerArith('LT', [a, b], r, runState) }, ], // 0x11: GT [ 0x11, function (runState) { const [a, b] = runState.stack.popN(2) const r = a > b ? BIGINT_1 : BIGINT_0 runState.stack.push(r) // For Synthesizer // synthesizerArith('GT', [a, b], r, runState) }, ], // 0x12: SLT [ 0x12, function (runState) { const [a, b] = runState.stack.popN(2) const r = fromTwos(a) < fromTwos(b) ? BIGINT_1 : BIGINT_0 runState.stack.push(r) // For Synthesizer // synthesizerArith('SLT', [a, b], r, runState) }, ], // 0x13: SGT [ 0x13, function (runState) { const [a, b] = runState.stack.popN(2) const r = fromTwos(a) > fromTwos(b) ? BIGINT_1 : BIGINT_0 runState.stack.push(r) // For Synthesizer // synthesizerArith('SGT', [a, b], r, runState) }, ], // 0x14: EQ [ 0x14, function (runState) { const [a, b] = runState.stack.popN(2) const r = a === b ? BIGINT_1 : BIGINT_0 runState.stack.push(r) // For Synthesizer // synthesizerArith('EQ', [a, b], r, runState) }, ], // 0x15: ISZERO [ 0x15, function (runState) { const a = runState.stack.pop() const r = a === BIGINT_0 ? BIGINT_1 : BIGINT_0 runState.stack.push(r) // For Synthesizer // synthesizerArith('ISZERO', [a], r, runState) }, ], // 0x16: AND [ 0x16, function (runState) { const [a, b] = runState.stack.popN(2) const r = a & b runState.stack.push(r) // For Synthesizer // synthesizerArith('AND', [a, b], r, runState) }, ], // 0x17: OR [ 0x17, function (runState) { const [a, b] = runState.stack.popN(2) const r = a | b runState.stack.push(r) // For Synthesizer // synthesizerArith('OR', [a, b], r, runState) }, ], // 0x18: XOR [ 0x18, function (runState) { const [a, b] = runState.stack.popN(2) const r = a ^ b runState.stack.push(r) // For Synthesizer // synthesizerArith('XOR', [a, b], r, runState) }, ], // 0x19: NOT [ 0x19, function (runState) { const a = runState.stack.pop() const r = BigInt.asUintN(256, ~a) runState.stack.push(r) // For Synthesizer // synthesizerArith('NOT', [a], r, runState) }, ], // 0x1a: BYTE [ 0x1a, function (runState) { const [pos, word] = runState.stack.popN(2) if (pos > BIGINT_32) { runState.stack.push(BIGINT_0) return } const r = (word >> ((BIGINT_31 - pos) * BIGINT_8)) & BIGINT_255 runState.stack.push(r) // For Synthesizer // synthesizerArith('BYTE', [pos, word], r, runState) }, ], // 0x1b: SHL [ 0x1b, function (runState) { const [a, b] = runState.stack.popN(2) if (a > BIGINT_256) { runState.stack.push(BIGINT_0) return } const r = (b << a) & MAX_INTEGER_BIGINT runState.stack.push(r) // For Synthesizer // synthesizerArith('SHL', [a, b], r, runState) }, ], // 0x1c: SHR [ 0x1c, function (runState) { const [a, b] = runState.stack.popN(2) if (a > 256) { runState.stack.push(BIGINT_0) // For Synthesizer // synthesizerArith('SHR', [a, b], BIGINT_0, runState) return } const r = b >> a runState.stack.push(r) // For Synthesizer // synthesizerArith('SHR', [a, b], r, runState) }, ], // 0x1d: SAR [ 0x1d, function (runState) { const [a, b] = runState.stack.popN(2) let r const bComp = BigInt.asIntN(256, b) const isSigned = bComp < 0 if (a > 256) { if (isSigned) { r = MAX_INTEGER_BIGINT } else { r = BIGINT_0 } runState.stack.push(r) return } const c = b >> a if (isSigned) { const shiftedOutWidth = BIGINT_255 - a const mask = (MAX_INTEGER_BIGINT >> shiftedOutWidth) << shiftedOutWidth r = c | mask } else { r = c } runState.stack.push(r) // For Synthesizer // synthesizerArith('SAR', [a, b], r, runState) }, ], // 0x20 range - crypto // 0x20: KECCAK256 [ 0x20, function (runState, common) { const [offset, length] = runState.stack.popN(2) let data = new Uint8Array(0) if (length !== BIGINT_0) { data = runState.memory.read(Number(offset), Number(length)) } const r = BigInt(bytesToHex((common.customCrypto.keccak256 ?? keccak256)(data))) runState.stack.push(r) // For synthesizer // const [offsetPt, lengthPt] = runState.stackPt.popN(2) if (offsetPt.value !== offset || lengthPt.value !== length) { throw new Error(`Synthesizer: KECCAK256: Input data mismatch`) } const offsetNum = Number(offset) const lengthNum = Number(length) let nChunks = 1 if (lengthNum > 32) { nChunks = Math.ceil(lengthNum / 32) } const chunkDataPts = [] let dataRecovered = BIGINT_0 let lengthLeft = lengthNum for (let i = 0; i < nChunks; i++) { const _offset = offsetNum + 32 * i const _length = lengthLeft > 32 ? 32 : lengthLeft lengthLeft -= _length const dataAliasInfos = runState.memoryPt.getDataAlias(_offset, _length) if (dataAliasInfos.length > 0) { chunkDataPts[i] = runState.synthesizer.placeMemoryToStack(dataAliasInfos) } else { chunkDataPts[i] = runState.synthesizer.loadAuxin(BIGINT_0) } dataRecovered += chunkDataPts[i].value << BigInt((nChunks - i - 1) * 32 * 8) } if (bytesToBigInt(data) !== dataRecovered) { throw new Error(`Synthesizer: KECCAK256: Data loaded to be hashed mismatch`) } runState.stackPt.push(runState.synthesizer.loadKeccak(chunkDataPts, r, length)) if (runState.stack.peek(1)[0] !== runState.stackPt.peek(1)[0].value) { throw new Error(`Synthesizer: KECCAK256: Output data mismatch`) } }, ], // 0x30 range - closure state // 0x30: ADDRESS [ 0x30, async function (runState) { const address = bytesToBigInt(runState.interpreter.getAddress().bytes) runState.stack.push(address) // For Synthesizer // await synthesizerEnvInf('ADDRESS', runState) }, ], // 0x31: BALANCE [ 0x31, async function (runState) { const addressBigInt = runState.stack.pop() const address = createAddressFromStackBigInt(addressBigInt) const balance = await runState.interpreter.getExternalBalance(address) runState.stack.push(balance) // For Synthesizer // await synthesizerEnvInf('BALANCE', runState, addressBigInt) }, ], // 0x32: ORIGIN [ 0x32, async function (runState) { runState.stack.push(runState.interpreter.getTxOrigin()) // For Synthesizer // await synthesizerEnvInf('ORIGIN', runState) }, ], // 0x33: CALLER [ 0x33, async function (runState) { runState.stack.push(runState.interpreter.getCaller()) // For Synthesizer // await synthesizerEnvInf('ADDRESS', runState) }, ], // 0x34: CALLVALUE [ 0x34, async function (runState) { runState.stack.push(runState.interpreter.getCallValue()) // For Synthesizer // await synthesizerEnvInf('CALLVALUE', runState) }, ], // 0x35: CALLDATALOAD [ 0x35, async function (runState) { const pos = runState.stack.pop() if (pos > runState.interpreter.getCallDataSize()) { runState.stack.push(BIGINT_0) // For synthesizer // await synthesizerEnvInf('CALLDATALOAD', runState, undefined, pos) return } const i = Number(pos) let loaded = runState.interpreter.getCallData().subarray(i, i + 32) loaded = loaded.length ? loaded : Uint8Array.from([0]) let r = bytesToBigInt(loaded) if (loaded.length < 32) { r = r << (BIGINT_8 * BigInt(32 - loaded.length)) } runState.stack.push(r) // For synthesizer // await synthesizerEnvInf('CALLDATALOAD', runState, undefined, pos) }, ], // 0x36: CALLDATASIZE [ 0x36, async function (runState) { runState.stack.push(runState.interpreter.getCallDataSize()) // For Synthesizer // await synthesizerEnvInf('CALLDATASIZE', runState) }, ], // 0x37: CALLDATACOPY [ 0x37, function (runState) { const [memOffset, dataOffset, dataLength] = runState.stack.popN(3) if (dataLength !== BIGINT_0) { const data = getDataSlice(runState.interpreter.getCallData(), dataOffset, dataLength) const memOffsetNum = Number(memOffset) const dataLengthNum = Number(dataLength) runState.memory.write(memOffsetNum, dataLengthNum, data) } // For synthesizer const [_memOffset, _dataOffset, _dataLength] = runState.stackPt.popN(3) if ( _memOffset.value !== memOffset || _dataOffset.value !== dataOffset || _dataLength.value !== dataLength ) { throw new Error(`Synthesizer: CALLDATACOPY: Input data mismatch`) } if (dataLength !== BIGINT_0) { const dataOffsetNum = Number(dataOffset) const calldataMemoryPts = runState.interpreter._env.callMemoryPts let memoryPtsToCopy: MemoryPts = [] if (calldataMemoryPts.length > 0) { // Case: The calldata is originated from the parent context memoryPtsToCopy = copyMemoryRegion( runState, dataOffset, dataLength, calldataMemoryPts, memOffset, ) } else { // Case: The calldata is originated from Environmental Information let chunkedLength = Number(dataLength) let accumOffsetShift = 0n let entryToCopy: MemoryPtEntry while (chunkedLength > 0){ const chunkSize = chunkedLength >= DEFAULT_SOURCE_SIZE ? DEFAULT_SOURCE_SIZE : chunkedLength const data = getDataSlice(runState.interpreter.getCallData(), dataOffset + accumOffsetShift, BigInt(chunkSize)) entryToCopy = { memOffset: Number(memOffset + accumOffsetShift), containerSize: chunkSize, dataPt: runState.synthesizer.loadEnvInf( runState.env.address.toString(), 'Calldata', bytesToBigInt(data), Number(dataOffset + accumOffsetShift), chunkSize, ) } memoryPtsToCopy.push(entryToCopy) chunkedLength -= chunkSize accumOffsetShift += BigInt(chunkSize) } } for (const entry of memoryPtsToCopy) { // the lower index, the older data runState.memoryPt.write(entry.memOffset, entry.containerSize, entry.dataPt) } } const _outData = runState.memoryPt.viewMemory(Number(memOffset), Number(dataLength)) const outData = runState.memory.read(Number(memOffset), Number(dataLength)) if (!equalsBytes(_outData, outData)) { throw new Error(`Synthesizer: CALLDATACOPY: Output data mismatch`) } }, ], // 0x38: CODESIZE [ 0x38, async function (runState) { runState.stack.push(runState.interpreter.getCodeSize()) // For Synthesizer // await synthesizerEnvInf('CODESIZE', runState) }, ], // 0x39: CODECOPY [ 0x39, function (runState) { const [memOffset, codeOffset, dataLength] = runState.stack.popN(3) if (dataLength !== BIGINT_0) { const data = getDataSlice(runState.interpreter.getCode(), codeOffset, dataLength) const memOffsetNum = Number(memOffset) const lengthNum = Number(dataLength) runState.memory.write(memOffsetNum, lengthNum, data) } // For Synthesizer // const [memOffsetPt, codeOffsetPt, dataLengthPt] = runState.stackPt.popN(3) if ( memOffsetPt.value !== memOffset || codeOffsetPt.value !== codeOffset || dataLengthPt.value !== dataLength ) { throw new Error(`Synthesizer: CODECOPY: Input data mismatch`) } if (dataLength !== BIGINT_0) { let chunkedLength = Number(dataLength) let accumOffsetShift = 0n while (chunkedLength > 0){ const chunkSize = chunkedLength >= DEFAULT_SOURCE_SIZE ? DEFAULT_SOURCE_SIZE : chunkedLength const data = getDataSlice(runState.interpreter.getCode(), codeOffset+accumOffsetShift, BigInt(chunkSize)) const dataBigint = bytesToBigInt(data) const dataPt = runState.synthesizer.loadEnvInf( runState.env.address.toString(), 'Code', dataBigint, Number(codeOffset+accumOffsetShift), chunkSize, ) runState.memoryPt.write(Number(memOffset+accumOffsetShift), chunkSize, dataPt) chunkedLength -= chunkSize accumOffsetShift += BigInt(chunkSize) } } const _outData = runState.memoryPt.viewMemory(Number(memOffset), Number(dataLength)) const outData = runState.memory.read(Number(memOffset), Number(dataLength)) if (!equalsBytes(_outData, outData)) { throw new Error(`Synthesizer: CODECOPY: Output data mismatch`) } }, ], // 0x3b: EXTCODESIZE [ 0x3b, async function (runState, common) { const addressBigInt = runState.stack.pop() const address = createAddressFromStackBigInt(addressBigInt) // EOF check let code = await runState.stateManager.getCode(address) if (isEOF(code)) { // In legacy code, the target code is treated as to be "EOFBYTES" code runState.stack.push(BigInt(EOFBYTES.length)) // For Synthesizer // await synthesizerEnvInf('EXTCODESIZE', runState, addressBigInt) return } else if (common.isActivatedEIP(7702)) { code = await eip7702CodeCheck(runState, code) } const size = BigInt(code.length) runState.stack.push(size) // For Synthesizer // await synthesizerEnvInf('EXTCODESIZE', runState, addressBigInt) }, ], // 0x3c: EXTCODECOPY [ 0x3c, async function (runState, common) { const [addressBigInt, memOffset, codeOffset, dataLength] = runState.stack.popN(4) if (dataLength !== BIGINT_0) { const address = createAddressFromStackBigInt(addressBigInt) let code = await runState.stateManager.getCode(address) if (isEOF(code)) { // In legacy code, the target code is treated as to be "EOFBYTES" code code = EOFBYTES } else if (common.isActivatedEIP(7702)) { code = await eip7702CodeCheck(runState, code) } const data = getDataSlice(code, codeOffset, dataLength) const memOffsetNum = Number(memOffset) const lengthNum = Number(dataLength) runState.memory.write(memOffsetNum, lengthNum, data) } // For Synthesizer // const [addressPt, memOffsetPt, codeOffsetPt, dataLengthPt] = runState.stackPt.popN(4) if ( addressPt.value !== addressBigInt || memOffsetPt.value !== memOffset || codeOffsetPt.value !== codeOffset || dataLengthPt.value !== dataLength ) { throw new Error(`Synthesizer: EXTCODECOPY: Input data mismatch`) } if (dataLength !== BIGINT_0) { let chunkedLength = Number(dataLength) let accumOffsetShift = 0n while (chunkedLength > 0){ const chunkSize = chunkedLength >= DEFAULT_SOURCE_SIZE ? DEFAULT_SOURCE_SIZE : chunkedLength const dataPt = await prepareEXTCodePt(runState, addressBigInt, codeOffset+accumOffsetShift, BigInt(chunkSize)) runState.memoryPt.write(Number(memOffset+accumOffsetShift), chunkSize, dataPt) chunkedLength -= chunkSize accumOffsetShift += BigInt(chunkSize) } } const _outData = runState.memoryPt.viewMemory(Number(memOffset), Number(dataLength)) const outData = runState.memory.read(Number(memOffset), Number(dataLength)) if (!equalsBytes(_outData, outData)) { throw new Error(`Synthesizer: EXTCODECOPY: Output data mismatch`) } }, ], // 0x3f: EXTCODEHASH [ 0x3f, async function (runState, common) { const addressBigInt = runState.stack.pop() const address = createAddressFromStackBigInt(addressBigInt) // EOF check const code = await runState.stateManager.getCode(address) if (isEOF(code)) { // In legacy code, the target code is treated as to be "EOFBYTES" code // Therefore, push the hash of EOFBYTES to the stack runState.stack.push(bytesToBigInt(EOFHASH)) // For Synthesizer // await synthesizerEnvInf('EXTCODEHASH', runState, addressBigInt) return } else if (common.isActivatedEIP(7702)) { const possibleDelegatedAddress = getEIP7702DelegatedAddress(code) if (possibleDelegatedAddress !== undefined) { const account = await runState.stateManager.getAccount(possibleDelegatedAddress) if (!account || account.isEmpty()) { runState.stack.push(BIGINT_0) // For Synthesizer // await synthesizerEnvInf('EXTCODEHASH', runState, addressBigInt) return } runState.stack.push(BigInt(bytesToHex(account.codeHash))) // For Synthesizer // await synthesizerEnvInf('EXTCODEHASH', runState, addressBigInt) return } else { runState.stack.push(bytesToBigInt(keccak256(code))) // For Synthesizer // await synthesizerEnvInf('EXTCODEHASH', runState, addressBigInt) return } } const account = await runState.stateManager.getAccount(address) if (!account || account.isEmpty()) { runState.stack.push(BIGINT_0) // For Synthesizer // await synthesizerEnvInf('EXTCODEHASH', runState, addressBigInt) return } runState.stack.push(BigInt(bytesToHex(account.codeHash))) // For Synthesizer // await synthesizerEnvInf('EXTCODEHASH', runState, addressBigInt) }, ], // 0x3d: RETURNDATASIZE [ 0x3d, async function (runState) { runState.stack.push(runState.interpreter.getReturnDataSize()) // For Synthesizer // await synthesizerEnvInf('RETURNDATASIZE', runState) }, ], // 0x3e: RETURNDATACOPY [ 0x3e, function (runState) { const [memOffset, returnDataOffset, dataLength] = runState.stack.popN(3) if (dataLength !== BIGINT_0) { const data = getDataSlice( runState.interpreter.getReturnData(), returnDataOffset, dataLength, ) const memOffsetNum = Number(memOffset) const lengthNum = Number(dataLength) runState.memory.write(memOffsetNum, lengthNum, data) } // For Synthesizer // const [memOffsetPt, returnDataOffsetPt, dataLengthPt] = runState.stackPt.popN(3) if ( memOffset !== memOffsetPt.value || returnDataOffset !== returnDataOffsetPt.value || dataLength !== dataLengthPt.value ) { throw new Error(`Synthesizer: 'RETURNDATACOPY': Input data mismatch`) } if (dataLength !== BIGINT_0) { const copiedMemoryPts = copyMemoryRegion( runState, returnDataOffset, dataLength, runState.returnMemoryPts, memOffset, ) for (const entry of copiedMemoryPts) { // the lower index, the older data runState.memoryPt.write(entry.memOffset, entry.containerSize, entry.dataPt) } } const _outData = runState.memoryPt.viewMemory(Number(memOffset), Number(dataLength)) const outData = runState.memory.read(Number(memOffset), Number(dataLength)) if (!equalsBytes(_outData, outData)) { throw new Error(`Synthesizer: RETURNDATACOPY: Output data mismatch`) } }, ], // 0x3a: GASPRICE [ 0x3a, async function (runState) { runState.stack.push(runState.interpreter.getTxGasPrice()) // For Synthesizer // await synthesizerEnvInf('GASPRICE', runState) }, ], // '0x40' range - block operations // 0x40: BLOCKHASH [ 0x40, async function (runState, common) { const number = runState.stack.pop() if (common.isActivatedEIP(7709)) { if (number >= runState.interpreter.getBlockNumber()) { runState.stack.push(BIGINT_0) // For Synthesizer // synthesizerBlkInf('BLOCKHASH', runState, number) return } const diff = runState.interpreter.getBlockNumber() - number // block lookups must be within the original window even if historyStorageAddress's // historyServeWindow is much greater than 256 if (diff > BIGINT_256 || diff <= BIGINT_0) { runState.stack.push(BIGINT_0) // For Synthesizer // synthesizerBlkInf('BLOCKHASH', runState, number) return } const historyAddress = new Address( bigIntToAddressBytes(common.param('historyStorageAddress')), ) const historyServeWindow = common.param('historyServeWindow') const key = setLengthLeft(bigIntToBytes(number % historyServeWindow), 32) if (common.isActivatedEIP(6800)) { const { treeIndex, subIndex } = getVerkleTreeIndicesForStorageSlot(number) // create witnesses and charge gas const statelessGas = runState.env.accessWitness!.touchAddressOnReadAndComputeGas( historyAddress, treeIndex, subIndex, ) runState.interpreter.useGas(statelessGas, `BLOCKHASH`) } const storage = await runState.stateManager.getStorage(historyAddress, key) runState.stack.push(bytesToBigInt(storage)) } else { const diff = runState.interpreter.getBlockNumber() - number // block lookups must be within the past 256 blocks if (diff > BIGINT_256 || diff <= BIGINT_0) { runState.stack.push(BIGINT_0) // For Synthesizer // synthesizerBlkInf('BLOCKHASH', runState, number) return } const block = await runState.blockchain.getBlock(Number(number)) runState.stack.push(bytesToBigInt(block.hash())) } // For Synthesizer // synthesizerBlkInf('BLOCKHASH', runState, number) }, ], // 0x41: COINBASE [ 0x41, function (runState) { runState.stack.push(runState.interpreter.getBlockCoinbase()) // For Synthesizer // synthesizerBlkInf('COINBASE', runState) }, ], // 0x42: TIMESTAMP [ 0x42, function (runState) { runState.stack.push(runState.interpreter.getBlockTimestamp()) // For Synthesizer // synthesizerBlkInf('TIMESTAMP', runState) }, ], // 0x43: NUMBER [ 0x43, function (runState) { runState.stack.push(runState.interpreter.getBlockNumber()) // For Synthesizer // synthesizerBlkInf('NUMBER', runState) }, ], // 0x44: DIFFICULTY (EIP-4399: supplanted as PREVRANDAO) [ 0x44, function (runState, common) { if (common.isActivatedEIP(4399)) { runState.stack.push(runState.interpreter.getBlockPrevRandao()) } else { runState.stack.push(runState.interpreter.getBlockDifficulty()) } // For Synthesizer // synthesizerBlkInf('DIFFICULTY', runState) }, ], // 0x45: GASLIMIT [ 0x45, function (runState) { runState.stack.push(runState.interpreter.getBlockGasLimit()) // For Synthesizer // synthesizerBlkInf('GASLIMIT', runState) }, ], // 0x46: CHAINID [ 0x46, function (runState) { runState.stack.push(runState.interpreter.getChainId()) // For Synthesizer // synthesizerBlkInf('CHAINID', runState) }, ], // 0x47: SELFBALANCE [ 0x47, function (runState) { runState.stack.push(runState.interpreter.getSelfBalance()) // For Synthesizer // synthesizerBlkInf('SELFBALANCE', runState) }, ], // 0x48: BASEFEE [ 0x48, function (runState) { runState.stack.push(runState.interpreter.getBlockBaseFee()) // For Synthesizer // synthesizerBlkInf('BASEFEE', runState) }, ], // 0x49: BLOBHASH [ 0x49, function (runState) { const index = runState.stack.pop() if (runState.env.blobVersionedHashes.length > Number(index)) { runState.stack.push(BigInt(runState.env.blobVersionedHashes[Number(index)])) } else { runState.stack.push(BIGINT_0) } // For Synthesizer // synthesizerBlkInf('BLOBHASH', runState, index) }, ], // 0x4a: BLOBBASEFEE [ 0x4a, function (runState) { runState.stack.push(runState.interpreter.getBlobBaseFee()) // For Synthesizer // synthesizerBlkInf('BLOBBASEFEE', runState) }, ], // 0x50 range - 'storage' and execution // 0x50: POP [ 0x50, function (runState) { runState.stack.pop() // For Synthesizer // runState.stackPt.pop() }, ], // 0x51: MLOAD [ 0x51, function (runState) { const pos = runState.stack.pop() const word = runState.memory.read(Number(pos), 32, true) runState.stack.push(bytesToBigInt(word)) // For Synthesizer // const loadSize = 32 const offsetPt = runState.stackPt.pop() if (pos !== offsetPt.value) { throw new Error(`Synthesizer: MLOAD: Input data mismatch`) } const offsetNum = Number(offsetPt.value) const dataAliasInfos = runState.memoryPt.getDataAlias(offsetNum, loadSize) let mutDataPt if (dataAliasInfos.length === 0 ){ mutDataPt = runState.synthesizer.loadAuxin(BIGINT_0) } else { mutDataPt = runState.synthesizer.placeMemoryToStack(dataAliasInfos) } /** * Consistency validation between EVM execution and pointer tracking system * 1. Memory offset validation: * - pos: Actual memory offset popped from main stack * - offsetNum: Tracked memory offset from pointer stack (stackPt) * 2. Loaded value validation: * - stack.peek(1)[0]: Actual value pushed to stack after memory load * - mutDataPt.value: Expected memory value calculated by pointer tracking system * If these values don't match, it indicates a critical inconsistency * between EVM execution and pointer tracking, requiring immediate error handling */ if (Number(pos) !== offsetNum || runState.stack.peek(1)[0] !== mutDataPt.value) { console.log('******ERROR******') if (Number(pos) !== offsetNum) { console.group() console.log('***Number(pos) !== offsetNum)***') console.log(Number(pos), offsetNum) console.groupEnd() } if (runState.stack.peek(1)[0] !== mutDataPt.value) { console.group() console.log('***runState.stack.peek(1)[0] !== mutDataPt.value***') console.log(runState.stack.peek(1)[0], mutDataPt.value) console.groupEnd() } console.log('runState.stack : ', runState.stack) console.log('mutDataPt : ', mutDataPt) throw new Error(`Synthesizer: MLOAD: Output data mismatch`) } runState.stackPt.push(mutDataPt) }, ], // 0x52: MSTORE [ 0x52, function (runState) { const [offset, word] = runState.stack.popN(2) const buf = setLengthLeft(bigIntToBytes(word), 32) const offsetNum = Number(offset) runState.memory.write(offsetNum, 32, buf) // For Synthesizer // const truncSize = 32 const dataPt = runState.stackPt.peek(2)[1] const memPt = runState.synthesizer.placeMSTORE(dataPt, truncSize) if (truncSize < dataPt.sourceSize) { // Replace dataPt in StackPt with the tracked memPt runState.stackPt.swap(1) runState.stackPt.pop() // Discard the original dataPt runState.stackPt.push(memPt) // Replace dataPt in StackPt with the tracked memPt runState.stackPt.swap(1) } const [offsetPt, newDataPt] = runState.stackPt.popN(2) const _offsetNum = Number(offsetPt.value) if ( _offsetNum !== offsetNum || BigInt.asIntN(256, newDataPt.value) !== BigInt.asIntN(256, word) ) { if (_offsetNum !== offsetNum) { console.log(`_offsetNum !== offsetNum`) console.log(_offsetNum, offsetNum) } if (newDataPt.value !== word) { console.log(`newDataPt.value !== word`) console.log(newDataPt.value, word) console.log('runState.stack : ', runState.stack) console.log('newDataPt : ', newDataPt) console.log('stack', runState.stack) console.log('stackPt : ', runState.stackPt) } throw new Error(`MSTORE: Data mismatch between stackPt and stack`) } runState.memoryPt.write(offsetNum, truncSize, newDataPt) }, ], // 0x53: MSTORE8 [ 0x53, function (runState) { const [offset, byte] = runState.stack.popN(2) const buf = bigIntToBytes(byte & BIGINT_255) const offsetNum = Number(offset) runState.memory.write(offsetNum, 1, buf) // For Synthesizer // const truncSize = 1 // MSTORE8 stores only the lowest byte and discards the higher bytes const dataPt = runState.stackPt.peek(2)[1] // Return the top second pointer from StackPt (refer to stack.ts) // Track data modifications and reflect them in Placements const memPt = runState.synthesizer.placeMSTORE(dataPt, truncSize) if (truncSize < dataPt.sourceSize) { // Replace dataPt in StackPt with the tracked memPt runState.stackPt.swap(1) runState.stackPt.pop() // Discard the original dataPt runState.stackPt.push(memPt) // Replace dataPt in StackPt with the tracked memPt runState.stackPt.swap(1) } // Now perform the usual MSTORE operation const [offsetPt, newDataPt] = runState.stackPt.popN(2) const _offsetNum = Number(offsetPt.value) if (_offsetNum !== offsetNum || newDataPt.value !== bytesToBigInt(buf)) { throw new Error(`MSTORE8: Data mismatch between stackPt and stack`) } runState.memoryPt.write(offsetNum, truncSize, newDataPt) }, ], // 0x54: SLOAD [ 0x54, async function (runState) { const key = runState.stack.pop() const keyBuf = setLengthLeft(bigIntToBytes(key), 32) const value = await runState.interpreter.storageLoad(keyBuf) const valueBigInt = value.length ? bytesToBigInt(value) : BIGINT_0 runState.stack.push(valueBigInt) // For Synthesizer // if (key !== runState.stackPt.pop().value) { throw new Error(`Synthesizer: 'SLOAD': Input data mismatch`) } runState.stackPt.push( runState.synthesizer.loadStorage(runState.env.address.toString(), key, valueBigInt), ) if (runState.stackPt.peek(1)[0].value !== runState.stack.peek(1)[0]) { throw new Error(`Synthesizer: 'SLOAD': Output data mismatch`) } }, ], // 0x55: SSTORE [ 0x55, async function (runState) { const [key, val] = runState.stack.popN(2) const keyBuf = setLengthLeft(bigIntToBytes(key), 32) // NOTE: this should be the shortest representation let value if (val === BIGINT_0) { value = Uint8Array.from([]) } else { value = bigIntToBytes(val) } await runState.interpreter.storageStore(keyBuf, value) // For Synthesizer // const [keyPt, valPt] = runState.stackPt.popN(2) if (key !== keyPt.value || val !== valPt.value) { throw new Error(`Synthesizer: 'SSTORE': Input data mismatch`) } runState.synthesizer.storeStorage(runState.env.address.toString(), key, valPt) }, ], // 0x56: JUMP [ 0x56, function (runState) { const dest = runState.stack.pop() if (dest > runState.interpreter.getCodeSize()) { trap(ERROR.INVALID_JUMP + ' at ' + describeLocation(runState)) } const destNum = Number(dest) if (!jumpIsValid(runState, destNum)) { trap(ERROR.INVALID_JUMP + ' at ' + describeLocation(runState)) } runState.programCounter = destNum // For Synthesizer // runState.stackPt.pop() }, ], // 0x57: JUMPI [ 0x57, function (runState) { const [dest, cond] = runState.stack.popN(2) if (cond !== BIGINT_0) { if (dest > runState.interpreter.getCodeSize()) { trap(ERROR.INVALID_JUMP + ' at ' + describeLocation(runState)) } const destNum = Number(dest) if (!jumpIsValid(runState, destNum)) { trap(ERROR.INVALID_JUMP + ' at ' + describeLocation(runState)) } runState.programCounter = destNum } // For Synthesizer // runState.stackPt.popN(2) // stackPt도 동일하게 pop }, ], // 0x58: PC [ 0x58, function (runState) { runState.stack.push(BigInt(runState.programCounter - 1)) // for opcode not implemented with Synthesizer SynthesizerValidator.validateOpcodeImplemented(0x58, 'PC') }, ], // 0x59: MSIZE [ 0x59, function (runState) { runState.stack.push(runState.memoryWordCount * BIGINT_32) // for opcode not implemented with Synthesizer SynthesizerValidator.validateOpcodeImplemented(0x59, 'MSIZE') }, ], // 0x5a: GAS [ 0x5a, function (runState) { runState.stack.push(runState.interpreter.getGasLeft()) // For Synthesizer. Temporary handling. Will be updated. const dataPt = runState.synthesizer.loadAuxin(runState.interpreter.getGasLeft()) runState.stackPt.push(dataPt) if (runState.stackPt.peek(1)[0].value !== runState.stack.peek(1)[0]) { throw new Error(`Synthesizer: 'GAS': Output data mismatch`) } }, ], // 0x5b: JUMPDEST [0x5b, function () {}], // 0x5c: TLOAD (EIP 1153) [ 0x5c, function (runState) { const key = runState.stack.pop() const keyBuf = setLengthLeft(bigIntToBytes(key), 32) const value = runState.interpreter.transientStorageLoad(keyBuf) const valueBN = value.length ? bytesToBigInt(value) : BIGINT_0 runState.stack.push(valueBN) // For Synthesizer // if (key !== runState.stackPt.pop().value) { throw new Error(`Synthesizer: 'TLOAD': Input data mismatch`) } let dataPt = runState.synthesizer.TStoragePt.get(runState.env.address.toString())?.get(key) if (dataPt === undefined) { dataPt = runState.synthesizer.loadAuxin(BIGINT_0) } runState.stackPt.push(dataPt) if (runState.stackPt.peek(1)[0].value !== runState.stack.peek(1)[0]) { throw new Error(`Synthesizer: 'TLOAD': Output data mismatch`) } }, ], // 0x5d: TSTORE (EIP 1153) [ 0x5d, function (runState) { // TSTORE if (runState.interpreter.isStatic()) { trap(ERROR.STATIC_STATE_CHANGE) } const [key, val] = runState.stack.popN(2) const keyBuf = setLengthLeft(bigIntToBytes(key), 32) // NOTE: this should be the shortest representation let value if (val === BIGINT_0) { value = Uint8Array.from([]) } else { value = bigIntToBytes(val) } runState.interpreter.transientStorageStore(keyBuf, value) // For Synthesizer // const [keyPt, valPt] = runState.stackPt.popN(2) if (key !== keyPt.value || val !== valPt.value) { throw new Error(`Synthesizer: 'TSTORE': Input data mismatch`) } const TStoragePt = runState.synthesizer.TStoragePt const thisAddress = runState.env.address.toString() const entry = TStoragePt.get(thisAddress) ?? new Map() entry.set(key, valPt) TStoragePt.set(thisAddress, entry) }, ], // 0x5e: MCOPY (5656) [ 0x5e, function (runState) { const [dst, src, length] = runState.stack.popN(3) const data = runState.memory.read(Number(src), Number(length), true) runState.memory.write(Number(dst), Number(length), data) // For Synthesizer // const [dstPt, srcPt, lengthPt] = runState.stackPt.popN(3) if (dst !== dstPt.value || src !== srcPt.value || length !== lengthPt.value) { throw new Error(`Synthesizer: 'MCOPY': Input data mismatch`) } const copiedMemoryPts = copyMemoryRegion(runState, src, length, undefined, dst) for (const entry of copiedMemoryPts) { // the lower index, the older data runState.memoryPt.write(entry.memOffset, entry.containerSize, entry.dataPt) } const _outData = runState.memoryPt.viewMemory(Number(dst), Number(length)) const outData = runState.memory.read(Number(dst), Number(length)) if (!equalsBytes(_outData, outData)) { throw new Error(`Synthesizer: MCOPY: Output data mismatch`) } }, ], // 0x5f: PUSH0 [ 0x5f, function (runState) { runState.stack.push(BIGINT_0) // For Synthesizer // const dataPt = runState.synthesizer.loadPUSH( runState.env.address.toString(), runState.programCounterPrev, BIGINT_0, 1, ) runState.stackPt.push(dataPt) if (runState.stackPt.peek(1)[0].value !== runState.stack.peek(1)[0]) { throw new Error(`Synthesizer: PUSH0: Output data mismatch`) } }, ], // 0x60: PUSH [ 0x60, function (runState, common) { const numToPush = runState.opCode - 0x5f if ( runState.programCounter + numToPush > runState.code.length && common.isActivatedEIP(3540) ) { trap(ERROR.OUT_OF_RANGE) } if (common.isActivatedEIP(6800) && runState.env.chargeCodeAccesses === true) { const contract = runState.interpreter.getAddress() const startOffset = Math.min(runState.code.length, runState.programCounter + 1) const endOffset = Math.min(runState.code.length, startOffset + numToPush - 1) const statelessGas = runState.env.accessWitness!.touchCodeChunksRangeOnReadAndChargeGas( contract, startOffset, endOffset, ) runState.interpreter.useGas(statelessGas, `PUSH`) } if (!runState.shouldDoJumpAnalysis) { runState.stack.push(runState.cachedPushes[runState.programCounter]) runState.programCounter += numToPush } else { const loaded = bytesToBigInt( runState.code.subarray(runState.programCounter, runState.programCounter + numToPush), ) runState.programCounter += numToPush runState.stack.push(loaded) } // For Synthesizer const value = runState.stack.peek(1)[0] const dataPt = runState.synthesizer.loadPUSH( runState.env.address.toString(), runState.programCounterPrev, value, numToPush, ) runState.stackPt.push(dataPt) if (runState.stackPt.peek(1)[0].value !== runState.stack.peek(1)[0]) { throw new Error(`Synthesizer: PUSH${numToPush}: Output data mismatch`) } }, ], // 0x80: DUP [ 0x80, function (runState) { const stackPos = runState.opCode - 0x7f runState.stack.dup(stackPos) // // For Synthesizer runState.stackPt.dup(stackPos) }, ], // 0x90: SWAP [ 0x90, function (runState) { const stackPos = runState.opCode - 0x8f runState.stack.swap(stackPos) // For Synthesizer // runState.stackPt.swap(stackPos) }, ], // 0xa0: LOG [ 0xa0, function (runState) { const [memOffset, memLength] = runState.stack.popN(2) const topicsCount = runState.opCode - 0xa0 const topics = runState.stack.popN(topicsCount) const topicsBuf = topics.map(function (a: bigint) { return setLengthLeft(bigIntToBytes(a), 32) }) let mem = new Uint8Array(0) if (memLength !== BIGINT_0) { mem = runState.memory.read(Number(memOffset), Number(memLength)) } runState.interpreter.log(mem, topicsCount, topicsBuf) // For Synthesizer // const [memOffsetPt, memLengthPt] = runState.stackPt.popN(2) const topicPts = runState.stackPt.popN(topicsCount) if (memOffsetPt.value !== memOffset || memLengthPt.value !== memLength) { throw new Error(`Synthesizer: 'LOG': Input data mismatch`) } for (let i = 0; i < topicsCount; i++) { if (topicPts[i].value !== topics[i]) { throw new Error(`Synthesizer: 'LOG': Input data mismatch`) } } const dataAlias = runState.memoryPt.getDataAlias(Number(memOffset), Number(memLength)) const dataPt = runState.synthesizer.placeMemoryToStack(dataAlias) runState.synthesizer.storeLog(dataPt, topicPts) }, ], // 0xd0: DATALOAD [ 0xd0, function (runState, _common) { if (runState.env.eof === undefined) { // Opcode not available in legacy contracts trap(ERROR.INVALID_OPCODE) } const pos = runState.stack.pop() if (pos > runState.env.eof!.container.body.dataSection.length) { runState.stack.push(BIGINT_0) return } const i = Number(pos) let loaded = runState.env.eof!.container.body.dataSection.subarray(i, i + 32) loaded = loaded.length ? loaded : Uint8Array.from([0]) let r = bytesToBigInt(loaded) // Pad the loaded length with 0 bytes in case it is smaller than 32 if (loaded.length < 32) { r = r << (BIGINT_8 * BigInt(32 - loaded.length)) } runState.stack.push(r) // for opcode not implemented with Synthesizer SynthesizerValidator.validateOpcodeImplemented(0xd0, 'DATALOAD') }, ], // 0xd1: DATALOADN [ 0xd1, function (runState, _