@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
JavaScript
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