UNPKG

@tokamak-zk-evm/synthesizer

Version:

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

232 lines 7.64 kB
import { Hardfork } from '@synthesizer-libs/common'; import { BIGINT_0, BIGINT_1, BIGINT_160, BIGINT_2, BIGINT_32, BIGINT_64, BIGINT_NEG1, bytesToHex, createAddressFromBigInt, equalsBytes, setLengthLeft, setLengthRight, } from "@synthesizer-libs/util"; import { keccak256 } from 'ethereum-cryptography/keccak.js'; import { EvmError } from '../exceptions.js'; import { copyMemoryRegion } from '../tokamak/pointers/index.js'; const MASK_160 = (BIGINT_1 << BIGINT_160) - BIGINT_1; /** * Create an address from a stack item (256 bit integer). * This wrapper ensures that the value is masked to 160 bits. * @param value 160-bit integer */ export function createAddressFromStackBigInt(value) { const maskedValue = value & MASK_160; return createAddressFromBigInt(maskedValue); } /** * Proxy function for @synthesizer-libs/util's setLengthLeft, except it returns a zero * length Uint8Array in case the Uint8Array is full of zeros. * @param value Uint8Array which we want to pad */ export function setLengthLeftStorage(value) { if (equalsBytes(value, new Uint8Array(value.length))) { // return the empty Uint8Array (the value is zero) return new Uint8Array(0); } else { return setLengthLeft(value, 32); } } /** * Wraps error message as EvmError */ export function trap(err) { // TODO: facilitate extra data along with errors throw new EvmError(err); } /** * Error message helper - generates location string */ export function describeLocation(runState) { const keccakFunction = runState.interpreter._evm.common.customCrypto.keccak256 ?? keccak256; const hash = bytesToHex(keccakFunction(runState.interpreter.getCode())); const address = runState.interpreter.getAddress().toString(); const pc = runState.programCounter - 1; return `${hash}/${address}:${pc}`; } /** * Find Ceil(a / b) * * @param {bigint} a * @param {bigint} b * @return {bigint} */ export function divCeil(a, b) { const div = a / b; const modulus = mod(a, b); // Fast case - exact division if (modulus === BIGINT_0) return div; // Round up return div < BIGINT_0 ? div - BIGINT_1 : div + BIGINT_1; } /** * Returns an overflow-safe slice of an array. It right-pads * the data with zeros to `length`. */ export function getDataSlice(data, offset, length) { const len = BigInt(data.length); if (offset > len) { offset = len; } let end = offset + length; if (end > len) { end = len; } data = data.subarray(Number(offset), Number(end)); // Right-pad with zeros to fill dataLength bytes data = setLengthRight(data, Number(length)); return data; } /** * Get full opcode name from its name and code. * * @param code Integer code of opcode. * @param name Short name of the opcode. * @returns Full opcode name */ export function getFullname(code, name) { switch (name) { case 'LOG': name += code - 0xa0; break; case 'PUSH': name += code - 0x5f; break; case 'DUP': name += code - 0x7f; break; case 'SWAP': name += code - 0x8f; break; } return name; } /** * Checks if a jump is valid given a destination (defined as a 1 in the validJumps array) */ export function jumpIsValid(runState, dest) { return runState.validJumps[dest] === 1; } /** * Returns an overflow-safe slice of an array. It right-pads * the data with zeros to `length`. * @param gasLimit requested gas Limit * @param gasLeft current gas left * @param runState the current runState * @param common the common */ export function maxCallGas(gasLimit, gasLeft, runState, common) { if (common.gteHardfork(Hardfork.TangerineWhistle)) { const gasAllowed = gasLeft - gasLeft / BIGINT_64; return gasLimit > gasAllowed ? gasAllowed : gasLimit; } else { return gasLimit; } } /** * Subtracts the amount needed for memory usage from `runState.gasLeft` */ export function subMemUsage(runState, offset, length, common) { // YP (225): access with zero length will not extend the memory if (length === BIGINT_0) return BIGINT_0; const newMemoryWordCount = divCeil(offset + length, BIGINT_32); if (newMemoryWordCount <= runState.memoryWordCount) return BIGINT_0; const words = newMemoryWordCount; const fee = common.param('memoryGas'); const quadCoefficient = common.param('quadCoefficientDivGas'); // words * 3 + words ^2 / 512 let cost = words * fee + (words * words) / quadCoefficient; if (cost > runState.highestMemCost) { const currentHighestMemCost = runState.highestMemCost; runState.highestMemCost = cost; cost -= currentHighestMemCost; } runState.memoryWordCount = newMemoryWordCount; return cost; } /** * Writes data returned by evm.call* methods to memory */ export function writeCallOutput(runState, outOffset, outLength) { const returnData = runState.interpreter.getReturnData(); if (returnData.length > 0) { const memOffset = Number(outOffset); let dataLength = Number(outLength); if (BigInt(returnData.length) < dataLength) { dataLength = returnData.length; } const data = getDataSlice(returnData, BIGINT_0, BigInt(dataLength)); runState.memory.extend(memOffset, dataLength); runState.memory.write(memOffset, dataLength, data); } // For synthesizer const returnMemoryPts = runState.interpreter.getReturnMemoryPts(); if (returnMemoryPts.length > 0) { const acceptMemoryPts = copyMemoryRegion(runState, BIGINT_0, outLength, returnMemoryPts); for (const entry of acceptMemoryPts) { // the lower index, the older data runState.memoryPt.write(Number(outOffset) + entry.memOffset, entry.containerSize, entry.dataPt); } } } /** * The first rule set of SSTORE rules, which are the rules pre-Constantinople and in Petersburg */ export function updateSstoreGas(runState, currentStorage, value, common) { if ((value.length === 0 && currentStorage.length === 0) || (value.length > 0 && currentStorage.length > 0)) { const gas = common.param('sstoreResetGas'); return gas; } else if (value.length === 0 && currentStorage.length > 0) { const gas = common.param('sstoreResetGas'); runState.interpreter.refundGas(common.param('sstoreRefundGas'), 'updateSstoreGas'); return gas; } else { /* The situations checked above are: -> Value/Slot are both 0 -> Value/Slot are both nonzero -> Value is zero, but slot is nonzero Thus, the remaining case is where value is nonzero, but slot is zero, which is this clause */ return common.param('sstoreSetGas'); } } export function mod(a, b) { let r = a % b; if (r < BIGINT_0) { r = b + r; } return r; } export function fromTwos(a) { return BigInt.asIntN(256, a); } export function toTwos(a) { return BigInt.asUintN(256, a); } export function abs(a) { if (a > 0) { return a; } return a * BIGINT_NEG1; } const N = BigInt(115792089237316195423570985008687907853269984665640564039457584007913129639936); export function exponentiation(bas, exp) { let t = BIGINT_1; while (exp > BIGINT_0) { if (exp % BIGINT_2 !== BIGINT_0) { t = (t * bas) % N; } bas = (bas * bas) % N; exp = exp / BIGINT_2; } return t; } //# sourceMappingURL=util.js.map