2020-06-17 12:08:29 -07:00

102 lines
4.6 KiB
TypeScript

import { constants, TraceInfo } from '@0x/sol-tracing-utils';
import { logUtils } from '@0x/utils';
import { OpCode } from 'ethereum-types';
import { stripHexPrefix } from 'ethereumjs-util';
import * as _ from 'lodash';
const ZERO_BYTE_CALL_DATA_COST = 4;
const NON_ZERO_BYTE_CALL_DATA_COST = 16;
const WORD_SIZE = 32;
const G_MEMORY = 3;
const G_QUAD_COEF = 512;
const HEX_BASE = 16;
const G_COPY = 3;
export const costUtils = {
reportCallDataCost(traceInfo: TraceInfo): number {
if (traceInfo.dataIfExists === undefined) {
// No call data to report
return 0;
}
const callData = traceInfo.dataIfExists;
const callDataBuf = Buffer.from(stripHexPrefix(callData), 'hex');
const { true: zeroBytesCountIfExist, false: nonZeroBytesCountIfExist } = _.countBy(
callDataBuf,
byte => byte === 0,
);
const zeroBytesCost = (zeroBytesCountIfExist || 0) * ZERO_BYTE_CALL_DATA_COST;
const nonZeroBytesCost = (nonZeroBytesCountIfExist || 0) * NON_ZERO_BYTE_CALL_DATA_COST;
const callDataCost = zeroBytesCost + nonZeroBytesCost;
logUtils.header('Call data breakdown', '-');
logUtils.table({
'call data size (bytes)': callDataBuf.byteLength,
callDataCost,
zeroBytesCost,
nonZeroBytesCost,
zeroBytesCountIfExist,
nonZeroBytesCountIfExist,
});
return callDataCost;
},
reportMemoryCost(traceInfo: TraceInfo): number {
const structLogs = traceInfo.trace.structLogs;
const MEMORY_OPCODES = [OpCode.MLoad, OpCode.MStore, OpCode.MStore8];
const CALL_DATA_OPCODES = [OpCode.CallDataCopy];
const memoryLogs = _.filter(structLogs, structLog =>
_.includes([...MEMORY_OPCODES, ...CALL_DATA_OPCODES], structLog.op),
);
const memoryLocationsAccessed = _.map(memoryLogs, structLog => {
if (_.includes(CALL_DATA_OPCODES, structLog.op)) {
const memoryOffsetStackOffset = constants.opCodeToParamToStackOffset[structLog.op as any].memoryOffset;
const lengthStackOffset = constants.opCodeToParamToStackOffset[structLog.op as any].length;
const memOffset = parseInt(
structLog.stack[structLog.stack.length - memoryOffsetStackOffset - 1],
HEX_BASE,
);
const length = parseInt(structLog.stack[structLog.stack.length - lengthStackOffset - 1], HEX_BASE);
return memOffset + length;
} else {
const memoryLocationStackOffset = constants.opCodeToParamToStackOffset[structLog.op].offset;
return parseInt(structLog.stack[structLog.stack.length - memoryLocationStackOffset - 1], HEX_BASE);
}
});
const highestMemoryLocationAccessed = _.max(memoryLocationsAccessed);
return costUtils._printMemoryCost(highestMemoryLocationAccessed);
},
reportCopyingCost(traceInfo: TraceInfo): number {
const structLogs = traceInfo.trace.structLogs;
const COPY_OPCODES = [OpCode.CallDataCopy];
const copyLogs = _.filter(structLogs, structLog => _.includes(COPY_OPCODES, structLog.op));
const copyCosts = _.map(copyLogs, structLog => {
const lengthStackOffset = constants.opCodeToParamToStackOffset[structLog.op as any].length;
const length = parseInt(structLog.stack[structLog.stack.length - lengthStackOffset - 1], HEX_BASE);
return Math.ceil(length / WORD_SIZE) * G_COPY;
});
return _.sum(copyCosts);
},
reportOpcodesCost(traceInfo: TraceInfo): number {
const structLogs = traceInfo.trace.structLogs;
const gasCosts = _.map(structLogs, structLog => structLog.gasCost);
const gasCost = _.sum(gasCosts);
return gasCost;
},
_printMemoryCost(highestMemoryLocationAccessed?: number): number {
if (highestMemoryLocationAccessed === undefined) {
return 0;
}
const memoryWordsUsed = Math.ceil((highestMemoryLocationAccessed + WORD_SIZE) / WORD_SIZE);
const linearMemoryCost = G_MEMORY * memoryWordsUsed;
const quadraticMemoryCost = Math.floor((memoryWordsUsed * memoryWordsUsed) / G_QUAD_COEF);
const memoryCost = linearMemoryCost + quadraticMemoryCost;
logUtils.header('Memory breakdown', '-');
logUtils.table({
'memoryCost = linearMemoryCost + quadraticMemoryCost': memoryCost,
linearMemoryCost,
quadraticMemoryCost,
highestMemoryLocationAccessed,
memoryWordsUsed,
});
return memoryCost;
},
};