From 14ba667d46428c8496a7299ff7395e285b0d1f21 Mon Sep 17 00:00:00 2001 From: romanman Date: Thu, 3 Jul 2014 16:58:40 +0100 Subject: [PATCH] JSON Testing introduced: + JSON defined tests to run + VM fixes and adaptation for recent changes --- .../org/ethereum/db/ByteArrayWrapper.java | 6 + .../main/java/org/ethereum/db/Repository.java | 9 +- .../ethereum/jsontestsuite/AccountState.java | 110 +++++++ .../ethereum/jsontestsuite/CallCreate.java | 76 +++++ .../java/org/ethereum/jsontestsuite/Env.java | 89 ++++++ .../java/org/ethereum/jsontestsuite/Exec.java | 114 +++++++ .../org/ethereum/jsontestsuite/Helper.java | 66 ++++ .../org/ethereum/jsontestsuite/TestCase.java | 131 ++++++++ .../ethereum/jsontestsuite/TestRunner.java | 287 ++++++++++++++++++ .../main/java/org/ethereum/util/ByteUtil.java | 21 ++ .../main/java/org/ethereum/vm/CallCreate.java | 40 +++ .../main/java/org/ethereum/vm/Program.java | 195 +++++++----- .../java/org/ethereum/vm/ProgramInvoke.java | 1 + .../org/ethereum/vm/ProgramInvokeImpl.java | 21 +- .../ethereum/vm/ProgramInvokeMockImpl.java | 14 + .../java/org/ethereum/vm/ProgramResult.java | 24 ++ .../src/main/java/org/ethereum/vm/VM.java | 2 + .../jsontestsuite/JSONTestSuiteTest.java | 175 +++++++++++ 18 files changed, 1305 insertions(+), 76 deletions(-) create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/AccountState.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/CallCreate.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Env.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Exec.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Helper.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestCase.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/vm/CallCreate.java create mode 100644 ethereumj-core/src/test/java/org/ethereum/jsontestsuite/JSONTestSuiteTest.java diff --git a/ethereumj-core/src/main/java/org/ethereum/db/ByteArrayWrapper.java b/ethereumj-core/src/main/java/org/ethereum/db/ByteArrayWrapper.java index f92532d3..cda96800 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/ByteArrayWrapper.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/ByteArrayWrapper.java @@ -3,6 +3,7 @@ package org.ethereum.db; import java.util.Arrays; import org.ethereum.util.FastByteComparisons; +import org.spongycastle.util.encoders.Hex; /** * www.ethereumJ.com @@ -45,4 +46,9 @@ public class ByteArrayWrapper implements Comparable { public byte[] getData() { return data; } + + @Override + public String toString() { + return Hex.toHexString(data); + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/db/Repository.java b/ethereumj-core/src/main/java/org/ethereum/db/Repository.java index b2dd926f..c757c493 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/Repository.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/Repository.java @@ -138,8 +138,10 @@ public class Repository { public BigInteger addBalance(byte[] address, BigInteger value) { AccountState state = getAccountState(address); - if (state == null) - return BigInteger.ZERO; + + if (state == null){ + state = createAccount(address); + } BigInteger newBalance = state.addToBalance(value); @@ -295,7 +297,8 @@ public class Repository { byte[] code = details.getCode(); Map storage = details.getStorage(); - String accountLine = JSONHelper.dumpLine(key.getData(), nonce.toByteArray(), + String accountLine = JSONHelper.dumpLine(key.getData(), + nonce.toByteArray(), balance.toByteArray(), stateRoot, codeHash, code, storage); bw.write(accountLine); diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/AccountState.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/AccountState.java new file mode 100644 index 00000000..9ff9735f --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/AccountState.java @@ -0,0 +1,110 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.util.ByteUtil; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 28/06/2014 10:25 + */ + +public class AccountState { + + byte[] address; + byte[] balance; + byte[] code; + byte[] nonce; + + Map storage = new HashMap<>(); + + + public AccountState(byte[] address, JSONObject accountState) { + + this.address = address; + String balance = accountState.get("balance").toString(); + JSONArray code = (JSONArray)accountState.get("code"); + String nonce = accountState.get("nonce").toString(); + + JSONObject store = (JSONObject)accountState.get("storage"); + + this.balance = new BigInteger(balance).toByteArray(); + this.code = Helper.parseDataArray(code); + this.nonce = new BigInteger(nonce).toByteArray(); + + int size = store.keySet().size(); + Object[] keys = store.keySet().toArray(); + for (int i = 0; i < size; ++i){ + + String keyS = keys[i].toString(); + Object valObject = store.get(keys[i]); + byte[] valBytes = Helper.parseDataArray((JSONArray)valObject); + + ByteArrayWrapper key; + boolean hexVal = Pattern.matches("0[xX][0-9a-fA-F]+", keyS); + if (hexVal){ + key = new ByteArrayWrapper( Hex.decode(keyS.substring(2)) ); + } else { + + byte[] data = ByteUtil.bigIntegerToBytes( new BigInteger(keyS) ); + key = new ByteArrayWrapper( data ); + } + + ByteArrayWrapper value = + new ByteArrayWrapper(valBytes); + + storage.put(key, value); + } + + } + + public byte[] getAddress() { + return address; + } + + public byte[] getBalance() { + return balance; + } + + public BigInteger getBigIntegerBalance() { + return new BigInteger(balance); + } + + + public byte[] getCode() { + return code; + } + + public byte[] getNonce() { + return nonce; + } + + public long getNonceLong() { + return new BigInteger(nonce).longValue(); + } + + + public Map getStorage() { + return storage; + } + + @Override + public String toString() { + return "AccountState{" + + "address=" + Hex.toHexString(address) + + ", balance=" + Hex.toHexString(balance) + + ", code=" + Hex.toHexString(code) + + ", nonce=" + Hex.toHexString(nonce) + + ", storage=" + storage + + '}'; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/CallCreate.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/CallCreate.java new file mode 100644 index 00000000..640465e0 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/CallCreate.java @@ -0,0 +1,76 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.util.ByteUtil; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 28/06/2014 10:25 + */ + +public class CallCreate { + + byte[] data; + byte[] destination; + byte[] gasLimit; + byte[] value; + +/* e.g. + "data" : [ + ], + "destination" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "gasLimit" : 9792, + "value" : 74 +*/ + + public CallCreate(JSONObject callCreateJSON) { + + JSONArray data = (JSONArray)callCreateJSON.get("data"); + String destination = (String)callCreateJSON.get("destination"); + Long gasLimit = (Long)callCreateJSON.get("gasLimit"); + Long value = (Long)callCreateJSON.get("value"); + + this.data = Helper.parseDataArray(data); + this.destination = Hex.decode(destination); + this.gasLimit = ByteUtil.bigIntegerToBytes( BigInteger.valueOf(gasLimit) ); + this.value = ByteUtil.bigIntegerToBytes( BigInteger.valueOf(value) ); + } + + + public byte[] getData() { + return data; + } + + public byte[] getDestination() { + return destination; + } + + public byte[] getGasLimit() { + return gasLimit; + } + + public byte[] getValue() { + return value; + } + + @Override + public String toString() { + return "CallCreate{" + + "data=" + Hex.toHexString(data) + + ", destination=" + Hex.toHexString(destination) + + ", gasLimit=" + Hex.toHexString(gasLimit) + + ", value=" + Hex.toHexString(value) + + '}'; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Env.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Env.java new file mode 100644 index 00000000..d8527ce4 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Env.java @@ -0,0 +1,89 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.util.ByteUtil; +import org.json.simple.JSONObject; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Arrays; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 28/06/2014 10:23 + */ + +public class Env { + + private byte[] currentCoinbase; + private byte[] currentDifficlty; + private byte[] currentGasLimit; + private byte[] currentNumber; + private byte[] currentTimestamp; + private byte[] previousHash; + + + /* + e.g: + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "256", + "currentGasLimit" : "1000000", + "currentNumber" : "0", + "currentTimestamp" : 1, + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" + */ + public Env(JSONObject env) { + + String coinbase = env.get("currentCoinbase").toString(); + String difficulty = env.get("currentDifficulty").toString(); + String timestamp = env.get("currentTimestamp").toString(); + String number = env.get("currentNumber").toString(); + String gasLimit = env.get("currentGasLimit").toString(); + String prevHash = env.get("previousHash").toString(); + + this.currentCoinbase = Hex.decode(coinbase); + this.currentDifficlty = ByteUtil.bigIntegerToBytes( new BigInteger(difficulty) ); + this.currentGasLimit = ByteUtil.bigIntegerToBytes( new BigInteger(gasLimit) ); + this.currentNumber = ByteUtil.bigIntegerToBytes( new BigInteger(number) ); + this.currentTimestamp = ByteUtil.bigIntegerToBytes( new BigInteger(timestamp) ); + this.previousHash = Hex.decode(prevHash); + + } + + public byte[] getCurrentCoinbase() { + return currentCoinbase; + } + + public byte[] getCurrentDifficlty() { + return currentDifficlty; + } + + public byte[] getCurrentGasLimit() { + return currentGasLimit; + } + + public byte[] getCurrentNumber() { + return currentNumber; + } + + public byte[] getCurrentTimestamp() { + return currentTimestamp; + } + + public byte[] getPreviousHash() { + return previousHash; + } + + @Override + public String toString() { + return "Env{" + + "currentCoinbase=" + Hex.toHexString(currentCoinbase) + + ", currentDifficlty=" + Hex.toHexString(currentDifficlty) + + ", currentGasLimit=" + Hex.toHexString(currentGasLimit) + + ", currentNumber=" + Hex.toHexString(currentNumber) + + ", currentTimestamp=" + Hex.toHexString(currentTimestamp) + + ", previousHash=" + Hex.toHexString(previousHash) + + '}'; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Exec.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Exec.java new file mode 100644 index 00000000..8da81d64 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Exec.java @@ -0,0 +1,114 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.util.ByteUtil; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Arrays; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 28/06/2014 10:23 + */ + +public class Exec { + + private byte[] address; + private byte[] caller; + private byte[] data; + private byte[] code; + + private byte[] gas; + private byte[] gasPrice; + + private byte[] origin; + private byte[] value; + + /* + e.g: + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "data" : [ + ], + + "code" : [ 96,0,96,0,96,0,96,0,96,74,51,96,200,92,3,241 ], + + "gas" : 10000, + "gasPrice" : 100000000000000, + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : 1000000000000000000 + */ + public Exec(JSONObject exec) { + + String address = exec.get("address").toString(); + String caller = exec.get("caller").toString(); + + String gas = exec.get("gas").toString(); + String gasPrice = exec.get("gasPrice").toString(); + String origin = exec.get("origin").toString(); + + String value = exec.get("value").toString(); + + this.address = Hex.decode(address); + this.caller = Hex.decode(caller); + this.data = Helper.parseDataArray((JSONArray) exec.get("data")); + this.code = Helper.parseDataArray((JSONArray) exec.get("code")); + + this.gas = ByteUtil.bigIntegerToBytes( new BigInteger(gas) ); + this.gasPrice = ByteUtil.bigIntegerToBytes( new BigInteger(gasPrice) ); + + this.origin = Hex.decode(origin); + this.value = ByteUtil.bigIntegerToBytes(new BigInteger(value)); + } + + + public byte[] getAddress() { + return address; + } + + public byte[] getCaller() { + return caller; + } + + public byte[] getData() { + return data; + } + public byte[] getCode() { + return code; + } + + public byte[] getGas() { + return gas; + } + + public byte[] getGasPrice() { + return gasPrice; + } + + public byte[] getOrigin() { + return origin; + } + + public byte[] getValue() { + return value; + } + + + @Override + public String toString() { + return "Exec{" + + "address=" + Hex.toHexString(address) + + ", caller=" + Hex.toHexString(caller) + + ", data=" + Hex.toHexString(data) + + ", code=" + Hex.toHexString(data) + + ", gas=" + Hex.toHexString(gas) + + ", gasPrice=" + Hex.toHexString(gasPrice) + + ", origin=" + Hex.toHexString(origin) + + ", value=" + Hex.toHexString(value) + + '}'; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Helper.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Helper.java new file mode 100644 index 00000000..9b796dae --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Helper.java @@ -0,0 +1,66 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.util.ByteUtil; +import org.json.simple.JSONArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.util.regex.Pattern; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 28/06/2014 11:59 + */ +public class Helper { + + private static Logger logger = LoggerFactory.getLogger("misc"); + + public static byte[] parseDataArray(JSONArray valArray){ + + // value can be: + // 1. 324234 number + // 2. "0xAB3F23A" - hex string + // 3. "239472398472" - big number + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + for (int i = 0; i < valArray.size(); ++i){ + + Object val = valArray.get(i); + if (val instanceof String){ + + // Hex num + boolean hexVal = Pattern.matches("0[xX][0-9a-fA-F]+", val.toString()); + if (hexVal){ + String number = ((String) val).substring(2); + if (number.length() % 2 == 1) number = "0" + number; + byte[] data = Hex.decode(number); + try {bos.write(data);} catch (IOException e) { logger.error("should not happen", e);} + } else{ + + // BigInt num + boolean isNumeric = Pattern.matches("[0-9a-fA-F]+", val.toString()); + if (!isNumeric) throw new Error("Wrong test case JSON format"); + else{ + BigInteger value = new BigInteger(val.toString()); + try {bos.write(value.toByteArray());} catch (IOException e) + { logger.error("should not happen", e);} + } + } + } else if (val instanceof Long) { + + // Simple long + byte[] data = ByteUtil.bigIntegerToBytes( BigInteger.valueOf((Long)val) ); + try {bos.write(data);} catch (IOException e) { logger.error("should not happen", e);} + } else { + throw new Error("Wrong test case JSON format"); + } + } + return bos.toByteArray(); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestCase.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestCase.java new file mode 100644 index 00000000..fff7a8d2 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestCase.java @@ -0,0 +1,131 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.util.ByteUtil; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.ParseException; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.*; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 28/06/2014 10:22 + */ + +public class TestCase { + + // "env": { ... }, + private Env env; + + // "exec": { ... }, + private Exec exec; + + // "gas": { ... }, + private byte[] gas; + + // "out": { ... }, + private byte[] out; + + // "pre": { ... }, + private Map pre = new HashMap<>(); + + // "post": { ... }, + private Map post = new HashMap<>(); + + // "callcreates": { ... } + private List callCreateList = new ArrayList<>(); + + + public TestCase(JSONObject testCaseJSONObj) throws ParseException{ + + try { + + 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"); + + Long gasNum = (Long)testCaseJSONObj.get("gas"); + this.gas = ByteUtil.bigIntegerToBytes(BigInteger.valueOf(gasNum)); + + this.out = Helper.parseDataArray((JSONArray) testCaseJSONObj.get("out")); + + for (Object key : preJSON.keySet()){ + + byte[] keyBytes = Hex.decode(key.toString()); + AccountState accountState = + new AccountState(keyBytes, (JSONObject) preJSON.get(key)); + + pre.put(new ByteArrayWrapper(keyBytes), accountState); + } + + for (Object key : postJSON.keySet()){ + + byte[] keyBytes = Hex.decode(key.toString()); + AccountState accountState = + new AccountState(keyBytes, (JSONObject) postJSON.get(key)); + + post.put(new ByteArrayWrapper(keyBytes), accountState); + } + + for (int i = 0; i < callCreates.size(); ++i){ + + CallCreate cc = new CallCreate((JSONObject)callCreates.get(i)); + this.callCreateList.add(cc); + } + + this.env = new Env(envJSON); + this.exec = new Exec(execJSON); + + } catch (Throwable e) { + throw new ParseException(0, e); + } + } + + public Env getEnv() { + return env; + } + + public Exec getExec() { + return exec; + } + + public byte[] getGas() { + return gas; + } + + public byte[] getOut() { + return out; + } + + public Map getPre() { + return pre; + } + + public Map getPost() { + return post; + } + + public List getCallCreateList() { + return callCreateList; + } + + @Override + public String toString() { + return "TestCase{" + + "" + env + + ", " + exec + + ", gas=" + Hex.toHexString(gas) + + ", out=" + Hex.toHexString(out) + + ", pre=" + pre + + ", post=" + post + + ", callcreates=" + callCreateList + + '}'; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java new file mode 100644 index 00000000..0a0acab7 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java @@ -0,0 +1,287 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.db.ContractDetails; +import org.ethereum.db.Repository; +import org.ethereum.util.ByteUtil; +import org.ethereum.vm.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 02/07/2014 13:03 + */ + +public class TestRunner { + + private Logger logger = LoggerFactory.getLogger("JSONTest"); + private List results = new ArrayList<>(); + + public void runTestCase(TestCase testCase){ + + Repository repository = new Repository(); + + + /* 1. Store pre-exist accounts - Pre */ + for (ByteArrayWrapper key : testCase.getPre().keySet()){ + + AccountState accountState = testCase.getPre().get(key); + + repository.createAccount(key.getData()); + repository.saveCode(key.getData(), accountState.getCode()); + repository.addBalance(key.getData(), new BigInteger(accountState.getBalance())); + + for (long i = 0; i < accountState.getNonceLong(); ++i) + repository.increaseNonce(key.getData()); + } + + /* 2. Create ProgramInvoke - Env/Exec */ + Env env = testCase.getEnv(); + Exec exec = testCase.getExec(); + + byte[] address = exec.getAddress(); + byte[] origin = exec.getOrigin(); + byte[] caller = exec.getCaller(); + byte[] balance = ByteUtil.bigIntegerToBytes(repository.getBalance(exec.getAddress())); + byte[] gasPrice = exec.getGasPrice(); + byte[] gas = exec.getGas(); + byte[] callValue = exec.getValue(); + byte[] msgData = exec.getData(); + byte[] lastHash = env.getPreviousHash(); + byte[] coinbase = env.getCurrentCoinbase(); + long timestamp = new BigInteger(env.getCurrentTimestamp()).longValue(); + long number = new BigInteger(env.getCurrentNumber()).longValue(); + byte[] difficulty = env.getCurrentDifficlty(); + long gaslimit = new BigInteger(env.getCurrentGasLimit()).longValue(); + + ProgramInvoke programInvoke = new ProgramInvokeImpl(address, origin, caller, balance, + gasPrice, gas, callValue, msgData, lastHash, coinbase, + timestamp, number, difficulty, gaslimit, repository, true); + + /* 3. Create Program - exec.code */ + /* 4. run VM */ + VM vm = new VM(); + Program program = new Program(exec.getCode(), programInvoke); + + try { + while(!program.isStopped()) + vm.step(program); + } catch (RuntimeException e) { + program.setRuntimeFailure(e); + } + + /* 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. key: [ %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(storageKey.getData()); + + 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 (!Arrays.equals(expectedStValue, actualValue.getNoLeadZeroesData())){ + + String output = + String.format("Storage value different: key [ %s ], expectedValue: [ %s ], actualValue: [ %s ]", + Hex.toHexString(storageKey.getData()), + Hex.toHexString(actualValue.getData()), + Hex.toHexString(expectedStValue) + ); + logger.info(output); + results.add(output); + + } + } + } + + 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 = new byte[0]; + if (program.getResult().getHReturn() != null){ + actualHReturn = program.getResult().getHReturn().array(); + } + + if (!Arrays.equals(expectedHReturn, actualHReturn)){ + + String output = + String.format("HReturn is differnt 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("HReturn is differnt expected hReturn: [ %s ], actual hReturn: [ %s ]", + expectedGas.toString() , + actualGas.toString()); + logger.info(output); + results.add(output); + } + program.getResult().getRepository().close(); + + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java index ca51ca06..60d1dec6 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java @@ -37,6 +37,27 @@ public class ByteUtil { System.arraycopy(biBytes, start, bytes, numBytes - length, length); return bytes; } + + /** + * emitting sign indication byte + * + * @param b - any big integer number + * @return + */ + public static byte[] bigIntegerToBytes(BigInteger b) { + if (b == null) + return null; + + byte[] data = b.toByteArray(); + + if (data.length != 1 && data[0] == 0) { + byte[] tmp = new byte[data.length - 1]; + System.arraycopy(data, 1, tmp, 0, tmp.length); + data = tmp; + } + return data; + } + public static byte[] longToBytes(long l) { return ByteBuffer.allocate(8).putLong(l).array(); diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/CallCreate.java b/ethereumj-core/src/main/java/org/ethereum/vm/CallCreate.java new file mode 100644 index 00000000..946a3ddf --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/vm/CallCreate.java @@ -0,0 +1,40 @@ +package org.ethereum.vm; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 03/07/2014 08:29 + */ + +public class CallCreate { + + byte[] data; + byte[] destination; + byte[] gasLimit; + byte[] value; + + + public CallCreate(byte[] data, byte[] destination, byte[] gasLimit, byte[] value) { + this.data = data; + this.destination = destination; + this.gasLimit = gasLimit; + this.value = value; + } + + public byte[] getData() { + return data; + } + + public byte[] getDestination() { + return destination; + } + + public byte[] getGasLimit() { + return gasLimit; + } + + public byte[] getValue() { + return value; + } +} 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 eaaf4d14..3fca7dc6 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java @@ -210,12 +210,15 @@ public class Program { public void createContract(DataWord gas, DataWord memStart, DataWord memSize) { + if (invokeData.byTestingSuite()){ + + logger.info("[testing suite] - omit real create"); + return; + } + // 1. FETCH THE CODE FROM THE MEMORY ByteBuffer programCode = memoryChunk(memStart, memSize); - Repository trackRepository = result.getRepository().getTrack(); - trackRepository.startTracking(); - byte[] senderAddress = this.getOwnerAddress().getNoLeadZeroesData(); if (logger.isInfoEnabled()) logger.info("creating a new contract inside contract run: [{}]", Hex.toHexString(senderAddress)); @@ -229,14 +232,20 @@ public class Program { return; } + // actual gas subtract + this.spendGas(gas.intValue(), "internal call"); + // 2.2 CREATE THE CONTRACT ADDRESS - byte[] nonce = trackRepository.getNonce(senderAddress).toByteArray(); + byte[] nonce = result.getRepository().getNonce(senderAddress).toByteArray(); byte[] newAddress = HashUtil.calcNewAddr(this.getOwnerAddress().getNoLeadZeroesData(), nonce); - trackRepository.createAccount(newAddress); + result.getRepository().createAccount(newAddress); // 2.3 UPDATE THE NONCE // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) - trackRepository.increaseNonce(senderAddress); + result.getRepository().increaseNonce(senderAddress); + + Repository trackRepository = result.getRepository().getTrack(); + trackRepository.startTracking(); // 3. COOK THE INVOKE AND EXECUTE ProgramInvoke programInvoke = @@ -264,6 +273,16 @@ public class Program { // IN SUCCESS PUSH THE ADDRESS INTO THE STACK stackPush(new DataWord(newAddress)); trackRepository.commit(); + + // 5. REFUND THE REMAIN GAS + BigInteger refundGas = gas.value().subtract(BigInteger.valueOf(result.getGasUsed())); + if (refundGas.compareTo(BigInteger.ZERO) == 1){ + + this.refundGas(refundGas.intValue(), "remain gas from the internal call"); + logger.info("The remain gas refunded, account: [ {} ], gas: [ {} ] ", + refundGas.toString(), refundGas.toString()); + } + } /** @@ -288,81 +307,108 @@ public class Program { // FETCH THE CODE byte[] programCode = this.result.getRepository().getCode(toAddress); + + if (logger.isInfoEnabled()) + logger.info("calling for existing contract: address={}", + Hex.toHexString(toAddress)); + + byte[] senderAddress = this.getOwnerAddress().getNoLeadZeroesData(); + + // 2.1 PERFORM THE GAS VALUE TX + // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) + if (this.getGas().longValue() - gas.longValue() < 0 ) { + logger.info("No gas for the internal call, \n" + + "fromAddress={}, toAddress={}", + Hex.toHexString(senderAddress), Hex.toHexString(toAddress)); + this.stackPushZero(); + return; + } + + // actual gas subtract + this.spendGas(gas.intValue(), "internal call"); + + BigInteger endowment = endowmentValue.value(); + BigInteger senderBalance = result.getRepository().getBalance(senderAddress); + if (senderBalance.compareTo(endowment) < 0){ + stackPushZero(); + return; + } + result.getRepository().addBalance(senderAddress, endowment.negate()); + + + if (invokeData.byTestingSuite()) { + logger.info("[testing suite] - omit real call"); + + stackPushOne(); + + this.getResult().addCallCreate(data.array(), + toAddressDW.getNoLeadZeroesData(), + gas.getNoLeadZeroesData(), endowmentValue.getNoLeadZeroesData()); + + return; + } + + + Repository trackRepository = result.getRepository().getTrack(); + trackRepository.startTracking(); + trackRepository.addBalance(toAddress, endowmentValue.value()); + + ProgramInvoke programInvoke = + ProgramInvokeFactory.createProgramInvoke(this, toAddressDW, + endowmentValue, gas, result.getRepository().getBalance(toAddress), + data.array(), + trackRepository); + + ProgramResult result = null; + + if (programCode != null && programCode.length != 0) { - if (logger.isInfoEnabled()) - logger.info("calling for existing contract: address={}", - Hex.toHexString(toAddress)); - - byte[] senderAddress = this.getOwnerAddress().getNoLeadZeroesData(); - - // 2.1 PERFORM THE GAS VALUE TX - // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) - if (this.getGas().longValue() - gas.longValue() < 0 ) { - logger.info("No gas for the internal call, \n" + - "fromAddress={}, toAddress={}", - Hex.toHexString(senderAddress), Hex.toHexString(toAddress)); - this.stackPushZero(); - return; - } - - // 2.2 UPDATE THE NONCE - // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) - this.result.repository.increaseNonce(senderAddress); - - Repository trackRepository = result.getRepository().getTrack(); - trackRepository.startTracking(); - - // todo: check if the endowment can really be done - trackRepository.addBalance(toAddress, endowmentValue.value()); - - ProgramInvoke programInvoke = - ProgramInvokeFactory.createProgramInvoke(this, toAddressDW, - endowmentValue, gas, result.getRepository().getBalance(toAddress), - data.array(), - trackRepository); - VM vm = new VM(); Program program = new Program(programCode, programInvoke); vm.play(program); - ProgramResult result = program.getResult(); - - if (result.getException() != null && - result.getException() instanceof Program.OutOfGasException) { - logger.info("contract run halted by OutOfGas: contract={}" , Hex.toHexString(toAddress)); - - trackRepository.rollback(); - stackPushZero(); - return; - } - - // 3. APPLY RESULTS: result.getHReturn() into out_memory allocated - ByteBuffer buffer = result.getHReturn(); - if (buffer != null) { - int retSize = buffer.array().length; - int allocSize = outDataSize.intValue(); - if (retSize > allocSize) { - byte[] outArray = Arrays.copyOf(buffer.array(), allocSize); - this.memorySave(outArray, buffer.array()); - } else { - this.memorySave(outDataOffs.getData(), buffer.array()); - } - } - - // 4. THE FLAG OF SUCCESS IS ONE PUSHED INTO THE STACK - stackPushOne(); - trackRepository.commit(); - stackPush(new DataWord(1)); - - // the gas spent in any internal outcome - // even if execution was halted by an exception - spendGas(result.getGasUsed(), " 'Total for CALL run' "); - logger.info("The usage of the gas in external call updated", result.getGasUsed()); + result = program.getResult(); } + + if (result.getException() != null && + result.getException() instanceof Program.OutOfGasException) { + logger.info("contract run halted by OutOfGas: contract={}" , Hex.toHexString(toAddress)); + + trackRepository.rollback(); + stackPushZero(); + return; + } + + // 3. APPLY RESULTS: result.getHReturn() into out_memory allocated + ByteBuffer buffer = result.getHReturn(); + if (buffer != null) { + int retSize = buffer.array().length; + int allocSize = outDataSize.intValue(); + if (retSize > allocSize) { + byte[] outArray = Arrays.copyOf(buffer.array(), allocSize); + this.memorySave(outArray, buffer.array()); + } else { + this.memorySave(outDataOffs.getData(), buffer.array()); + } + } + + // 4. THE FLAG OF SUCCESS IS ONE PUSHED INTO THE STACK + trackRepository.commit(); + stackPushOne(); + + // 5. REFUND THE REMAIN GAS + BigInteger refundGas = gas.value().subtract(BigInteger.valueOf(result.getGasUsed())); + if (refundGas.compareTo(BigInteger.ZERO) == 1){ + + this.refundGas(refundGas.intValue(), "remain gas from the internal call"); + logger.info("The remain gas refunded, account: [ {} ], gas: [ {} ] ", + refundGas.toString(), refundGas.toString()); + } + } public void spendGas(int gasValue, String cause) { - gasLogger.info("Spent: for cause={} gas={}", cause, gasValue); + gasLogger.info("Spent for cause: [ {} ], gas: [ {} ]", cause, gasValue); long afterSpend = invokeData.getGas().longValue() - gasValue - result.getGasUsed(); if (afterSpend < 0) @@ -370,6 +416,11 @@ public class Program { result.spendGas(gasValue); } + public void refundGas(int gasValue, String cause) { + gasLogger.info("Refund for cause: [ {} ], gas: [ {} ]", cause, gasValue); + result.refundGas(gasValue); + } + public void storageSave(DataWord word1, DataWord word2) { storageSave(word1.getData(), word2.getData()); } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvoke.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvoke.java index 9f1df5e3..d12b1541 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvoke.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvoke.java @@ -30,4 +30,5 @@ public interface ProgramInvoke { public Repository getRepository(); public boolean byTransaction(); + boolean byTestingSuite(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java index d0943b62..4b435ab3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java @@ -35,6 +35,7 @@ public class ProgramInvokeImpl implements ProgramInvoke { private Repository repository; private boolean byTransaction = true; + private boolean byTestingSuite = false; public ProgramInvokeImpl(DataWord address, DataWord origin, DataWord caller, DataWord balance, DataWord gasPrice, DataWord gas, DataWord callValue, byte[] msgData, @@ -63,6 +64,17 @@ public class ProgramInvokeImpl implements ProgramInvoke { this.byTransaction = false; } + public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] balance, + byte[] gasPrice, byte[] gas, byte[] callValue, byte[] msgData, + byte[] lastHash, byte[] coinbase, long timestamp, long number, byte[] difficulty, + long gaslimit, + Repository repository, boolean byTestingSuite) { + this(address, origin, caller, balance, gasPrice, gas, callValue, msgData, lastHash, coinbase, + timestamp, number, difficulty, gaslimit, repository); + this.byTestingSuite = byTestingSuite; + } + + public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] balance, byte[] gasPrice, byte[] gas, byte[] callValue, byte[] msgData, byte[] lastHash, byte[] coinbase, long timestamp, long number, byte[] difficulty, @@ -214,6 +226,11 @@ public class ProgramInvokeImpl implements ProgramInvoke { return byTransaction; } + @Override + public boolean byTestingSuite() { + return byTestingSuite; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -221,6 +238,7 @@ public class ProgramInvokeImpl implements ProgramInvoke { ProgramInvokeImpl that = (ProgramInvokeImpl) o; + if (byTestingSuite != that.byTestingSuite) return false; if (byTransaction != that.byTransaction) return false; if (address != null ? !address.equals(that.address) : that.address != null) return false; if (balance != null ? !balance.equals(that.balance) : that.balance != null) return false; @@ -259,8 +277,9 @@ public class ProgramInvokeImpl implements ProgramInvoke { result = 31 * result + (difficulty != null ? difficulty.hashCode() : 0); result = 31 * result + (gaslimit != null ? gaslimit.hashCode() : 0); result = 31 * result + (storage != null ? storage.hashCode() : 0); - result = 31 * result + (repository!= null ? repository.hashCode() : 0); + result = 31 * result + (repository != null ? repository.hashCode() : 0); result = 31 * result + (byTransaction ? 1 : 0); + result = 31 * result + (byTestingSuite ? 1 : 0); return result; } } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeMockImpl.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeMockImpl.java index c0ca939f..c16e8f70 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeMockImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeMockImpl.java @@ -14,6 +14,7 @@ public class ProgramInvokeMockImpl implements ProgramInvoke { private byte[] msgData; + private Repository repository = null; private String ownerAddress = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826"; @@ -27,6 +28,11 @@ public class ProgramInvokeMockImpl implements ProgramInvoke { this.repository.createAccount(Hex.decode(ownerAddress)); } + public ProgramInvokeMockImpl(boolean defaults){ + + + } + /* ADDRESS op */ public DataWord getOwnerAddress() { byte[] addr = Hex.decode(ownerAddress); @@ -167,6 +173,11 @@ public class ProgramInvokeMockImpl implements ProgramInvoke { return true; } + @Override + public boolean byTestingSuite() { + return false; + } + @Override public Repository getRepository() { return this.repository; @@ -175,4 +186,7 @@ public class ProgramInvokeMockImpl implements ProgramInvoke { public void setRepository(Repository repository) { this.repository = repository; } + + + } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java index b462833f..11c93ded 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java @@ -3,6 +3,8 @@ package org.ethereum.vm; import org.ethereum.db.Repository; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; /** * www.ethereumJ.com @@ -17,9 +19,19 @@ public class ProgramResult { Repository repository = null; + /* + * for testing runs , + * call/create is not executed + * but dummy recorded + */ + List callCreateList; + public void spendGas(int gas) { gasUsed += gas; } + public void refundGas(int gas) { + gasUsed -= gas; + } public void setHReturn(byte[] hReturn) { this.hReturn = ByteBuffer.allocate(hReturn.length); @@ -49,4 +61,16 @@ public class ProgramResult { public void setRepository(Repository repository) { this.repository = repository; } + + public List getCallCreateList() { + return callCreateList; + } + + public void addCallCreate(byte[] data, byte[] destination, byte[] gasLimit, byte[] value){ + + if (callCreateList == null) + callCreateList = new ArrayList<>(); + + callCreateList.add(new CallCreate(data, destination, gasLimit, value)); + } } 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 10ce7534..7f4d37e5 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -556,6 +556,8 @@ public class VM { program.fullTrace(); } catch (RuntimeException e) { program.stop(); + logger.info("VM halted: ", e); + throw e; } } diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/JSONTestSuiteTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/JSONTestSuiteTest.java new file mode 100644 index 00000000..e519a686 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/JSONTestSuiteTest.java @@ -0,0 +1,175 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.db.ByteArrayWrapper; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.junit.Assert; +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 29/06/2014 10:46 + */ + +public class JSONTestSuiteTest { + + + + @Test // AccountState parsing // + public void test1() throws ParseException { + + String expectedNonce = "01"; + String expectedBalance = "0de0b6b3a763ff6c"; + String expectedCode = "6000600060006000604a3360c85c03f1"; + + ByteArrayWrapper expectedKey1 = new ByteArrayWrapper( Hex.decode("ffaa") ); + ByteArrayWrapper expectedKey2 = new ByteArrayWrapper( Hex.decode("ffab") ); + + ByteArrayWrapper expectedVal1 = new ByteArrayWrapper( Hex.decode("c8") ); + ByteArrayWrapper expectedVal2 = new ByteArrayWrapper( Hex.decode("b2b2b2") ); + + JSONParser parser = new JSONParser(); + String accountString = "{'balance':999999999999999852,'nonce':1," + + "'code':[96,0,96,0,96,0,96,0,96,74,51,96,200,92,3,241]," + + "'storage':{'0xffaa' : [200], '0xffab' : ['0xb2b2b2']}}"; + accountString = accountString.replace("'", "\""); + + JSONObject accountJSONObj = (JSONObject)parser.parse(accountString); + + AccountState state = + new AccountState(Hex.decode("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6"), + accountJSONObj); + + Assert.assertEquals(expectedNonce, Hex.toHexString(state.nonce)); + Assert.assertEquals(expectedBalance, Hex.toHexString(state.balance)); + Assert.assertEquals(expectedCode, Hex.toHexString(state.code)); + + Assert.assertTrue( state.storage.keySet().contains(expectedKey1) ); + Assert.assertTrue(state.storage.keySet().contains(expectedKey2)); + + Assert.assertTrue( state.storage.values().contains(expectedVal1) ); + Assert.assertTrue(state.storage.values().contains(expectedVal2)); + } + + + + @Test // Exec parsing // + public void test2() throws ParseException { + + String expectedAddress = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6"; + String expectedCaller = "cd1722f3947def4cf144679da39c4c32bdc35681"; + String expectedData = "ffaabb"; + String expectedCode = "6000600060006000604a3360c85c03f1"; + String expectedGas = "2710"; + String expectedGasPrice = "5af3107a4000"; + String expectedOrigin = "cd1722f3947def4cf144679da39c4c32bdc35681"; + String expectedValue = "0de0b6b3a7640000"; + + JSONParser parser = new JSONParser(); + String execString = "{'address' : '0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6'," + + " 'caller' : 'cd1722f3947def4cf144679da39c4c32bdc35681'," + + " 'data' : ['0xffaabb']," + + " 'code' : [96,0,96,0,96,0,96,0,96,74,51,96,200,92,3,241]," + + " 'gas' : 10000," + + " 'gasPrice' : 100000000000000," + + " 'origin' : 'cd1722f3947def4cf144679da39c4c32bdc35681'," + + " 'value' : 1000000000000000000}"; + execString = execString.replace("'", "\""); + + JSONObject execJSONObj = (JSONObject)parser.parse(execString); + Exec exec = new Exec(execJSONObj); + + Assert.assertEquals(expectedAddress, Hex.toHexString(exec.getAddress())); + Assert.assertEquals(expectedCaller, Hex.toHexString(exec.getCaller())); + Assert.assertEquals(expectedData, Hex.toHexString(exec.getData())); + Assert.assertEquals(expectedCode, Hex.toHexString(exec.getCode())); + Assert.assertEquals(expectedGas, Hex.toHexString(exec.getGas())); + Assert.assertEquals(expectedGasPrice, Hex.toHexString(exec.getGasPrice())); + Assert.assertEquals(expectedOrigin, Hex.toHexString(exec.getOrigin())); + Assert.assertEquals(expectedValue, Hex.toHexString(exec.getValue())); + } + + + + @Test // Env parsing // + public void test3() throws ParseException { + + String expectedCurrentCoinbase = "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"; + String expectedCurrentDifficulty = "256"; + String expectedCurrentGasLimit = "1000000"; + String expectedCurrentNumber = "0"; + String expectedCurrentTimestamp = "1"; + String expectedPreviousHash = "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6"; + + JSONParser parser = new JSONParser(); + String envString = "{'currentCoinbase' : '2adc25665018aa1fe0e6bc666dac8fc2697ff9ba'," + + "'currentDifficulty' : '256'," + + "'currentGasLimit' : '1000000'," + + "'currentNumber' : '0'," + + "'currentTimestamp' : 1," + + "'previousHash' : '5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6'}"; + envString = envString.replace("'", "\""); + + JSONObject envJSONObj = (JSONObject)parser.parse(envString); + + Env env = new Env(envJSONObj); + + Assert.assertEquals(expectedCurrentCoinbase, Hex.toHexString(env.getCurrentCoinbase())); + Assert.assertEquals(expectedCurrentDifficulty, new BigInteger( env.getCurrentDifficlty()).toString()); + Assert.assertEquals(expectedCurrentGasLimit, new BigInteger( env.getCurrentGasLimit()).toString()); + Assert.assertEquals(expectedCurrentNumber, new BigInteger( env.getCurrentNumber()).toString()); + Assert.assertEquals(expectedCurrentTimestamp, new BigInteger( env.getCurrentTimestamp()).toString()); + Assert.assertEquals(expectedPreviousHash, Hex.toHexString(env.getPreviousHash())); + + } + + + @Test // CallCreate parsing // + public void test4() throws ParseException { + + String expectedData = ""; + String expectedDestination = "cd1722f3947def4cf144679da39c4c32bdc35681"; + String expectedGasLimit = "9792"; + String expectedValue = "74"; + + JSONParser parser = new JSONParser(); + String callCreateString = "{'data' : [],'destination' : 'cd1722f3947def4cf144679da39c4c32bdc35681','gasLimit' : 9792,'value' : 74}"; + callCreateString = callCreateString.replace("'", "\""); + + JSONObject callCreateJSONObj = (JSONObject)parser.parse(callCreateString); + + CallCreate callCreate = new CallCreate(callCreateJSONObj); + + Assert.assertEquals(expectedData, Hex.toHexString(callCreate.getData())); + Assert.assertEquals(expectedDestination, Hex.toHexString(callCreate.getDestination())); + Assert.assertEquals(expectedGasLimit, new BigInteger( callCreate.getGasLimit()).toString()); + Assert.assertEquals(expectedValue, new BigInteger( callCreate.getValue()).toString()); + } + + @Test // TestCase parsing // + public void test5() throws ParseException { + + JSONParser parser = new JSONParser(); +// String testCaseString = "{'callcreates':[{'data':[],'destination':'cd1722f3947def4cf144679da39c4c32bdc35681','gasLimit':9792,'value':74}],'env':{'currentCoinbase':'2adc25665018aa1fe0e6bc666dac8fc2697ff9ba','currentDifficulty':'256','currentGasLimit':'1000000','currentNumber':'0','currentTimestamp':1,'previousHash':'5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6'},'exec':{'address':'0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6','caller':'cd1722f3947def4cf144679da39c4c32bdc35681','code':[96,0,96,0,96,0,96,0,96,74,51,96,200,92,3,241],'data':[],'gas':10000,'gasPrice':100000000000000,'origin':'cd1722f3947def4cf144679da39c4c32bdc35681','value':1000000000000000000},'gas':9971,'out':[],'post':{'0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6':{'balance':999999999999999926,'code':[96,0,96,0,96,0,96,0,96,74,51,96,200,92,3,241],'nonce':0,'storage':{}}},'pre':{'0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6':{'balance':1000000000000000000,'code':[96,0,96,0,96,0,96,0,96,74,51,96,200,92,3,241],'nonce':0,'storage':{}}}}"; + String testCaseString = "{'callcreates':[{'data':[],'destination':'cd1722f3947def4cf144679da39c4c32bdc35681','gasLimit':9792,'value':74}],'env':{'currentCoinbase':'2adc25665018aa1fe0e6bc666dac8fc2697ff9ba','currentDifficulty':'256','currentGasLimit':'1000000','currentNumber':'0','currentTimestamp':1,'previousHash':'5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6'},'exec':{'address':'0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6','caller':'cd1722f3947def4cf144679da39c4c32bdc35681','code':[96,0,96,0,96,0,96,0,96,74,51,96,200,92,3,241],'data':[],'gas':10000,'gasPrice':100000000000000,'origin':'cd1722f3947def4cf144679da39c4c32bdc35681','value':1000000000000000000},'gas':9971,'out':[],'post':{'0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6':{'balance':999999999999999926,'code':[96,0,96,0,96,0,96,0,96,74,51,96,200,92,3,241],'nonce':0,'storage':{}}},'pre':{'0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6':{'balance':1000000000000000000,'code':[96,0,96,0,96,0,96,0,96,74,51,96,200,92,3,241],'nonce':0,'storage':{}}}}"; + testCaseString = testCaseString.replace("'", "\""); + + JSONObject testCaseJSONObj = (JSONObject)parser.parse(testCaseString); + + TestCase testCase = new TestCase(testCaseJSONObj); + int ccList = testCase.getCallCreateList().size(); + + Assert.assertEquals(1, ccList); + + TestRunner runner = new TestRunner(); + runner.runTestCase(testCase); + } + + +}