/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.vm; import org.ethereum.config.BlockchainConfig; import org.ethereum.config.SystemProperties; import org.ethereum.db.ContractDetails; import org.ethereum.vm.MessageCall.MsgType; import org.ethereum.vm.program.Program; import org.ethereum.vm.program.Stack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static org.ethereum.crypto.HashUtil.sha3; import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; import static org.ethereum.vm.OpCode.CALL; import static org.ethereum.vm.OpCode.CALLCODE; import static org.ethereum.vm.OpCode.CREATE; import static org.ethereum.vm.OpCode.DELEGATECALL; import static org.ethereum.vm.OpCode.PUSH1; /** * The Ethereum Virtual Machine (EVM) is responsible for initialization * and executing a transaction on a contract. * * It is a quasi-Turing-complete machine; the quasi qualification * comes from the fact that the computation is intrinsically bounded * through a parameter, gas, which limits the total amount of computation done. * * The EVM is a simple stack-based architecture. The word size of the machine * (and thus size of stack item) is 256-bit. This was chosen to facilitate * the SHA3-256 hash scheme and elliptic-curve computations. The memory model * is a simple word-addressed byte array. The stack has an unlimited size. * The machine also has an independent storage model; this is similar in concept * to the memory but rather than a byte array, it is a word-addressable word array. * * Unlike memory, which is volatile, storage is non volatile and is * maintained as part of the system state. All locations in both storage * and memory are well-defined initially as zero. * * The machine does not follow the standard von Neumann architecture. * Rather than storing program code in generally-accessible memory or storage, * it is stored separately in a virtual ROM interactable only though * a specialised instruction. * * The machine can have exceptional execution for several reasons, * including stack underflows and invalid instructions. These unambiguously * and validly result in immediate halting of the machine with all state changes * left intact. The one piece of exceptional execution that does not leave * state changes intact is the out-of-gas (OOG) exception. * * Here, the machine halts immediately and reports the issue to * the execution agent (either the transaction processor or, recursively, * the spawning execution environment) and which will deal with it separately. * * @author Roman Mandeleil * @since 01.06.2014 */ public class VM { private static final Logger logger = LoggerFactory.getLogger("VM"); private static final Logger dumpLogger = LoggerFactory.getLogger("dump"); private static BigInteger _32_ = BigInteger.valueOf(32); private static final String logString = "{} Op: [{}] Gas: [{}] Deep: [{}] Hint: [{}]"; private static BigInteger MAX_GAS = BigInteger.valueOf(Long.MAX_VALUE / 2); /* Keeps track of the number of steps performed in this VM */ private int vmCounter = 0; private static VMHook vmHook; private boolean vmTrace; private long dumpBlock; private final SystemProperties config; public VM() { this(SystemProperties.getDefault()); } @Autowired public VM(SystemProperties config) { this.config = config; vmTrace = config.vmTrace(); dumpBlock = config.dumpBlock(); } private long calcMemGas(GasCost gasCosts, long oldMemSize, BigInteger newMemSize, long copySize) { long gasCost = 0; // Avoid overflows if (newMemSize.compareTo(MAX_GAS) == 1) { throw Program.Exception.gasOverflow(newMemSize, MAX_GAS); } // memory gas calc long memoryUsage = (newMemSize.longValue() + 31) / 32 * 32; if (memoryUsage > oldMemSize) { long memWords = (memoryUsage / 32); long memWordsOld = (oldMemSize / 32); //TODO #POC9 c_quadCoeffDiv = 512, this should be a constant, not magic number long memGas = ( gasCosts.getMEMORY() * memWords + memWords * memWords / 512) - (gasCosts.getMEMORY() * memWordsOld + memWordsOld * memWordsOld / 512); gasCost += memGas; } if (copySize > 0) { long copyGas = gasCosts.getCOPY_GAS() * ((copySize + 31) / 32); gasCost += copyGas; } return gasCost; } private boolean isDeadAccount(Program program, byte[] addr) { return !program.getStorage().isExist(addr) || program.getStorage().getAccountState(addr).isEmpty(); } public void step(Program program) { if (vmTrace) { program.saveOpTrace(); } try { BlockchainConfig blockchainConfig = program.getBlockchainConfig(); OpCode op = OpCode.code(program.getCurrentOp()); if (op == null) { throw Program.Exception.invalidOpCode(program.getCurrentOp()); } if (op == DELEGATECALL) { // opcode since Homestead release only if (!blockchainConfig.getConstants().hasDelegateCallOpcode()) { throw Program.Exception.invalidOpCode(program.getCurrentOp()); } } program.setLastOp(op.val()); program.verifyStackSize(op.require()); program.verifyStackOverflow(op.require(), op.ret()); //Check not exceeding stack limits long oldMemSize = program.getMemSize(); Stack stack = program.getStack(); String hint = ""; long callGas = 0, memWords = 0; // parameters for logging long gasCost = op.getTier().asInt(); long gasBefore = program.getGasLong(); int stepBefore = program.getPC(); GasCost gasCosts = blockchainConfig.getGasCost(); DataWord adjustedCallGas = null; /*DEBUG #POC9 if( op.asInt() == 96 || op.asInt() == -128 || op.asInt() == 57 || op.asInt() == 115) { //byte alphaone = 0x63; //op = OpCode.code(alphaone); gasCost = 3; } if( op.asInt() == -13 ) { //byte alphaone = 0x63; //op = OpCode.code(alphaone); gasCost = 0; }*/ // Calculate fees and spend gas switch (op) { case STOP: gasCost = gasCosts.getSTOP(); break; case SUICIDE: gasCost = gasCosts.getSUICIDE(); DataWord suicideAddressWord = stack.get(stack.size() - 1); if (blockchainConfig.eip161()) { if (isDeadAccount(program, suicideAddressWord.getLast20Bytes()) && !program.getBalance(program.getOwnerAddress()).isZero()) { gasCost += gasCosts.getNEW_ACCT_SUICIDE(); } } else { if (!program.getStorage().isExist(suicideAddressWord.getLast20Bytes())) { gasCost += gasCosts.getNEW_ACCT_SUICIDE(); } } break; case SSTORE: DataWord newValue = stack.get(stack.size() - 2); DataWord oldValue = program.storageLoad(stack.peek()); if (oldValue == null && !newValue.isZero()) gasCost = gasCosts.getSET_SSTORE(); else if (oldValue != null && newValue.isZero()) { // todo: GASREFUND counter policy // refund step cost policy. program.futureRefundGas(gasCosts.getREFUND_SSTORE()); gasCost = gasCosts.getCLEAR_SSTORE(); } else gasCost = gasCosts.getRESET_SSTORE(); break; case SLOAD: gasCost = gasCosts.getSLOAD(); break; case BALANCE: gasCost = gasCosts.getBALANCE(); break; // These all operate on memory and therefore potentially expand it: case MSTORE: gasCost += calcMemGas(gasCosts, oldMemSize, memNeeded(stack.peek(), new DataWord(32)), 0); break; case MSTORE8: gasCost += calcMemGas(gasCosts, oldMemSize, memNeeded(stack.peek(), new DataWord(1)), 0); break; case MLOAD: gasCost += calcMemGas(gasCosts, oldMemSize, memNeeded(stack.peek(), new DataWord(32)), 0); break; case RETURN: gasCost = gasCosts.getSTOP() + calcMemGas(gasCosts, oldMemSize, memNeeded(stack.peek(), stack.get(stack.size() - 2)), 0); break; case SHA3: gasCost = gasCosts.getSHA3() + calcMemGas(gasCosts, oldMemSize, memNeeded(stack.peek(), stack.get(stack.size() - 2)), 0); DataWord size = stack.get(stack.size() - 2); long chunkUsed = (size.longValueSafe() + 31) / 32; gasCost += chunkUsed * gasCosts.getSHA3_WORD(); break; case CALLDATACOPY: gasCost += calcMemGas(gasCosts, oldMemSize, memNeeded(stack.peek(), stack.get(stack.size() - 3)), stack.get(stack.size() - 3).longValueSafe()); break; case CODECOPY: gasCost += calcMemGas(gasCosts, oldMemSize, memNeeded(stack.peek(), stack.get(stack.size() - 3)), stack.get(stack.size() - 3).longValueSafe()); break; case EXTCODESIZE: gasCost = gasCosts.getEXT_CODE_SIZE(); break; case EXTCODECOPY: gasCost = gasCosts.getEXT_CODE_COPY() + calcMemGas(gasCosts, oldMemSize, memNeeded(stack.get(stack.size() - 2), stack.get(stack.size() - 4)), stack.get(stack.size() - 4).longValueSafe()); break; case CALL: case CALLCODE: case DELEGATECALL: gasCost = gasCosts.getCALL(); DataWord callGasWord = stack.get(stack.size() - 1); DataWord callAddressWord = stack.get(stack.size() - 2); //check to see if account does not exist and is not a precompiled contract if (op == CALL) { DataWord value = stack.get(stack.size() - 3); if (blockchainConfig.eip161()) { if (isDeadAccount(program, callAddressWord.getLast20Bytes()) && !value.isZero()) { gasCost += gasCosts.getNEW_ACCT_CALL(); } } else { if (!program.getStorage().isExist(callAddressWord.getLast20Bytes())) { gasCost += gasCosts.getNEW_ACCT_CALL(); } } } //TODO #POC9 Make sure this is converted to BigInteger (256num support) if (op != DELEGATECALL && !stack.get(stack.size() - 3).isZero() ) gasCost += gasCosts.getVT_CALL(); int opOff = op == DELEGATECALL ? 3 : 4; BigInteger in = memNeeded(stack.get(stack.size() - opOff), stack.get(stack.size() - opOff - 1)); // in offset+size BigInteger out = memNeeded(stack.get(stack.size() - opOff - 2), stack.get(stack.size() - opOff - 3)); // out offset+size gasCost += calcMemGas(gasCosts, oldMemSize, in.max(out), 0); if (gasCost > program.getGas().longValueSafe()) { throw Program.Exception.notEnoughOpGas(op, callGasWord, program.getGas()); } DataWord gasLeft = program.getGas().clone(); gasLeft.sub(new DataWord(gasCost)); adjustedCallGas = blockchainConfig.getCallGas(op, callGasWord, gasLeft); gasCost += adjustedCallGas.longValueSafe(); break; case CREATE: gasCost = gasCosts.getCREATE() + calcMemGas(gasCosts, oldMemSize, memNeeded(stack.get(stack.size() - 2), stack.get(stack.size() - 3)), 0); break; case LOG0: case LOG1: case LOG2: case LOG3: case LOG4: int nTopics = op.val() - OpCode.LOG0.val(); BigInteger dataSize = stack.get(stack.size() - 2).value(); BigInteger dataCost = dataSize.multiply(BigInteger.valueOf(gasCosts.getLOG_DATA_GAS())); if (program.getGas().value().compareTo(dataCost) < 0) { throw Program.Exception.notEnoughOpGas(op, dataCost, program.getGas().value()); } gasCost = gasCosts.getLOG_GAS() + gasCosts.getLOG_TOPIC_GAS() * nTopics + gasCosts.getLOG_DATA_GAS() * stack.get(stack.size() - 2).longValue() + calcMemGas(gasCosts, oldMemSize, memNeeded(stack.peek(), stack.get(stack.size() - 2)), 0); break; case EXP: DataWord exp = stack.get(stack.size() - 2); int bytesOccupied = exp.bytesOccupied(); gasCost = gasCosts.getEXP_GAS() + gasCosts.getEXP_BYTE_GAS() * bytesOccupied; break; default: break; } //DEBUG System.out.println(" OP IS " + op.name() + " GASCOST IS " + gasCost + " NUM IS " + op.asInt()); program.spendGas(gasCost, op.name()); // Log debugging line for VM if (program.getNumber().intValue() == dumpBlock) this.dumpLine(op, gasBefore, gasCost + callGas, memWords, program); if (vmHook != null) { vmHook.step(program, op); } // Execute operation switch (op) { /** * Stop and Arithmetic Operations */ case STOP: { program.setHReturn(EMPTY_BYTE_ARRAY); program.stop(); } break; case ADD: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " + " + word2.value(); word1.add(word2); program.stackPush(word1); program.step(); } break; case MUL: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " * " + word2.value(); word1.mul(word2); program.stackPush(word1); program.step(); } break; case SUB: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " - " + word2.value(); word1.sub(word2); program.stackPush(word1); program.step(); } break; case DIV: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " / " + word2.value(); word1.div(word2); program.stackPush(word1); program.step(); } break; case SDIV: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.sValue() + " / " + word2.sValue(); word1.sDiv(word2); program.stackPush(word1); program.step(); } break; case MOD: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " % " + word2.value(); word1.mod(word2); program.stackPush(word1); program.step(); } break; case SMOD: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.sValue() + " #% " + word2.sValue(); word1.sMod(word2); program.stackPush(word1); program.step(); } break; case EXP: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " ** " + word2.value(); word1.exp(word2); program.stackPush(word1); program.step(); } break; case SIGNEXTEND: { DataWord word1 = program.stackPop(); BigInteger k = word1.value(); if (k.compareTo(_32_) < 0) { DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1 + " " + word2.value(); word2.signExtend(k.byteValue()); program.stackPush(word2); } program.step(); } break; case NOT: { DataWord word1 = program.stackPop(); word1.bnot(); if (logger.isInfoEnabled()) hint = "" + word1.value(); program.stackPush(word1); program.step(); } break; case LT: { // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " < " + word2.value(); if (word1.value().compareTo(word2.value()) == -1) { word1.and(DataWord.ZERO); word1.getData()[31] = 1; } else { word1.and(DataWord.ZERO); } program.stackPush(word1); program.step(); } break; case SLT: { // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.sValue() + " < " + word2.sValue(); if (word1.sValue().compareTo(word2.sValue()) == -1) { word1.and(DataWord.ZERO); word1.getData()[31] = 1; } else { word1.and(DataWord.ZERO); } program.stackPush(word1); program.step(); } break; case SGT: { // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.sValue() + " > " + word2.sValue(); if (word1.sValue().compareTo(word2.sValue()) == 1) { word1.and(DataWord.ZERO); word1.getData()[31] = 1; } else { word1.and(DataWord.ZERO); } program.stackPush(word1); program.step(); } break; case GT: { // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " > " + word2.value(); if (word1.value().compareTo(word2.value()) == 1) { word1.and(DataWord.ZERO); word1.getData()[31] = 1; } else { word1.and(DataWord.ZERO); } program.stackPush(word1); program.step(); } break; case EQ: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " == " + word2.value(); if (word1.xor(word2).isZero()) { word1.and(DataWord.ZERO); word1.getData()[31] = 1; } else { word1.and(DataWord.ZERO); } program.stackPush(word1); program.step(); } break; case ISZERO: { DataWord word1 = program.stackPop(); if (word1.isZero()) { word1.getData()[31] = 1; } else { word1.and(DataWord.ZERO); } if (logger.isInfoEnabled()) hint = "" + word1.value(); program.stackPush(word1); program.step(); } break; /** * Bitwise Logic Operations */ case AND: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " && " + word2.value(); word1.and(word2); program.stackPush(word1); program.step(); } break; case OR: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " || " + word2.value(); word1.or(word2); program.stackPush(word1); program.step(); } break; case XOR: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) hint = word1.value() + " ^ " + word2.value(); word1.xor(word2); program.stackPush(word1); program.step(); } break; case BYTE: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); final DataWord result; if (word1.value().compareTo(_32_) == -1) { byte tmp = word2.getData()[word1.intValue()]; word2.and(DataWord.ZERO); word2.getData()[31] = tmp; result = word2; } else { result = new DataWord(); } if (logger.isInfoEnabled()) hint = "" + result.value(); program.stackPush(result); program.step(); } break; case ADDMOD: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); DataWord word3 = program.stackPop(); word1.addmod(word2, word3); program.stackPush(word1); program.step(); } break; case MULMOD: { DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); DataWord word3 = program.stackPop(); word1.mulmod(word2, word3); program.stackPush(word1); program.step(); } break; /** * SHA3 */ case SHA3: { DataWord memOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); byte[] buffer = program.memoryChunk(memOffsetData.intValueSafe(), lengthData.intValueSafe()); byte[] encoded = sha3(buffer); DataWord word = new DataWord(encoded); if (logger.isInfoEnabled()) hint = word.toString(); program.stackPush(word); program.step(); } break; /** * Environmental Information */ case ADDRESS: { DataWord address = program.getOwnerAddress(); if (logger.isInfoEnabled()) hint = "address: " + Hex.toHexString(address.getLast20Bytes()); program.stackPush(address); program.step(); } break; case BALANCE: { DataWord address = program.stackPop(); DataWord balance = program.getBalance(address); if (logger.isInfoEnabled()) hint = "address: " + Hex.toHexString(address.getLast20Bytes()) + " balance: " + balance.toString(); program.stackPush(balance); program.step(); } break; case ORIGIN: { DataWord originAddress = program.getOriginAddress(); if (logger.isInfoEnabled()) hint = "address: " + Hex.toHexString(originAddress.getLast20Bytes()); program.stackPush(originAddress); program.step(); } break; case CALLER: { DataWord callerAddress = program.getCallerAddress(); if (logger.isInfoEnabled()) hint = "address: " + Hex.toHexString(callerAddress.getLast20Bytes()); program.stackPush(callerAddress); program.step(); } break; case CALLVALUE: { DataWord callValue = program.getCallValue(); if (logger.isInfoEnabled()) hint = "value: " + callValue; program.stackPush(callValue); program.step(); } break; case CALLDATALOAD: { DataWord dataOffs = program.stackPop(); DataWord value = program.getDataValue(dataOffs); if (logger.isInfoEnabled()) hint = "data: " + value; program.stackPush(value); program.step(); } break; case CALLDATASIZE: { DataWord dataSize = program.getDataSize(); if (logger.isInfoEnabled()) hint = "size: " + dataSize.value(); program.stackPush(dataSize); program.step(); } break; case CALLDATACOPY: { DataWord memOffsetData = program.stackPop(); DataWord dataOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); byte[] msgData = program.getDataCopy(dataOffsetData, lengthData); if (logger.isInfoEnabled()) hint = "data: " + Hex.toHexString(msgData); program.memorySave(memOffsetData.intValueSafe(), msgData); program.step(); } break; case CODESIZE: case EXTCODESIZE: { int length; if (op == OpCode.CODESIZE) length = program.getCode().length; else { DataWord address = program.stackPop(); length = program.getCodeAt(address).length; } DataWord codeLength = new DataWord(length); if (logger.isInfoEnabled()) hint = "size: " + length; program.stackPush(codeLength); program.step(); } break; case CODECOPY: case EXTCODECOPY: { byte[] fullCode = EMPTY_BYTE_ARRAY; if (op == OpCode.CODECOPY) fullCode = program.getCode(); if (op == OpCode.EXTCODECOPY) { DataWord address = program.stackPop(); fullCode = program.getCodeAt(address); } int memOffset = program.stackPop().intValueSafe(); int codeOffset = program.stackPop().intValueSafe(); int lengthData = program.stackPop().intValueSafe(); int sizeToBeCopied = (long) codeOffset + lengthData > fullCode.length ? (fullCode.length < codeOffset ? 0 : fullCode.length - codeOffset) : lengthData; byte[] codeCopy = new byte[lengthData]; if (codeOffset < fullCode.length) System.arraycopy(fullCode, codeOffset, codeCopy, 0, sizeToBeCopied); if (logger.isInfoEnabled()) hint = "code: " + Hex.toHexString(codeCopy); program.memorySave(memOffset, codeCopy); program.step(); } break; case GASPRICE: { DataWord gasPrice = program.getGasPrice(); if (logger.isInfoEnabled()) hint = "price: " + gasPrice.toString(); program.stackPush(gasPrice); program.step(); } break; /** * Block Information */ case BLOCKHASH: { int blockIndex = program.stackPop().intValueSafe(); DataWord blockHash = program.getBlockHash(blockIndex); if (logger.isInfoEnabled()) hint = "blockHash: " + blockHash; program.stackPush(blockHash); program.step(); } break; case COINBASE: { DataWord coinbase = program.getCoinbase(); if (logger.isInfoEnabled()) hint = "coinbase: " + Hex.toHexString(coinbase.getLast20Bytes()); program.stackPush(coinbase); program.step(); } break; case TIMESTAMP: { DataWord timestamp = program.getTimestamp(); if (logger.isInfoEnabled()) hint = "timestamp: " + timestamp.value(); program.stackPush(timestamp); program.step(); } break; case NUMBER: { DataWord number = program.getNumber(); if (logger.isInfoEnabled()) hint = "number: " + number.value(); program.stackPush(number); program.step(); } break; case DIFFICULTY: { DataWord difficulty = program.getDifficulty(); if (logger.isInfoEnabled()) hint = "difficulty: " + difficulty; program.stackPush(difficulty); program.step(); } break; case GASLIMIT: { DataWord gaslimit = program.getGasLimit(); if (logger.isInfoEnabled()) hint = "gaslimit: " + gaslimit; program.stackPush(gaslimit); program.step(); } break; case POP: { program.stackPop(); program.step(); } break; case DUP1: case DUP2: case DUP3: case DUP4: case DUP5: case DUP6: case DUP7: case DUP8: case DUP9: case DUP10: case DUP11: case DUP12: case DUP13: case DUP14: case DUP15: case DUP16:{ int n = op.val() - OpCode.DUP1.val() + 1; DataWord word_1 = stack.get(stack.size() - n); program.stackPush(word_1.clone()); program.step(); } break; case SWAP1: case SWAP2: case SWAP3: case SWAP4: case SWAP5: case SWAP6: case SWAP7: case SWAP8: case SWAP9: case SWAP10: case SWAP11: case SWAP12: case SWAP13: case SWAP14: case SWAP15: case SWAP16:{ int n = op.val() - OpCode.SWAP1.val() + 2; stack.swap(stack.size() - 1, stack.size() - n); program.step(); } break; case LOG0: case LOG1: case LOG2: case LOG3: case LOG4: { DataWord address = program.getOwnerAddress(); DataWord memStart = stack.pop(); DataWord memOffset = stack.pop(); int nTopics = op.val() - OpCode.LOG0.val(); List<DataWord> topics = new ArrayList<>(); for (int i = 0; i < nTopics; ++i) { DataWord topic = stack.pop(); topics.add(topic); } byte[] data = program.memoryChunk(memStart.intValueSafe(), memOffset.intValueSafe()); LogInfo logInfo = new LogInfo(address.getLast20Bytes(), topics, data); if (logger.isInfoEnabled()) hint = logInfo.toString(); program.getResult().addLogInfo(logInfo); program.step(); } break; case MLOAD: { DataWord addr = program.stackPop(); DataWord data = program.memoryLoad(addr); if (logger.isInfoEnabled()) hint = "data: " + data; program.stackPush(data); program.step(); } break; case MSTORE: { DataWord addr = program.stackPop(); DataWord value = program.stackPop(); if (logger.isInfoEnabled()) hint = "addr: " + addr + " value: " + value; program.memorySave(addr, value); program.step(); } break; case MSTORE8: { DataWord addr = program.stackPop(); DataWord value = program.stackPop(); byte[] byteVal = {value.getData()[31]}; program.memorySave(addr.intValueSafe(), byteVal); program.step(); } break; case SLOAD: { DataWord key = program.stackPop(); DataWord val = program.storageLoad(key); if (logger.isInfoEnabled()) hint = "key: " + key + " value: " + val; if (val == null) val = key.and(DataWord.ZERO); program.stackPush(val); program.step(); } break; case SSTORE: { DataWord addr = program.stackPop(); DataWord value = program.stackPop(); if (logger.isInfoEnabled()) hint = "[" + program.getOwnerAddress().toPrefixString() + "] key: " + addr + " value: " + value; program.storageSave(addr, value); program.step(); } break; case JUMP: { DataWord pos = program.stackPop(); int nextPC = program.verifyJumpDest(pos); if (logger.isInfoEnabled()) hint = "~> " + nextPC; program.setPC(nextPC); } break; case JUMPI: { DataWord pos = program.stackPop(); DataWord cond = program.stackPop(); if (!cond.isZero()) { int nextPC = program.verifyJumpDest(pos); if (logger.isInfoEnabled()) hint = "~> " + nextPC; program.setPC(nextPC); } else { program.step(); } } break; case PC: { int pc = program.getPC(); DataWord pcWord = new DataWord(pc); if (logger.isInfoEnabled()) hint = pcWord.toString(); program.stackPush(pcWord); program.step(); } break; case MSIZE: { int memSize = program.getMemSize(); DataWord wordMemSize = new DataWord(memSize); if (logger.isInfoEnabled()) hint = "" + memSize; program.stackPush(wordMemSize); program.step(); } break; case GAS: { DataWord gas = program.getGas(); if (logger.isInfoEnabled()) hint = "" + gas; program.stackPush(gas); program.step(); } break; case PUSH1: case PUSH2: case PUSH3: case PUSH4: case PUSH5: case PUSH6: case PUSH7: case PUSH8: case PUSH9: case PUSH10: case PUSH11: case PUSH12: case PUSH13: case PUSH14: case PUSH15: case PUSH16: case PUSH17: case PUSH18: case PUSH19: case PUSH20: case PUSH21: case PUSH22: case PUSH23: case PUSH24: case PUSH25: case PUSH26: case PUSH27: case PUSH28: case PUSH29: case PUSH30: case PUSH31: case PUSH32: { program.step(); int nPush = op.val() - PUSH1.val() + 1; byte[] data = program.sweep(nPush); if (logger.isInfoEnabled()) hint = "" + Hex.toHexString(data); program.stackPush(data); } break; case JUMPDEST: { program.step(); } break; case CREATE: { DataWord value = program.stackPop(); DataWord inOffset = program.stackPop(); DataWord inSize = program.stackPop(); if (logger.isInfoEnabled()) logger.info(logString, String.format("%5s", "[" + program.getPC() + "]"), String.format("%-12s", op.name()), program.getGas().value(), program.getCallDeep(), hint); program.createContract(value, inOffset, inSize); program.step(); } break; case CALL: case CALLCODE: case DELEGATECALL: { program.stackPop(); // use adjustedCallGas instead of requested DataWord codeAddress = program.stackPop(); DataWord value = !op.equals(DELEGATECALL) ? program.stackPop() : DataWord.ZERO; if( !value.isZero()) { adjustedCallGas.add(new DataWord(gasCosts.getSTIPEND_CALL())); } DataWord inDataOffs = program.stackPop(); DataWord inDataSize = program.stackPop(); DataWord outDataOffs = program.stackPop(); DataWord outDataSize = program.stackPop(); if (logger.isInfoEnabled()) { hint = "addr: " + Hex.toHexString(codeAddress.getLast20Bytes()) + " gas: " + adjustedCallGas.shortHex() + " inOff: " + inDataOffs.shortHex() + " inSize: " + inDataSize.shortHex(); logger.info(logString, String.format("%5s", "[" + program.getPC() + "]"), String.format("%-12s", op.name()), program.getGas().value(), program.getCallDeep(), hint); } program.memoryExpand(outDataOffs, outDataSize); MessageCall msg = new MessageCall( MsgType.fromOpcode(op), adjustedCallGas, codeAddress, value, inDataOffs, inDataSize, outDataOffs, outDataSize); PrecompiledContracts.PrecompiledContract contract = PrecompiledContracts.getContractForAddress(codeAddress); if (op.equals(CALL)) { program.getResult().addTouchAccount(codeAddress.getLast20Bytes()); } if (contract != null) { program.callToPrecompiledAddress(msg, contract); } else { program.callToAddress(msg); } program.step(); } break; case RETURN: { DataWord offset = program.stackPop(); DataWord size = program.stackPop(); byte[] hReturn = program.memoryChunk(offset.intValueSafe(), size.intValueSafe()); program.setHReturn(hReturn); if (logger.isInfoEnabled()) hint = "data: " + Hex.toHexString(hReturn) + " offset: " + offset.value() + " size: " + size.value(); program.step(); program.stop(); } break; case SUICIDE: { DataWord address = program.stackPop(); program.suicide(address); program.getResult().addTouchAccount(address.getLast20Bytes()); if (logger.isInfoEnabled()) hint = "address: " + Hex.toHexString(program.getOwnerAddress().getLast20Bytes()); program.stop(); } break; default: break; } program.setPreviouslyExecutedOp(op.val()); if (logger.isInfoEnabled() && !op.equals(CALL) && !op.equals(CALLCODE) && !op.equals(CREATE)) logger.info(logString, String.format("%5s", "[" + program.getPC() + "]"), String.format("%-12s", op.name()), program.getGas().value(), program.getCallDeep(), hint); vmCounter++; } catch (RuntimeException e) { logger.warn("VM halted: [{}]", e); program.spendAllGas(); program.resetFutureRefund(); program.stop(); throw e; } finally { program.fullTrace(); } } public void play(Program program) { try { if (vmHook != null) { vmHook.startPlay(program); } if (program.byTestingSuite()) return; while (!program.isStopped()) { this.step(program); } } catch (RuntimeException e) { program.setRuntimeFailure(e); } catch (StackOverflowError soe){ logger.error("\n !!! StackOverflowError: update your java run command with -Xss2M !!!\n", soe); System.exit(-1); } finally { if (vmHook != null) { vmHook.stopPlay(program); } } } public static void setVmHook(VMHook vmHook) { VM.vmHook = vmHook; } /** * Utility to calculate new total memory size needed for an operation. * <br/> Basically just offset + size, unless size is 0, in which case the result is also 0. * * @param offset starting position of the memory * @param size number of bytes needed * @return offset + size, unless size is 0. In that case memNeeded is also 0. */ private static BigInteger memNeeded(DataWord offset, DataWord size) { return size.isZero() ? BigInteger.ZERO : offset.value().add(size.value()); } /* * Dumping the VM state at the current operation in various styles * - standard Not Yet Implemented * - standard+ (owner address, program counter, operation, gas left) * - pretty (stack, memory, storage, level, contract, * vmCounter, internalSteps, operation gasBefore, gasCost, memWords) */ private void dumpLine(OpCode op, long gasBefore, long gasCost, long memWords, Program program) { if (config.dumpStyle().equals("standard+")) { switch (op) { case STOP: case RETURN: case SUICIDE: ContractDetails details = program.getStorage() .getContractDetails(program.getOwnerAddress().getLast20Bytes()); List<DataWord> storageKeys = new ArrayList<>(details.getStorage().keySet()); Collections.sort(storageKeys); for (DataWord key : storageKeys) { dumpLogger.trace("{} {}", Hex.toHexString(key.getNoLeadZeroesData()), Hex.toHexString(details.getStorage().get(key).getNoLeadZeroesData())); } default: break; } String addressString = Hex.toHexString(program.getOwnerAddress().getLast20Bytes()); String pcString = Hex.toHexString(new DataWord(program.getPC()).getNoLeadZeroesData()); String opString = Hex.toHexString(new byte[]{op.val()}); String gasString = Hex.toHexString(program.getGas().getNoLeadZeroesData()); dumpLogger.trace("{} {} {} {}", addressString, pcString, opString, gasString); } else if (config.dumpStyle().equals("pretty")) { dumpLogger.trace(" STACK"); for (DataWord item : program.getStack()) { dumpLogger.trace("{}", item); } dumpLogger.trace(" MEMORY"); String memoryString = program.memoryToString(); if (!"".equals(memoryString)) dumpLogger.trace("{}", memoryString); dumpLogger.trace(" STORAGE"); ContractDetails details = program.getStorage() .getContractDetails(program.getOwnerAddress().getLast20Bytes()); List<DataWord> storageKeys = new ArrayList<>(details.getStorage().keySet()); Collections.sort(storageKeys); for (DataWord key : storageKeys) { dumpLogger.trace("{}: {}", key.shortHex(), details.getStorage().get(key).shortHex()); } int level = program.getCallDeep(); String contract = Hex.toHexString(program.getOwnerAddress().getLast20Bytes()); String internalSteps = String.format("%4s", Integer.toHexString(program.getPC())).replace(' ', '0').toUpperCase(); dumpLogger.trace("{} | {} | #{} | {} : {} | {} | -{} | {}x32", level, contract, vmCounter, internalSteps, op, gasBefore, gasCost, memWords); } } }