@polygon-hermez/vm
Version:
An Ethereum VM implementation
255 lines • 8.48 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateSstoreGas = exports.writeCallOutput = exports.subMemUsage = exports.maxCallGas = exports.jumpSubIsValid = exports.jumpIsValid = exports.getFullname = exports.getDataSlice = exports.short = exports.divCeil = exports.describeLocation = exports.addressToBuffer = exports.trap = exports.setLengthLeftStorage = void 0;
const ethereumjs_util_1 = require("ethereumjs-util");
const exceptions_1 = require("./../../exceptions");
const MASK_160 = new ethereumjs_util_1.BN(1).shln(160).subn(1);
/**
* Proxy function for ethereumjs-util's setLengthLeft, except it returns a zero
*
* length buffer in case the buffer is full of zeros.
* @param {Buffer} value Buffer which we want to pad
*/
function setLengthLeftStorage(value) {
if (value.equals(Buffer.alloc(value.length, 0))) {
// return the empty buffer (the value is zero)
return Buffer.alloc(0);
}
else {
return (0, ethereumjs_util_1.setLengthLeft)(value, 32);
}
}
exports.setLengthLeftStorage = setLengthLeftStorage;
/**
* Wraps error message as VMError
*
* @param {string} err
*/
function trap(err) {
// TODO: facilitate extra data along with errors
throw new exceptions_1.VmError(err);
}
exports.trap = trap;
/**
* Converts BN address (they're stored like this on the stack) to buffer address
*
* @param {BN} address
* @return {Buffer}
*/
function addressToBuffer(address) {
if (Buffer.isBuffer(address))
return address;
return address.and(MASK_160).toArrayLike(Buffer, 'be', 20);
}
exports.addressToBuffer = addressToBuffer;
/**
* Error message helper - generates location string
*
* @param {RunState} runState
* @return {string}
*/
function describeLocation(runState) {
const hash = (0, ethereumjs_util_1.keccak256)(runState.eei.getCode()).toString('hex');
const address = runState.eei.getAddress().buf.toString('hex');
const pc = runState.programCounter - 1;
return `${hash}/${address}:${pc}`;
}
exports.describeLocation = describeLocation;
/**
* Find Ceil(a / b)
*
* @param {BN} a
* @param {BN} b
* @return {BN}
*/
function divCeil(a, b) {
const div = a.div(b);
const mod = a.mod(b);
// Fast case - exact division
if (mod.isZero())
return div;
// Round up
return div.isNeg() ? div.isubn(1) : div.iaddn(1);
}
exports.divCeil = divCeil;
function short(buffer) {
const MAX_LENGTH = 50;
const bufferStr = buffer.toString('hex');
if (bufferStr.length <= MAX_LENGTH) {
return bufferStr;
}
return bufferStr.slice(0, MAX_LENGTH) + '...';
}
exports.short = short;
/**
/**
* Returns an overflow-safe slice of an array. It right-pads
* the data with zeros to `length`.
*
* @param {BN} offset
* @param {BN} length
* @param {Buffer} data
* @returns {Buffer}
*/
function getDataSlice(data, offset, length) {
const len = new ethereumjs_util_1.BN(data.length);
if (offset.gt(len)) {
offset = len;
}
let end = offset.add(length);
if (end.gt(len)) {
end = len;
}
data = data.slice(offset.toNumber(), end.toNumber());
// Right-pad with zeros to fill dataLength bytes
data = (0, ethereumjs_util_1.setLengthRight)(data, length.toNumber());
return data;
}
exports.getDataSlice = getDataSlice;
/**
* Get full opcode name from its name and code.
*
* @param code {number} Integer code of opcode.
* @param name {string} Short name of the opcode.
* @returns {string} Full opcode name
*/
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;
}
exports.getFullname = getFullname;
/**
* Checks if a jump is valid given a destination (defined as a 1 in the validJumps array)
*
* @param {RunState} runState
* @param {number} dest
* @return {boolean}
*/
function jumpIsValid(runState, dest) {
return runState.validJumps[dest] === 1;
}
exports.jumpIsValid = jumpIsValid;
/**
* Checks if a jumpsub is valid given a destination (defined as a 2 in the validJumps array)
*
* @param {RunState} runState
* @param {number} dest
* @return {boolean}
*/
function jumpSubIsValid(runState, dest) {
return runState.validJumps[dest] === 2;
}
exports.jumpSubIsValid = jumpSubIsValid;
/**
* Returns an overflow-safe slice of an array. It right-pads
*
* the data with zeros to `length`.
* @param {BN} gasLimit - requested gas Limit
* @param {BN} gasLeft - current gas left
* @param {RunState} runState - the current runState
* @param {Common} common - the common
*/
function maxCallGas(gasLimit, gasLeft, runState, common) {
const isTangerineWhistleOrLater = common.gteHardfork('tangerineWhistle');
if (isTangerineWhistleOrLater) {
const gasAllowed = gasLeft.sub(gasLeft.divn(64));
return gasLimit.gt(gasAllowed) ? gasAllowed : gasLimit;
}
else {
return gasLimit;
}
}
exports.maxCallGas = maxCallGas;
/**
* Subtracts the amount needed for memory usage from `runState.gasLeft`
*
* @method subMemUsage
* @param {Object} runState
* @param {BN} offset
* @param {BN} length
*/
function subMemUsage(runState, offset, length, common) {
// YP (225): access with zero length will not extend the memory
if (length.isZero())
return new ethereumjs_util_1.BN(0);
const newMemoryWordCount = divCeil(offset.add(length), new ethereumjs_util_1.BN(32));
if (newMemoryWordCount.lte(runState.memoryWordCount))
return new ethereumjs_util_1.BN(0);
const words = newMemoryWordCount;
const fee = new ethereumjs_util_1.BN(common.param('gasPrices', 'memory'));
const quadCoeff = new ethereumjs_util_1.BN(common.param('gasPrices', 'quadCoeffDiv'));
// words * 3 + words ^2 / 512
const cost = words.mul(fee).add(words.mul(words).div(quadCoeff));
if (cost.gt(runState.highestMemCost)) {
const currentHighestMemCost = runState.highestMemCost;
runState.highestMemCost = cost.clone();
cost.isub(currentHighestMemCost);
}
runState.memoryWordCount = newMemoryWordCount;
return cost;
}
exports.subMemUsage = subMemUsage;
/**
* Writes data returned by eei.call* methods to memory
*
* @param {RunState} runState
* @param {BN} outOffset
* @param {BN} outLength
*/
function writeCallOutput(runState, outOffset, outLength) {
const returnData = runState.eei.getReturnData();
if (returnData.length > 0) {
const memOffset = outOffset.toNumber();
let dataLength = outLength.toNumber();
if (returnData.length < dataLength) {
dataLength = returnData.length;
}
const data = getDataSlice(returnData, new ethereumjs_util_1.BN(0), new ethereumjs_util_1.BN(dataLength));
runState.memory.extend(memOffset, dataLength);
runState.memory.write(memOffset, dataLength, data);
}
}
exports.writeCallOutput = writeCallOutput;
/** The first rule set of SSTORE rules, which are the rules pre-Constantinople and in Petersburg
* @param {RunState} runState
* @param {Buffer} currentStorage
* @param {Buffer} value
* @param {Buffer} keyBuf
*/
function updateSstoreGas(runState, currentStorage, value, common) {
if ((value.length === 0 && currentStorage.length === 0) ||
(value.length > 0 && currentStorage.length > 0)) {
const gas = new ethereumjs_util_1.BN(common.param('gasPrices', 'sstoreReset'));
return gas;
}
else if (value.length === 0 && currentStorage.length > 0) {
const gas = new ethereumjs_util_1.BN(common.param('gasPrices', 'sstoreReset'));
runState.eei.refundGas(new ethereumjs_util_1.BN(common.param('gasPrices', 'sstoreRefund')), '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 new ethereumjs_util_1.BN(common.param('gasPrices', 'sstoreSet'));
}
}
exports.updateSstoreGas = updateSstoreGas;
//# sourceMappingURL=util.js.map