JSON Testing introduced:
+ JSON defined tests to run + VM fixes and adaptation for recent changes
This commit is contained in:
parent
33be499ba6
commit
14ba667d46
|
@ -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<ByteArrayWrapper> {
|
|||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Hex.toHexString(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<DataWord, DataWord> 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);
|
||||
|
|
|
@ -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<ByteArrayWrapper, ByteArrayWrapper> 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<ByteArrayWrapper, ByteArrayWrapper> 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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) +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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) +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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) +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<ByteArrayWrapper, AccountState> pre = new HashMap<>();
|
||||
|
||||
// "post": { ... },
|
||||
private Map<ByteArrayWrapper, AccountState> post = new HashMap<>();
|
||||
|
||||
// "callcreates": { ... }
|
||||
private List<CallCreate> 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<ByteArrayWrapper, AccountState> getPre() {
|
||||
return pre;
|
||||
}
|
||||
|
||||
public Map<ByteArrayWrapper, AccountState> getPost() {
|
||||
return post;
|
||||
}
|
||||
|
||||
public List<CallCreate> 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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<String> 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<ByteArrayWrapper, ByteArrayWrapper> 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<DataWord, DataWord> 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<org.ethereum.vm.CallCreate> 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();
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -30,4 +30,5 @@ public interface ProgramInvoke {
|
|||
public Repository getRepository();
|
||||
|
||||
public boolean byTransaction();
|
||||
boolean byTestingSuite();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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<CallCreate> 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<CallCreate> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -556,6 +556,8 @@ public class VM {
|
|||
program.fullTrace();
|
||||
} catch (RuntimeException e) {
|
||||
program.stop();
|
||||
logger.info("VM halted: ", e);
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue