UNPKG

ewasm-jsvm

Version:
1,211 lines (1,180 loc) 50.5 kB
const BN = require('bn.js'); const { hexToUint8Array, toBN, BN2hex, BN2uint8arr, keccak256, } = require('./utils.js'); const evmgas = require('./evmGasPrices'); const { ERROR } = require('./constants.js'); const {getPrice} = evmgas; const precompiles = { '0000000000000000000000000000000000000001': 'ecrecover', '0000000000000000000000000000000000000002': 'sha256', '0000000000000000000000000000000000000003': 'ripemd160', '0000000000000000000000000000000000000004': 'identity', '0000000000000000000000000000000000000005': 'modexp', '0000000000000000000000000000000000000006': 'ecadd', '0000000000000000000000000000000000000007': 'ecmul', '0000000000000000000000000000000000000008': 'ecpairing', '0000000000000000000000000000000000000009': 'blake2f', } const initializeImports = ( vmcore, txObj, internalCallWrap, asyncResourceWrap, getMemory, _getCache, finishAction, revertAction, logger, ) => { const jsvm_env = vmcore.call(txObj, internalCallWrap, asyncResourceWrap, getMemory, _getCache); const api = {ethereum: {}}; const getCache = jsvm_env.getCache; api.ethereum = { storeMemory: function (offset, bytes, {stack, position}) { const value = BN2uint8arr(bytes); const { baseFee, addl, highestMemCost, memoryWordCount } = getPrice('mstore', { offset, length: toBN(value.length), memWordCount: jsvm_env.memWordCount(), highestMemCost: jsvm_env.highestMemCost(), }); jsvm_env.useGas(baseFee); if (addl) jsvm_env.useGas(addl); if (highestMemCost) jsvm_env.setHighestMemCost(highestMemCost); if (memoryWordCount) jsvm_env.setMemWordCount(memoryWordCount); jsvm_env.storeMemory(value, offset); const changed = {memory: [offset.toNumber(), value, 1]} logger.debug('MSTORE', [bytes, offset], [], getCache(), stack, changed, position, baseFee, addl); return; }, storeMemory8: function (offset, bytes, {stack, position}) { const gasCost = getPrice('mstore8'); jsvm_env.useGas(gasCost); const value = BN2uint8arr(bytes); jsvm_env.storeMemory8(value, offset); const changed = {memory: [offset.toNumber(), value, 1]} logger.debug('MSTORE8', [bytes, offset], [], getCache(), stack, changed, position, gasCost); return; }, loadMemory: function (offset, {stack, position}) { const gasCost = getPrice('mload'); jsvm_env.useGas(gasCost); const result = toBN(jsvm_env.loadMemory(offset)); stack.push(result); const changed = {memory: [offset.toNumber(), BN2hex(result), 0]} logger.debug('MLOAD', [offset], [result], getCache(), stack, changed, position, gasCost); return {stack, position}; }, useGas: function (amount) { jsvm_env.useGas(amount); logger.debug('USEGAS', [amount], [], getCache(), stack, undefined, position, 0); return; }, getAddress: function ({stack, position}) { const gasCost = getPrice('address'); jsvm_env.useGas(gasCost); const address = toBN(jsvm_env.getAddress()); stack.push(address); logger.debug('ADDRESS', [], [address], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, // result is u128 getExternalBalance: function (_address, {stack, position}){ const {baseFee, addl} = getPrice('balance'); jsvm_env.useGas(baseFee); jsvm_env.useGas(addl); const address = BN2uint8arr(_address); const balance = toBN(jsvm_env.getExternalBalance(address)); stack.push(balance); logger.debug('BALANCE', [_address], [balance], getCache(), stack, undefined, position, baseFee, addl); return {stack, position}; }, // result i32 Returns 0 on success and 1 on failure getBlockHash: function (number, {stack, position}) { const gasCost = getPrice('blockhash'); jsvm_env.useGas(gasCost); const hash = toBN(jsvm_env.getBlockHash(number)); stack.push(hash); logger.debug('BLOCKHASH', [number], [hash], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, // result i32 Returns 0 on success, 1 on failure and 2 on revert call: function ( gas_limit, address, // the memory offset to load the address from (address) value, dataOffset, dataLength, outputOffset, outputLength, {stack, position} ) { const {baseFee, addl} = getPrice('call', {value}); jsvm_env.useGas(baseFee); jsvm_env.useGas(addl); const result = toBN(jsvm_env.call( gas_limit, BN2uint8arr(address), BN2uint8arr(value), dataOffset, dataLength, outputOffset, outputLength, )); stack.push(result); logger.debug('CALL', [gas_limit, address, value, dataOffset, dataLength, outputOffset, outputLength,], [result], getCache(), stack, undefined, position, baseFee, addl); return {stack, position}; }, callDataCopy: function (resultOffset, dataOffset, length, {stack, position}) { const { baseFee, addl, highestMemCost, memoryWordCount } = getPrice('calldatacopy', { offset: resultOffset, length: toBN(length), memWordCount: jsvm_env.memWordCount(), highestMemCost: jsvm_env.highestMemCost(), }); jsvm_env.useGas(baseFee); if (addl) jsvm_env.useGas(addl); if (highestMemCost) jsvm_env.setHighestMemCost(highestMemCost); if (memoryWordCount) jsvm_env.setMemWordCount(memoryWordCount); const result = jsvm_env.callDataCopy(resultOffset, dataOffset, length); const changed = {memory: [resultOffset.toNumber(), result, 1]}; logger.debug('CALLDATACOPY', [resultOffset, dataOffset, length], [result], getCache(), stack, changed, position, baseFee, addl); return; }, getCallDataSize: function ({stack, position}) { const gasCost = getPrice('calldatasize'); jsvm_env.useGas(gasCost); const result = toBN(jsvm_env.getCallDataSize()); stack.push(result); logger.debug('CALLDATASIZE', [], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, callDataLoad: function(dataOffset, {stack, position}) { const gasCost = getPrice('calldataload'); jsvm_env.useGas(gasCost); const result = toBN(jsvm_env.callDataLoad(dataOffset)); stack.push(result); const changed = {memory: [dataOffset.toNumber(), result, 0]}; logger.debug('CALLDATALOAD', [dataOffset], [result], getCache(), stack, changed, position, gasCost); return {stack, position}; }, // result i32 Returns 0 on success, 1 on failure and 2 on revert callCode: function ( gas_limit, address, value, dataOffset, dataLength, outputOffset, outputLength, {stack, position} ) { const {baseFee, addl} = getPrice('callcode', {value}); jsvm_env.useGas(baseFee); jsvm_env.useGas(addl); const result = toBN(jsvm_env.callCode( gas_limit, BN2uint8arr(address), BN2uint8arr(value), dataOffset, dataLength, outputOffset, outputLength )); stack.push(result); logger.debug('CALLCODE', [gas_limit_i64, addressOffset_i32ptr_address, valueOffset_i32ptr_u128, dataOffset_i32ptr_bytes, dataLength_i32], [result], getCache(), stack, undefined, position, baseFee, addl); return {stack, position}; }, // result i32 Returns 0 on success, 1 on failure and 2 on revert callDelegate: function ( gas_limit_i64, address, dataOffset, dataLength, outputOffset, outputLength, {stack, position} ) { const {baseFee} = getPrice('delegatecall'); jsvm_env.useGas(baseFee); const result = toBN(jsvm_env.callDelegate( gas_limit_i64, BN2uint8arr(address), dataOffset, dataLength, outputOffset, outputLength )); stack.push(result); logger.debug('DELEGATECALL', [ gas_limit_i64, address, dataOffset, dataLength, outputOffset, outputLength], [result], getCache(), stack, undefined, position, baseFee, ); return {stack, position}; }, // result Returns 0 on success, 1 on failure and 2 on revert callStatic: function ( gas_limit_i64, address, dataOffset, dataLength, outputOffset, outputLength, {stack, position} ) { const {baseFee} = getPrice('staticcall'); jsvm_env.useGas(baseFee); // Even for precompiles we now pay staticcall gas const addressHex = address.toString(16).padStart(40, '0'); if (precompiles[addressHex]) return api.ethereum[precompiles[addressHex]]( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength, {stack, position}, ); const result = toBN(jsvm_env.callStatic( gas_limit_i64, BN2uint8arr(address), dataOffset, dataLength, outputOffset, outputLength )); stack.push(result); logger.debug('STATICCALL', [ gas_limit_i64, address, dataOffset, dataLength, outputOffset, outputLength], [result], getCache(), stack, undefined, position, baseFee); return {stack, position}; }, storageStore: function (pathOffset, value, {stack, position}) { const key = BN2uint8arr(pathOffset); // TODO correct original value after EIP 2200 const origValue = toBN(jsvm_env.storageLoadOriginal(key)); const currentValue = toBN(jsvm_env.storageLoad(key)); const count = jsvm_env.storageRecords.write(key); const gasLeft = toBN(jsvm_env.getGasLeft()); const {baseFee, addl, refund} = getPrice('sstore', {count, value, currentValue, origValue, gasLeft}); jsvm_env.useGas(baseFee); if (addl) jsvm_env.useGas(addl); if (refund) jsvm_env.refundGas(refund); jsvm_env.storageStore(key, BN2uint8arr(value)); const changed = {storage: [BN2hex(pathOffset), BN2hex(value), 1]} logger.debug('SSTORE', [pathOffset, value], [], getCache(), stack, changed, position, baseFee, addl, refund); return; }, storageLoad: function (pathOffset, {stack, position}) { const key = BN2uint8arr(pathOffset); const count = jsvm_env.storageRecords.read(key); const {baseFee, addl} = getPrice('sload'); jsvm_env.useGas(baseFee); jsvm_env.useGas(addl); const result = toBN(jsvm_env.storageLoad(key)); stack.push(result); const changed = {storage: [BN2hex(pathOffset), BN2hex(result), 0]} logger.debug('SLOAD', [pathOffset], [result], getCache(), stack, changed, position, baseFee, addl); return {stack, position}; }, getCaller: function ({stack, position}) { const gasCost = getPrice('caller'); jsvm_env.useGas(gasCost); const address = toBN(jsvm_env.getCaller()); stack.push(address); logger.debug('CALLER', [], [address], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, getCallValue: function ({stack, position}) { const gasCost = getPrice('callvalue'); jsvm_env.useGas(gasCost); const value = toBN(jsvm_env.getCallValue()); stack.push(value); logger.debug('CALLVALUE', [], [value], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, codeCopy: function (resultOffset, codeOffset, length, {stack, position}) { const { baseFee, addl, highestMemCost, memoryWordCount } = getPrice('codecopy', { offset: resultOffset, length: toBN(length), memWordCount: jsvm_env.memWordCount(), highestMemCost: jsvm_env.highestMemCost(), }); jsvm_env.useGas(baseFee); if (addl) jsvm_env.useGas(addl); if (highestMemCost) jsvm_env.setHighestMemCost(highestMemCost); if (memoryWordCount) jsvm_env.setMemWordCount(memoryWordCount); const result = jsvm_env.codeCopy(resultOffset, codeOffset, length); const changed = {memory: [resultOffset.toNumber(), result, 1]}; logger.debug('CODECOPY', [resultOffset, codeOffset, length], [], getCache(), stack, changed, position, baseFee, addl); return; }, // returns i32 - code size current env getCodeSize: function({stack, position}) { const gasCost = getPrice('codesize'); jsvm_env.useGas(gasCost); const result = toBN(jsvm_env.getCodeSize()); stack.push(result); logger.debug('CODESIZE', [], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, // block’s beneficiary address getBlockCoinbase: function({stack, position}) { const gasCost = getPrice('coinbase'); jsvm_env.useGas(gasCost); const value = toBN(jsvm_env.getBlockCoinbase()); stack.push(value); logger.debug('COINBASE', [], [value], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, create: function ( value, dataOffset, dataLength, {stack, position} ) { const {baseFee, addl} = getPrice('create', {length: dataLength}); jsvm_env.useGas(baseFee); jsvm_env.useGas(addl); const address = toBN(jsvm_env.create(value, dataOffset, dataLength)); stack.push(address); logger.debug('CREATE', [value, dataOffset, dataLength, ], [address], getCache(), stack, undefined, position, baseFee, addl); return {stack, position}; }, getBlockDifficulty: function ({stack, position}) { const gasCost = getPrice('difficulty'); jsvm_env.useGas(gasCost); const value = toBN(jsvm_env.getBlockDifficulty()); stack.push(value); logger.debug('DIFFICULTY', [], [value], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, externalCodeCopy: function ( address, resultOffset, codeOffset, dataLength, {stack, position} ) { const { baseFee, addl, highestMemCost, memoryWordCount } = getPrice('extcodecopy', { offset: resultOffset, length: dataLength, memWordCount: jsvm_env.memWordCount(), highestMemCost: jsvm_env.highestMemCost(), }); jsvm_env.useGas(baseFee); if (addl) jsvm_env.useGas(addl); if (highestMemCost) jsvm_env.setHighestMemCost(highestMemCost); if (memoryWordCount) jsvm_env.setMemWordCount(memoryWordCount); const result = jsvm_env.externalCodeCopy( BN2uint8arr(address), resultOffset, codeOffset, dataLength, ) const changed = {memory: [resultOffset.toNumber(), result, 1]}; logger.debug('EXTCODECOPY', [address, resultOffset, codeOffset, dataLength, ], [], getCache(), stack, changed, position, baseFee, addl); return; }, // Returns extCodeSize i32 getExternalCodeSize: function (address, {stack, position}) { const {baseFee, addl} = getPrice('extcodesize'); jsvm_env.useGas(baseFee); jsvm_env.useGas(addl); const result = toBN(jsvm_env.getExternalCodeSize(BN2uint8arr(address))); stack.push(result); logger.debug('EXTCODESIZE', [address], [result], getCache(), stack, undefined, position, baseFee, addl); return {stack, position}; }, // result gasLeft i64 getGasLeft: function ({stack, position}) { const gasCost = getPrice('gas'); jsvm_env.useGas(gasCost); const result = toBN(jsvm_env.getGasLeft()); stack.push(result); logger.debug('GAS', [], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, // result blockGasLimit i64 getBlockGasLimit: function ({stack, position}) { const gasCost = getPrice('gaslimit'); jsvm_env.useGas(gasCost); const result = toBN(jsvm_env.getBlockGasLimit()); stack.push(result); logger.debug('GASLIMIT', [], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, getTxGasPrice: function ({stack, position}) { const gasCost = getPrice('gasprice'); jsvm_env.useGas(gasCost); const result = toBN(jsvm_env.getTxGasPrice()); stack.push(result); logger.debug('GASPRICE', [], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, log: function ( dataOffset, dataLength, ...topics ) { const {stack, position} = topics.pop(); const numberOfTopics = topics.length; const {baseFee, addl} = getPrice('log', {topics: toBN(topics.length), length: dataLength}); jsvm_env.useGas(baseFee); jsvm_env.useGas(addl); jsvm_env.log( dataOffset, dataLength, numberOfTopics, topics, ); logger.debug('LOG' + numberOfTopics, [dataOffset, dataLength, numberOfTopics, ...topics ], [], getCache(), stack, undefined, position, baseFee, addl); return; }, log0: function(dataOffset, dataLength, ...topics) { return api.ethereum.log(dataOffset, dataLength, ...topics); }, log1: function(dataOffset, dataLength, ...topics) { return api.ethereum.log(dataOffset, dataLength, ...topics); }, log2: function(dataOffset, dataLength, ...topics) { return api.ethereum.log(dataOffset, dataLength, ...topics); }, log3: function(dataOffset, dataLength, ...topics) { return api.ethereum.log(dataOffset, dataLength, ...topics); }, log4: function(dataOffset, dataLength, ...topics) { return api.ethereum.log(dataOffset, dataLength, ...topics); }, // result blockNumber i64 getBlockNumber: function ({stack, position}) { const gasCost = getPrice('number'); jsvm_env.useGas(gasCost); const result = toBN(jsvm_env.getBlockNumber()); stack.push(result); logger.debug('NUMBER', [], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, getTxOrigin: function ({stack, position}) { const gasCost = getPrice('origin'); jsvm_env.useGas(gasCost); const address = toBN(jsvm_env.getTxOrigin()); stack.push(address); logger.debug('ORIGIN', [], [address], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, finish: function (dataOffset, dataLength, {stack, position}) { const gasCost = getPrice('return'); jsvm_env.useGas(gasCost); const result = jsvm_env.finish(dataOffset, dataLength); logger.debug('FINISH', [dataOffset, dataLength], [result], getCache(), stack, undefined, position, gasCost); finishAction({result, gas: jsvm_env.getGas(), context: jsvm_env.getContext(), logs: jsvm_env.getLogs()}); return {stack, position: 0}; }, revert: function (dataOffset, dataLength, {stack, position}) { const gasCost = getPrice('revert'); jsvm_env.useGas(gasCost); const result = jsvm_env.revert(dataOffset, dataLength); logger.debug('REVERT', [dataOffset, dataLength], [result], getCache(), stack, undefined, position, gasCost); revertAction({result, gas: jsvm_env.getGas()}); return {stack, position: 0}; }, // result dataSize i32 getReturnDataSize: function ({stack, position}) { const gasCost = getPrice('returndatasize'); jsvm_env.useGas(gasCost); const result = toBN(jsvm_env.getReturnDataSize()); stack.push(result); logger.debug('RETURNDATASIZE', [], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, returnDataCopy: function (resultOffset, dataOffset, length, {stack, position}) { const { baseFee, addl, highestMemCost, memoryWordCount } = getPrice('returndatacopy', { offset: resultOffset, length: toBN(length), memWordCount: jsvm_env.memWordCount(), highestMemCost: jsvm_env.highestMemCost(), }); jsvm_env.useGas(baseFee); if (addl) jsvm_env.useGas(addl); if (highestMemCost) jsvm_env.setHighestMemCost(highestMemCost); if (memoryWordCount) jsvm_env.setMemWordCount(memoryWordCount); const result = jsvm_env.returnDataCopy(resultOffset, dataOffset, length); const changed = {memory: [resultOffset.toNumber(), result, 1]}; logger.debug('RETURNDATACOPY', [resultOffset, dataOffset, length], [], getCache(), stack, changed, position, baseFee, addl); return; }, selfDestruct: function (address, {stack, position}) { const gasCost = getPrice('selfdestruct'); jsvm_env.useGas(gasCost); jsvm_env.selfDestruct(BN2uint8arr(address)); logger.debug('SELFDESTRUCT', [address], [], getCache(), stack, undefined, position, gasCost); finishAction({gas: jsvm_env.getGas(), context: jsvm_env.getContext(), logs: jsvm_env.getLogs()}); return {stack, position: 0}; }, getBlockTimestamp: function ({stack, position}) { const gasCost = getPrice('timestamp'); jsvm_env.useGas(gasCost); const result = toBN(jsvm_env.getBlockTimestamp()); stack.push(result); logger.debug('TIMESTAMP', [], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, return: (offset, length, {stack, position}) => { const gasCost = getPrice('return'); jsvm_env.useGas(gasCost); const result = jsvm_env.finish(offset, length); logger.debug('RETURN', [offset, length], [result], getCache(), stack, undefined, position, gasCost); finishAction({result, gas: jsvm_env.getGas(), context: jsvm_env.getContext(), logs: jsvm_env.getLogs()}); return {stack, position: 0}; }, revert: (offset, length, {stack, position}) => { const gasCost = getPrice('revert'); jsvm_env.useGas(gasCost); const result = jsvm_env.revert(offset, length); logger.debug('REVERT', [offset, length], [result], getCache(), stack, undefined, position, gasCost); revertAction({result, gas: jsvm_env.getGas()}); return {stack, position: 0}; }, stop: (forced = true, {stack, position}) => { const gasCost = getPrice('stop'); jsvm_env.useGas(gasCost); if (!forced) { logger.debug('STOP', [], [], getCache(), stack, undefined, position, gasCost); } finishAction({gas: jsvm_env.getGas(), context: jsvm_env.getContext(), logs: jsvm_env.getLogs()}); return {stack, position: 0}; }, keccak256: (offset, length, {stack, position}) => { const {baseFee, addl} = getPrice('keccak256', {length}); jsvm_env.useGas(baseFee); jsvm_env.useGas(addl); const slots = Math.ceil(length.toNumber() / 32); const data = [...new Array(slots).keys()].map(index => { const delta = toBN(index * 32); return jsvm_env.loadMemory(offset.add(delta)); }).reduce((accum, value) => { return new Uint8Array([...accum, ...value]); }, []); const hash = keccak256(data); const result = toBN(hash); stack.push(result); logger.debug('keccak256', [offset, length], [result], getCache(), stack, undefined, position, baseFee, addl); return {stack, position}; }, uint256Max: () => new BN('10000000000000000000000000000000000000000000000000000000000000000', 16), // mimick evm overflow add: (a, b, {stack, position}) => { const gasCost = getPrice('add'); jsvm_env.useGas(gasCost); const result = a.add(b).mod(api.ethereum.uint256Max()); stack.push(result); logger.debug('ADD', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, mul: (a, b, {stack, position}) => { const gasCost = getPrice('mul'); jsvm_env.useGas(gasCost); const result = a.mul(b).mod(api.ethereum.uint256Max()); stack.push(result); logger.debug('MUL', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, // mimick evm underflow sub: (a, b, {stack, position}) => { const gasCost = getPrice('sub'); jsvm_env.useGas(gasCost); const result = a.sub(b).mod(api.ethereum.uint256Max()); stack.push(result); logger.debug('SUB', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, div: (a, b, {stack, position}) => { const gasCost = getPrice('div'); jsvm_env.useGas(gasCost); let result; if (b.isZero()) result = toBN(0); else result = a.abs().div(b.abs()); stack.push(result); logger.debug('DIV', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, sdiv: (a, b, {stack, position}) => { const gasCost = getPrice('sdiv'); jsvm_env.useGas(gasCost); let result; const _a = a.fromTwos(256); const _b = b.fromTwos(256); if (_b.isZero()) result = toBN(0); else result = _a.div(_b); stack.push(result); logger.debug('SDIV', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, mod: (a, b, {stack, position}) => { const gasCost = getPrice('mod'); jsvm_env.useGas(gasCost); const result = a.abs().mod(b.abs()); stack.push(result); logger.debug('MOD', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, smod: (a, b, {stack, position}) => { const gasCost = getPrice('smod'); jsvm_env.useGas(gasCost); let result; const _a = a.fromTwos(256); const _b = b.fromTwos(256); if (_b.isZero()) result = toBN(0); else result = _a.mod(_b); stack.push(result); logger.debug('SMOD', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, addmod: (a, b, c, {stack, position}) => { const gasCost = getPrice('addmod'); jsvm_env.useGas(gasCost); const result = api.ethereum.mod(api.ethereum.add(a, b), c); stack.push(result); logger.debug('ADDMOD', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, mulmod: (a, b, c, {stack, position}) => { const gasCost = getPrice('mulmod'); jsvm_env.useGas(gasCost); const result = api.ethereum.mod(api.ethereum.mul(a, b), c); stack.push(result); logger.debug('MULMOD', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, exp: (a, b, {stack, position}) => { const {baseFee, addl} = getPrice('exp', {exponent: b}); jsvm_env.useGas(baseFee); jsvm_env.useGas(addl); if (b.lt(toBN(0))) return toBN(0); const result = a.pow(b); stack.push(result); logger.debug('EXP', [a, b], [result], getCache(), stack, undefined, position, baseFee, addl); return {stack, position}; }, signextend: (size, value, {stack, position}) => { const gasCost = getPrice('signextend'); jsvm_env.useGas(gasCost); const result = value; stack.push(result); logger.debug('SIGNEXTEND', [size, value], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, lt: (a, b, {stack, position}) => { const gasCost = getPrice('lt'); jsvm_env.useGas(gasCost); let result = a.abs().lt(b.abs()); result = toBN(result ? 1 : 0); stack.push(result); logger.debug('LT', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, gt: (a, b, {stack, position}) => { const gasCost = getPrice('gt'); jsvm_env.useGas(gasCost); let result = a.abs().gt(b.abs()); result = toBN(result ? 1 : 0); stack.push(result); logger.debug('GT', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, slt: (a, b, {stack, position}) => { const gasCost = getPrice('slt'); jsvm_env.useGas(gasCost); const _a = a.fromTwos(256); const _b = b.fromTwos(256); let result = _a.lt(_b); result = toBN(result ? 1 : 0); stack.push(result); logger.debug('SLT', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, sgt: (a, b, {stack, position}) => { const gasCost = getPrice('sgt'); jsvm_env.useGas(gasCost); const _a = a.fromTwos(256); const _b = b.fromTwos(256); let result = _a.gt(_b); result = toBN(result ? 1 : 0); stack.push(result); logger.debug('SGT', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, eq: (a, b, {stack, position}) => { const gasCost = getPrice('eq'); jsvm_env.useGas(gasCost); let result = a.eq(b); result = toBN(result ? 1 : 0); stack.push(result); logger.debug('EQ', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, iszero: (a, {stack, position}) => { const gasCost = getPrice('iszero'); jsvm_env.useGas(gasCost); let result = a.isZero(); result = toBN(result ? 1 : 0); stack.push(result); logger.debug('ISZERO', [a], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, and: (a, b, {stack, position}) => { const gasCost = getPrice('and'); jsvm_env.useGas(gasCost); const result = a.and(b); stack.push(result); logger.debug('AND', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, or: (a, b, {stack, position}) => { const gasCost = getPrice('or'); jsvm_env.useGas(gasCost); const result = a.or(b); stack.push(result); logger.debug('OR', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, xor: (a, b, {stack, position}) => { const gasCost = getPrice('xor'); jsvm_env.useGas(gasCost); const result = a.xor(b); stack.push(result); logger.debug('XOR', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, not: (a, {stack, position}) => { const gasCost = getPrice('not'); jsvm_env.useGas(gasCost); const result = a.notn(256); stack.push(result); logger.debug('NOT', [a], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, byte: (nth, bb, {stack, position}) => { const gasCost = getPrice('byte'); jsvm_env.useGas(gasCost); const result = toBN(BN2uint8arr(bb).slice(nth, nth + 1)); stack.push(result); logger.debug('BYTE', [bb], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, shl: (a, b, {stack, position}) => { const gasCost = getPrice('shl'); jsvm_env.useGas(gasCost); const numberOfBits = a.toNumber(); const result = b.shln(numberOfBits); result.imaskn(256); // clear bits with indexes higher or equal to 256 stack.push(result); logger.debug('SHL', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, shr: (a, b, {stack, position}) => { const gasCost = getPrice('shr'); jsvm_env.useGas(gasCost); const result = b.shrn(a.toNumber()); stack.push(result); logger.debug('SHR', [a, b], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, sar: (nobits, value, {stack, position}) => { const gasCost = getPrice('sar'); jsvm_env.useGas(gasCost); const _nobits = nobits.toNumber(); let valueBase2; if (value.isNeg()) { valueBase2 = value.toTwos(256).toString(2); } else { valueBase2 = value.toString(2).padStart(256, '0'); } // remove LSB * _nobits valueBase2 = valueBase2.substring(0, valueBase2.length - _nobits); // add MSB * _nobits valueBase2 = valueBase2[0].repeat(_nobits) + valueBase2; const result = (new BN(valueBase2, 2)).fromTwos(256); stack.push(result); logger.debug('SAR', [nobits, value], [result], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, handlePush: (code, bytecode, {stack, position}) => { const gasCost = getPrice('push'); jsvm_env.useGas(gasCost); const _position = position; const no = code - 0x60 + 1; const value = toBN(bytecode.slice(position, position + no)); stack.push(value); position += no; logger.debug('PUSH' + no + ' 0x' + value.toString(16).padStart(no*2, '0'), [value], [], getCache(), stack, undefined, _position, gasCost); return {stack, position}; }, handleSwap: (code, {stack, position}) => { const gasCost = getPrice('swap'); jsvm_env.useGas(gasCost); const no = code - 0x90 + 1; if (no >= stack.length) throw new Error(`Invalid SWAP${no} ; stack: ${stack}`); const last = stack.pop(); const spliced = stack.splice(stack.length - no, 1, last); stack.push(spliced[0]); logger.debug('SWAP' + no, [], [], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, handleDup: (code, {stack, position}) => { const gasCost = getPrice('dup'); jsvm_env.useGas(gasCost); const no = code - 0x80 + 1; if (no > stack.length) throw new Error(`Invalid DUP${no} ; stack: ${stack}`); const value = stack[stack.length - no]; stack.push(value); logger.debug('DUP' + no, [], [], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, jump: (newpos, {stack, position}) => { const gasCost = getPrice('jump'); jsvm_env.useGas(gasCost); if (!newpos && newpos !== 0) throw new Error(`Invalid JUMP ${newpos}`); newpos = newpos.toNumber(); logger.debug('JUMP', [newpos], [], getCache(), stack, undefined, position, gasCost); return {stack, position: newpos}; }, jumpi: (newpos, condition, {stack, position}) => { const gasCost = getPrice('jumpi'); jsvm_env.useGas(gasCost); const _position = position; newpos = newpos.toNumber(); // EVM allows any uint256 except from 0 to be interpreted as true if (!condition.eqn(0)) position = newpos; logger.debug('JUMPI', [condition, newpos], [], getCache(), stack, undefined, _position, gasCost); return {stack, position}; }, jumpdest:({stack, position}) => { const gasCost = getPrice('jumpdest'); jsvm_env.useGas(gasCost); logger.debug('JUMPDEST', [], [], getCache(), stack, undefined, position, gasCost); }, pop: ({stack, position}) => { const gasCost = getPrice('pop'); jsvm_env.useGas(gasCost); stack.pop(); logger.debug('POP', [], [], getCache(), stack, undefined, position, gasCost); return {stack, position}; }, // helpers getGas: () => { return jsvm_env.getGas(); }, // precompiles ecrecover: function ( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength, {stack, position}, ) { const {baseFee} = getPrice('ecrecover'); jsvm_env.useGas(baseFee); const result = toBN(jsvm_env.ecrecover( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength )); if (result.eqn(0)) stack.push(toBN(0)); else stack.push(toBN(1)); logger.debug('ECRECOVER', [ gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength], [result], getCache(), stack, undefined, position, baseFee); return {stack, position}; }, sha256: function ( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength, {stack, position}, ) { throw new Error('sha256 not implemented'); }, ripemd160: function ( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength, {stack, position}, ) { throw new Error('ripemd160 not implemented'); }, identity: function ( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength, {stack, position}, ) { throw new Error('identity not implemented'); }, modexp: function ( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength, {stack, position}, ) { throw new Error('modexp not implemented'); }, ecadd: function ( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength, {stack, position}, ) { throw new Error('ecadd not implemented'); }, ecmul: function ( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength, {stack, position}, ) { throw new Error('ecmul not implemented'); }, ecpairing: function ( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength, {stack, position}, ) { throw new Error('ecpairing not implemented'); }, blake2f: function ( gas_limit_i64, dataOffset, dataLength, outputOffset, outputLength, {stack, position}, ) { throw new Error('blake2f not implemented'); }, } return api; } const instantiateModule = (bytecode, importObj) => { if (!bytecode) throw new Error('No bytecode found'); if (typeof bytecode === 'string') bytecode = hexToUint8Array(bytecode); if (!(bytecode instanceof Uint8Array)) throw new Error('evm - bytecode not Uint8Array'); const wmodule = { instance: { exports: { main: calldata => interpreter({bytecode, importObj: importObj.ethereum, calldata, position: 0}), memory: new WebAssembly.Memory({ initial: 2 }), } } } return new Promise((resolve, reject) => { resolve(wmodule); }); } const interpreter = async ({bytecode, importObj, stack = [], calldata=[], position=0}) => { ({bytecode, stack, position} = await interpretOpcode({bytecode, stack, importObj, position})); if (position > 0) await interpreter({bytecode, importObj, stack, calldata, position}); } const interpretOpcode = async ({bytecode, stack, importObj, position}) => { let hexcode; const args = []; if (position > bytecode.length) throw new Error(`Invalid pc ${position}. Codesize ${bytecode.length}`); if (bytecode[position]) { hexcode = bytecode[position].toString(16).padStart(2, '0'); } else { hexcode = '00'; args.push(true); } position += 1; const code = parseInt(hexcode, 16); const opcode = opcodes[hexcode]; let obj; let opcodeName; const invalidError = `Invalid opcode ${hexcode}; ${typeof hexcode} - ${JSON.stringify(opcode)}`; if (opcode && importObj[opcode.name]) opcodeName = opcode.name; else if (0x60 <= code && code < 0x80) opcodeName = 'push'; else if (0x80 <= code && code < 0x90) opcodeName = 'dup'; else if (0x90 <= code && code < 0xa0) opcodeName = 'swap'; else throw new Error(invalidError); if (opcodeName && importObj[opcodeName]) { for (let i = 0; i < opcode.arity; i++) { args.push(stack.pop()); } switch(opcodeName) { case 'jump': obj = importObj.jump(...args, {stack, position}); if (bytecode[obj.position] !== 0x5b) throw new Error(`Invalid JUMP destination ${obj.position}`); break; case 'jumpi': obj = importObj.jumpi(...args, {stack, position}); break; case 'pop': obj = importObj.pop({stack, position}); break; default: obj = await importObj[opcodeName](...args, {stack, position}); } } else if (opcodeName === 'push') { obj = importObj.handlePush(code, bytecode, {stack, position}); } else if (opcodeName === 'dup') { obj = importObj.handleDup(code, {stack, position}); } else if (opcodeName === 'swap') { obj = importObj.handleSwap(code, {stack, position}); } else { throw new Error(invalidError); } if (obj) { stack = typeof obj.stack !== 'undefined' ? obj.stack : stack; position = typeof obj.position !== 'undefined' ? obj.position : position; } return {bytecode, stack, position}; } const opcodes = { '00': {name: 'stop', arity: 0}, '01': {name: 'add', arity: 2}, '02': {name: 'mul', arity: 2}, '03': {name: 'sub', arity: 2}, '04': {name: 'div', arity: 2}, '05': {name: 'sdiv', arity: 2}, '06': {name: 'mod', arity: 2}, '07': {name: 'smod', arity: 2}, '08': {name: 'addmod', arity: 3}, '09': {name: 'mulmod', arity: 3}, '0a': {name: 'exp', arity: 2}, '0b': {name: 'signextend', arity: 2}, '10': {name: 'lt', arity: 2}, '11': {name: 'gt', arity: 2}, '12': {name: 'slt', arity: 2}, '13': {name: 'sgt', arity: 2}, '14': {name: 'eq', arity: 2}, '15': {name: 'iszero', arity: 1}, '16': {name: 'and', arity: 2}, '17': {name: 'or', arity: 2}, '18': {name: 'xor', arity: 2}, '19': {name: 'not', arity: 1}, '1a': {name: 'byte', arity: 2}, '1b': {name: 'shl', arity: 2}, '1c': {name: 'shr', arity: 2}, '1d': {name: 'sar', arity: 2}, '20': {name: 'keccak256', arity: 2}, '30': {name: 'getAddress', arity: 0}, '31': {name: 'getExternalBalance', arity: 1}, '32': {name: 'getTxOrigin', arity: 0}, '33': {name: 'getCaller', arity: 0}, '34': {name: 'getCallValue', arity: 0}, '35': {name: 'callDataLoad', arity: 1}, '36': {name: 'getCallDataSize', arity: 0}, '37': {name: 'callDataCopy', arity: 3}, '38': {name: 'getCodeSize', arity: 0}, '39': {name: 'codeCopy', arity: 3}, '3a': {name: 'getTxGasPrice', arity: 0}, '3b': {name: 'getExternalCodeSize', arity: 1}, '3c': {name: 'externalCodeCopy', arity: 4}, '3d': {name: 'getReturnDataSize', arity: 0}, '3e': {name: 'returnDataCopy', arity: 3}, '3f': {name: 'getExternalCodeHash', arity: 1}, '40': {name: 'getBlockHash', arity: 1}, '41': {name: 'getBlockCoinbase', arity: 0}, '42': {name: 'getBlockTimestamp', arity: 0}, '43': {name: 'getBlockNumber', arity: 0}, '44': {name: 'getBlockDifficulty', arity: 0}, '45': {name: 'getBlockGasLimit', arity: 0}, '46': {name: 'getBlockChainId', arity: 0}, '47': {name: 'getSelfBalance', arity: 0}, '50': {name: 'pop', arity: 0}, '51': {name: 'loadMemory', arity: 1}, '52': {name: 'storeMemory', arity: 2}, '53': {name: 'storeMemory8', arity: 2}, '54': {name: 'storageLoad', arity: 1}, '55': {name: 'storageStore', arity: 2}, '56': {name: 'jump', arity: 1}, '57': {name: 'jumpi', arity: 2}, '58': {name: 'pc', arity: 0}, '59': {name: 'getMSize', arity: 0}, '5a': {name: 'getGasLeft', arity: 0}, '5b': {name: 'jumpdest', arity: 0}, 'a0': {name: 'log0', arity: 2}, 'a1': {name: 'log1', arity: 3}, 'a2': {name: 'log2', arity: 4}, 'a3': {name: