From da9fc3d39170ce424924c64f1658b1005c143c23 Mon Sep 17 00:00:00 2001 From: alon muroch Date: Tue, 2 Dec 2014 11:36:37 +0100 Subject: [PATCH] vmIOandFlowOperationsTest --- .../org/ethereum/jsontestsuite/TestCase.java | 16 +- .../ethereum/jsontestsuite/TestRunner.java | 451 ++++++++++-------- .../org/ethereum/jsontestsuite/TestSuite.java | 1 + .../main/java/org/ethereum/vm/DataWord.java | 2 + .../main/java/org/ethereum/vm/Program.java | 23 +- .../src/main/java/org/ethereum/vm/VM.java | 18 +- .../ethereum/jsontestsuite/GitHubVMTest.java | 3 +- 7 files changed, 292 insertions(+), 222 deletions(-) diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestCase.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestCase.java index 4523d248..9769e87c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestCase.java +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestCase.java @@ -55,13 +55,21 @@ public class TestCase { JSONObject envJSON = (JSONObject)testCaseJSONObj.get("env"); JSONObject execJSON = (JSONObject)testCaseJSONObj.get("exec"); JSONObject preJSON = (JSONObject)testCaseJSONObj.get("pre"); - JSONObject postJSON = (JSONObject)testCaseJSONObj.get("post"); - JSONArray callCreates = (JSONArray)testCaseJSONObj.get("callcreates"); + JSONObject postJSON = new JSONObject(); + if(testCaseJSONObj.containsKey("post")) // in cases where there is no post dictionary (when testing for exceptions for example) + postJSON = (JSONObject)testCaseJSONObj.get("post"); + JSONArray callCreates = new JSONArray(); + if(testCaseJSONObj.containsKey("callcreates")) + callCreates = (JSONArray)testCaseJSONObj.get("callcreates"); - String gasString = testCaseJSONObj.get("gas").toString(); + String gasString = "0"; + if(testCaseJSONObj.containsKey("gas")) + gasString = testCaseJSONObj.get("gas").toString(); this.gas = ByteUtil.bigIntegerToBytes(new BigInteger(gasString)); - String outString = testCaseJSONObj.get("out").toString(); + String outString = null; + if(testCaseJSONObj.containsKey("out")) + outString = testCaseJSONObj.get("out").toString(); if (outString != null && outString.length() > 2) this.out = Hex.decode(outString.substring(2)); else diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java index 427184f3..ce6c3dc6 100644 --- a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java @@ -99,218 +99,249 @@ public class TestRunner { /* 4. run VM */ VM vm = new VM(); Program program = new Program(exec.getCode(), programInvoke); - while(!program.isStopped()) - vm.step(program); - + boolean vmDidThrowAnEception = false; + RuntimeException e = null; + try { + while(!program.isStopped()) + vm.step(program); + } + catch (RuntimeException ex) { + vmDidThrowAnEception = true; + e = ex; + } program.saveProgramTraceToFile(testCase.getName()); + + if(testCase.getPost().size() == 0) { + if(vmDidThrowAnEception != true) { + String output = + String.format("VM was expected to throw an exception"); + logger.info(output); + results.add(output); + } + else + logger.info("VM did throw an exception: " + e.toString()); + } + else { + if(vmDidThrowAnEception) { + String output = + String.format("VM threw an unexpected exception: " + e.toString()); + logger.info(output); + results.add(output); + return results; + } + + this.trace = program.getProgramTrace(); + + System.out.println("--------- POST --------"); + /* 5. Assert Post values */ + for (ByteArrayWrapper key : testCase.getPost().keySet()) { + + AccountState accountState = testCase.getPost().get(key); + + long expectedNonce = accountState.getNonceLong(); + BigInteger expectedBalance = accountState.getBigIntegerBalance(); + byte[] expectedCode = accountState.getCode(); + + boolean accountExist = (null != repository.getAccountState(key.getData())); + if (!accountExist) { + + String output = + String.format("The expected account does not exist. key: [ %s ]", + Hex.toHexString(key.getData())); + logger.info(output); + results.add(output); + continue; + } + + long actualNonce = repository.getNonce(key.getData()).longValue(); + BigInteger actualBalance = repository.getBalance(key.getData()); + byte[] actualCode = repository.getCode(key.getData()); + if (actualCode == null) actualCode = "".getBytes(); + + if (expectedNonce != actualNonce) { + + String output = + String.format("The nonce result is different. key: [ %s ], expectedNonce: [ %d ] is actualNonce: [ %d ] ", + Hex.toHexString(key.getData()), expectedNonce, actualNonce); + logger.info(output); + results.add(output); + } + + if (!expectedBalance.equals(actualBalance)) { + + String output = + String.format("The balance result is different. key: [ %s ], expectedBalance: [ %s ] is actualBalance: [ %s ] ", + Hex.toHexString(key.getData()), expectedBalance.toString(), actualBalance.toString()); + logger.info(output); + results.add(output); + } + + if (!Arrays.equals(expectedCode, actualCode)) { + + String output = + String.format("The code result is different. account: [ %s ], expectedCode: [ %s ] is actualCode: [ %s ] ", + Hex.toHexString(key.getData()), + Hex.toHexString(expectedCode), + Hex.toHexString(actualCode)); + logger.info(output); + results.add(output); + } + + // assert storage + Map storage = accountState.getStorage(); + for (ByteArrayWrapper storageKey : storage.keySet()) { + + byte[] expectedStValue = storage.get(storageKey).getData(); + + ContractDetails contractDetails = + program.getResult().getRepository().getContractDetails(accountState.getAddress()); + + if (contractDetails == null) { + + String output = + String.format("Storage raw doesn't exist: key [ %s ], expectedValue: [ %s ]", + Hex.toHexString(storageKey.getData()), + Hex.toHexString(expectedStValue) + ); + logger.info(output); + results.add(output); + continue; + } + + Map testStorage = contractDetails.getStorage(); + DataWord actualValue = testStorage.get(new DataWord(storageKey.getData())); - this.trace = program.getProgramTrace(); - - System.out.println("--------- POST --------"); - /* 5. Assert Post values */ - for (ByteArrayWrapper key : testCase.getPost().keySet()) { - - AccountState accountState = testCase.getPost().get(key); - - long expectedNonce = accountState.getNonceLong(); - BigInteger expectedBalance = accountState.getBigIntegerBalance(); - byte[] expectedCode = accountState.getCode(); - - boolean accountExist = (null != repository.getAccountState(key.getData())); - if (!accountExist) { - - String output = - String.format("The expected account does not exist. key: [ %s ]", - Hex.toHexString(key.getData())); - logger.info(output); - results.add(output); - continue; - } - - long actualNonce = repository.getNonce(key.getData()).longValue(); - BigInteger actualBalance = repository.getBalance(key.getData()); - byte[] actualCode = repository.getCode(key.getData()); - if (actualCode == null) actualCode = "".getBytes(); - - if (expectedNonce != actualNonce) { - - String output = - String.format("The nonce result is different. key: [ %s ], expectedNonce: [ %d ] is actualNonce: [ %d ] ", - Hex.toHexString(key.getData()), expectedNonce, actualNonce); - logger.info(output); - results.add(output); - } - - if (!expectedBalance.equals(actualBalance)) { - - String output = - String.format("The balance result is different. key: [ %s ], expectedBalance: [ %s ] is actualBalance: [ %s ] ", - Hex.toHexString(key.getData()), expectedBalance.toString(), actualBalance.toString()); - logger.info(output); - results.add(output); - } - - if (!Arrays.equals(expectedCode, actualCode)) { - - String output = - String.format("The code result is different. account: [ %s ], expectedCode: [ %s ] is actualCode: [ %s ] ", - Hex.toHexString(key.getData()), - Hex.toHexString(expectedCode), - Hex.toHexString(actualCode)); - logger.info(output); - results.add(output); - } - - // assert storage - Map storage = accountState.getStorage(); - for (ByteArrayWrapper storageKey : storage.keySet()) { - - byte[] expectedStValue = storage.get(storageKey).getData(); - - ContractDetails contractDetails = - program.getResult().getRepository().getContractDetails(accountState.getAddress()); - - if (contractDetails == null) { - - String output = - String.format("Storage raw doesn't exist: key [ %s ], expectedValue: [ %s ]", - Hex.toHexString(storageKey.getData()), - Hex.toHexString(expectedStValue) - ); - logger.info(output); - results.add(output); - continue; - } - - Map testStorage = contractDetails.getStorage(); - DataWord actualValue = testStorage.get(new DataWord(storageKey.getData())); - - if (actualValue == null || - !Arrays.equals(expectedStValue, actualValue.getNoLeadZeroesData())) { - - String output = - String.format("Storage value different: key [ %s ], expectedValue: [ %s ], actualValue: [ %s ]", - Hex.toHexString(storageKey.getData()), - Hex.toHexString(expectedStValue), - actualValue == null ? "" : Hex.toHexString(actualValue.getNoLeadZeroesData())); - logger.info(output); - results.add(output); - } - } - } - - // TODO: assert that you have no extra accounts in the repository - // TODO: -> basically the deleted by suicide should be deleted - // TODO: -> and no unexpected created - - List resultCallCreates = - program.getResult().getCallCreateList(); - - // assert call creates - for (int i = 0; i < testCase.getCallCreateList().size(); ++i) { - - org.ethereum.vm.CallCreate resultCallCreate = null; - if (resultCallCreates != null && resultCallCreates.size() > i) { - resultCallCreate = resultCallCreates.get(i); - } - - CallCreate expectedCallCreate = testCase.getCallCreateList().get(i); - - if (resultCallCreate == null && expectedCallCreate != null) { - - String output = - String.format("Missing call/create invoke: to: [ %s ], data: [ %s ], gas: [ %s ], value: [ %s ]", - Hex.toHexString(expectedCallCreate.getDestination()), - Hex.toHexString(expectedCallCreate.getData()), - Hex.toHexString(expectedCallCreate.getGasLimit()), - Hex.toHexString(expectedCallCreate.getValue())); - logger.info(output); - results.add(output); - - continue; - } - - boolean assertDestination = Arrays.equals( - expectedCallCreate.getDestination(), - resultCallCreate.getDestination()); - if (!assertDestination) { - - String output = - String.format("Call/Create destination is different. Expected: [ %s ], result: [ %s ]", - Hex.toHexString(expectedCallCreate.getDestination()), - Hex.toHexString(resultCallCreate.getDestination())); - logger.info(output); - results.add(output); - } - - boolean assertData = Arrays.equals( - expectedCallCreate.getData(), - resultCallCreate.getData()); - if (!assertData) { - - String output = - String.format("Call/Create data is different. Expected: [ %s ], result: [ %s ]", - Hex.toHexString(expectedCallCreate.getData()), - Hex.toHexString(resultCallCreate.getData())); - logger.info(output); - results.add(output); - } - - boolean assertGasLimit = Arrays.equals( - expectedCallCreate.getGasLimit(), - resultCallCreate.getGasLimit()); - if (!assertGasLimit) { - String output = - String.format("Call/Create gasLimit is different. Expected: [ %s ], result: [ %s ]", - Hex.toHexString(expectedCallCreate.getGasLimit()), - Hex.toHexString(resultCallCreate.getGasLimit())); - logger.info(output); - results.add(output); - } - - boolean assertValue = Arrays.equals( - expectedCallCreate.getValue(), - resultCallCreate.getValue()); - if (!assertValue) { - String output = - String.format("Call/Create value is different. Expected: [ %s ], result: [ %s ]", - Hex.toHexString(expectedCallCreate.getValue()), - Hex.toHexString(resultCallCreate.getValue())); - logger.info(output); - results.add(output); - } - } - - // assert out - byte[] expectedHReturn = testCase.getOut(); - byte[] actualHReturn = ByteUtil.EMPTY_BYTE_ARRAY; - if (program.getResult().getHReturn() != null) { - actualHReturn = program.getResult().getHReturn().array(); - } - - if (!Arrays.equals(expectedHReturn, actualHReturn)) { - - String output = - String.format("HReturn is different. Expected hReturn: [ %s ], actual hReturn: [ %s ]", - Hex.toHexString(expectedHReturn), - Hex.toHexString(actualHReturn)); - logger.info(output); - results.add(output); - } - - // assert gas - BigInteger expectedGas = new BigInteger(testCase.getGas()); - BigInteger actualGas = new BigInteger(gas).subtract(BigInteger.valueOf(program.getResult().getGasUsed())); - - if (!expectedGas.equals(actualGas)) { - - String output = - String.format("Gas remaining is different. Expected gas remaining: [ %s ], actual gas remaining: [ %s ]", - expectedGas.toString() , - actualGas.toString()); - logger.info(output); - results.add(output); - } + if (actualValue == null || + !Arrays.equals(expectedStValue, actualValue.getNoLeadZeroesData())) { + + String output = + String.format("Storage value different: key [ %s ], expectedValue: [ %s ], actualValue: [ %s ]", + Hex.toHexString(storageKey.getData()), + Hex.toHexString(expectedStValue), + actualValue == null ? "" : Hex.toHexString(actualValue.getNoLeadZeroesData())); + logger.info(output); + results.add(output); + } + } + } + + // TODO: assert that you have no extra accounts in the repository + // TODO: -> basically the deleted by suicide should be deleted + // TODO: -> and no unexpected created + + List resultCallCreates = + program.getResult().getCallCreateList(); + + // assert call creates + for (int i = 0; i < testCase.getCallCreateList().size(); ++i) { + + org.ethereum.vm.CallCreate resultCallCreate = null; + if (resultCallCreates != null && resultCallCreates.size() > i) { + resultCallCreate = resultCallCreates.get(i); + } + + CallCreate expectedCallCreate = testCase.getCallCreateList().get(i); + + if (resultCallCreate == null && expectedCallCreate != null) { + + String output = + String.format("Missing call/create invoke: to: [ %s ], data: [ %s ], gas: [ %s ], value: [ %s ]", + Hex.toHexString(expectedCallCreate.getDestination()), + Hex.toHexString(expectedCallCreate.getData()), + Hex.toHexString(expectedCallCreate.getGasLimit()), + Hex.toHexString(expectedCallCreate.getValue())); + logger.info(output); + results.add(output); + + continue; + } + + boolean assertDestination = Arrays.equals( + expectedCallCreate.getDestination(), + resultCallCreate.getDestination()); + if (!assertDestination) { + + String output = + String.format("Call/Create destination is different. Expected: [ %s ], result: [ %s ]", + Hex.toHexString(expectedCallCreate.getDestination()), + Hex.toHexString(resultCallCreate.getDestination())); + logger.info(output); + results.add(output); + } + + boolean assertData = Arrays.equals( + expectedCallCreate.getData(), + resultCallCreate.getData()); + if (!assertData) { + + String output = + String.format("Call/Create data is different. Expected: [ %s ], result: [ %s ]", + Hex.toHexString(expectedCallCreate.getData()), + Hex.toHexString(resultCallCreate.getData())); + logger.info(output); + results.add(output); + } + + boolean assertGasLimit = Arrays.equals( + expectedCallCreate.getGasLimit(), + resultCallCreate.getGasLimit()); + if (!assertGasLimit) { + String output = + String.format("Call/Create gasLimit is different. Expected: [ %s ], result: [ %s ]", + Hex.toHexString(expectedCallCreate.getGasLimit()), + Hex.toHexString(resultCallCreate.getGasLimit())); + logger.info(output); + results.add(output); + } + + boolean assertValue = Arrays.equals( + expectedCallCreate.getValue(), + resultCallCreate.getValue()); + if (!assertValue) { + String output = + String.format("Call/Create value is different. Expected: [ %s ], result: [ %s ]", + Hex.toHexString(expectedCallCreate.getValue()), + Hex.toHexString(resultCallCreate.getValue())); + logger.info(output); + results.add(output); + } + } + + // assert out + byte[] expectedHReturn = testCase.getOut(); + byte[] actualHReturn = ByteUtil.EMPTY_BYTE_ARRAY; + if (program.getResult().getHReturn() != null) { + actualHReturn = program.getResult().getHReturn().array(); + } + + if (!Arrays.equals(expectedHReturn, actualHReturn)) { + + String output = + String.format("HReturn is different. Expected hReturn: [ %s ], actual hReturn: [ %s ]", + Hex.toHexString(expectedHReturn), + Hex.toHexString(actualHReturn)); + logger.info(output); + results.add(output); + } + + // assert gas + BigInteger expectedGas = new BigInteger(testCase.getGas()); + BigInteger actualGas = new BigInteger(gas).subtract(BigInteger.valueOf(program.getResult().getGasUsed())); + + if (!expectedGas.equals(actualGas)) { + + String output = + String.format("Gas remaining is different. Expected gas remaining: [ %s ], actual gas remaining: [ %s ]", + expectedGas.toString() , + actualGas.toString()); + logger.info(output); + results.add(output); + } + /* + * end of if(testCase.getPost().size() == 0) + */ + } + return results; } finally { repository.close(); diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestSuite.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestSuite.java index e80c7688..accdb36b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestSuite.java +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestSuite.java @@ -21,6 +21,7 @@ public class TestSuite { for (Object key: testCaseJSONObj.keySet()){ Object testCaseJSON = testCaseJSONObj.get(key); + TestCase testCase = new TestCase(key.toString(), (JSONObject) testCaseJSON); testList.add(testCase); diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java b/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java index 2c12a01c..59bbc949 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java @@ -78,6 +78,8 @@ public class DataWord implements Comparable { */ public int intValue() { BigDecimal tmpValue = new BigDecimal(this.value()); + if(this.bytesOccupied() > 4) + return Integer.MAX_VALUE; return tmpValue.intValueExact(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java index 8b5f2173..eb11c6f9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java @@ -47,6 +47,7 @@ public class Program { byte[] ops; int pc = 0; byte lastOp = 0; + byte previouslyExecutedOp = 0; boolean stopped = false; ProgramInvoke invokeData; @@ -76,10 +77,30 @@ public class Program { return ops[pc]; } + /** + * Last Op can only be set publicly (no getLastOp method), is used for logging + * @param op + */ public void setLastOp(byte op) { this.lastOp = op; } - + + /** + * Should be set only after the OP is fully executed + * @param op + */ + public void setPreviouslyExecutedOp(byte op) { + this.previouslyExecutedOp = op; + } + + /** + * returns the last fully executed OP + * @return + */ + public byte getPreviouslyExecutedOp() { + return this.previouslyExecutedOp; + } + public void stackPush(byte[] data) { DataWord stackWord = new DataWord(data); stack.push(stackWord); diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java index 2d731cae..cac7ee80 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -821,9 +821,11 @@ public class VM { case JUMP:{ DataWord pos = program.stackPop(); int nextPC = pos.intValue(); // possible overflow - if (nextPC != 0 && program.getOp(nextPC) != OpCode.JUMPDEST.val()) - throw program.new BadJumpDestinationException(); - + if(program.getPreviouslyExecutedOp() < OpCode.PUSH1.val() || program.getPreviouslyExecutedOp() > OpCode.PUSH32.val()) { + if (nextPC != 0 && program.getOp(nextPC) != OpCode.JUMPDEST.val()) + throw program.new BadJumpDestinationException(); + } + if (logger.isInfoEnabled()) hint = "~> " + nextPC; @@ -836,8 +838,10 @@ public class VM { if (!cond.isZero()) { int nextPC = pos.intValue(); // possible overflow - if (nextPC != 0 && program.getOp(nextPC) != OpCode.JUMPDEST.val()) - throw program.new BadJumpDestinationException(); + if(program.getPreviouslyExecutedOp() < OpCode.PUSH1.val() || program.getPreviouslyExecutedOp() > OpCode.PUSH32.val()) { + if (nextPC != 0 && program.getOp(nextPC) != OpCode.JUMPDEST.val()) + throw program.new BadJumpDestinationException(); + } // todo: in case destination is not JUMPDEST, check if prev was strict push // todo: in EP: (ii) If a jump is preceded by a push, no jumpdest required; @@ -971,6 +975,8 @@ public class VM { break; } + program.setPreviouslyExecutedOp(op.val()); + if (logger.isInfoEnabled() && !op.equals(CALL) && !op.equals(CREATE)) logger.info(logString, stepBefore, String.format("%-12s", @@ -1003,7 +1009,7 @@ public class VM { program.spendGas(GasCost.TX_NO_ZERO_DATA * nonZeroesVals, "DATA"); program.spendGas(GasCost.TX_ZERO_DATA * zeroVals, "DATA"); } - + while(!program.isStopped()) this.step(program); diff --git a/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubVMTest.java b/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubVMTest.java index f47f7e89..b40ca275 100644 --- a/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubVMTest.java +++ b/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubVMTest.java @@ -11,6 +11,7 @@ import org.junit.runners.MethodSorters; public class GitHubVMTest { @Test // testing full suite + @Ignore public void testArithmeticFromGitHub() throws ParseException { String json = JSONReader.loadJSON("VMTests/vmArithmeticTest.json"); @@ -18,6 +19,7 @@ public class GitHubVMTest { } @Test // testing full suite + @Ignore public void testBitwiseLogicOperationFromGitHub() throws ParseException { String json = JSONReader.loadJSON("VMTests/vmBitwiseLogicOperationTest.json"); @@ -41,7 +43,6 @@ public class GitHubVMTest { } @Test // testing full suite - @Ignore public void testIOandFlowOperationsFromGitHub() throws ParseException { String json = JSONReader.loadJSON("vmtests/vmIOandFlowOperationsTest.json");