@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
text/typescript
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, _