diff --git a/ethereumj-core/src/main/java/org/ethereum/config/BlockchainConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/BlockchainConfig.java
index 9f651b655e..f4c7ed1f53 100644
--- a/ethereumj-core/src/main/java/org/ethereum/config/BlockchainConfig.java
+++ b/ethereumj-core/src/main/java/org/ethereum/config/BlockchainConfig.java
@@ -181,4 +181,10 @@ String validateTransactionChanges(BlockStore blockStore, Block curBlock, Transac
* Net gas metering for SSTORE without dirty maps
*/
boolean eip1283();
+
+ /**
+ * EIP 1014: https://eips.ethereum.org/EIPS/eip-1014
+ * Skinny CREATE2: same as CREATE but with deterministic address
+ */
+ boolean eip1014();
}
diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/AbstractConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/AbstractConfig.java
index 62d8eb84a9..14974f951c 100644
--- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/AbstractConfig.java
+++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/AbstractConfig.java
@@ -208,6 +208,11 @@ public boolean eip1283() {
return false;
}
+ @Override
+ public boolean eip1014() {
+ return false;
+ }
+
@Override
public String toString() {
return getClass().getSimpleName();
diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ConstantinopleConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ConstantinopleConfig.java
index 81242f0351..84068ecbef 100644
--- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ConstantinopleConfig.java
+++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ConstantinopleConfig.java
@@ -25,7 +25,7 @@
*
145 - Bitwise shifting instructions in EVM
* 1014 - Skinny CREATE2
* 1052 - EXTCODEHASH opcode
- * 1087 - Net gas metering for SSTORE operations
+ * 1283 - Net gas metering for SSTORE without dirty maps
*
*/
public class ConstantinopleConfig extends ByzantiumConfig {
@@ -48,4 +48,9 @@ public boolean eip145() {
public boolean eip1283() {
return true;
}
+
+ @Override
+ public boolean eip1014() {
+ return true;
+ }
}
diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java
index 1e10473746..2cc83fcce5 100644
--- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java
+++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java
@@ -196,4 +196,9 @@ public boolean eip1052() {
public boolean eip1283() {
return false;
}
+
+ @Override
+ public boolean eip1014() {
+ return false;
+ }
}
diff --git a/ethereumj-core/src/main/java/org/ethereum/crypto/HashUtil.java b/ethereumj-core/src/main/java/org/ethereum/crypto/HashUtil.java
index 24c887f1f6..9ab434ba23 100644
--- a/ethereumj-core/src/main/java/org/ethereum/crypto/HashUtil.java
+++ b/ethereumj-core/src/main/java/org/ethereum/crypto/HashUtil.java
@@ -35,6 +35,8 @@
import static java.util.Arrays.copyOfRange;
import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY;
+import static org.ethereum.util.ByteUtil.bigIntegerToBytes;
+import static org.ethereum.util.ByteUtil.bytesToBigInteger;
public class HashUtil {
@@ -182,6 +184,30 @@ public static byte[] calcNewAddr(byte[] addr, byte[] nonce) {
return sha3omit12(RLP.encodeList(encSender, encNonce));
}
+ /**
+ * The way to calculate new address inside ethereum for {@link org.ethereum.vm.OpCode#CREATE2}
+ * sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code)))[12:]
+ *
+ * @param senderAddr - creating address
+ * @param initCode - contract init code
+ * @param salt - salt to make different result addresses
+ * @return new address
+ */
+ public static byte[] calcSaltAddr(byte[] senderAddr, byte[] initCode, byte[] salt) {
+ // 1 - 0xff length, 32 bytes - keccak-256
+ byte[] data = new byte[1 + senderAddr.length + salt.length + 32];
+ data[0] = (byte) 0xff;
+ int currentOffset = 1;
+ System.arraycopy(senderAddr, 0, data, currentOffset, senderAddr.length);
+ currentOffset += senderAddr.length;
+ System.arraycopy(salt, 0, data, currentOffset, salt.length);
+ currentOffset += salt.length;
+ byte[] sha3InitCode = sha3(initCode);
+ System.arraycopy(sha3InitCode, 0, data, currentOffset, sha3InitCode.length);
+
+ return sha3omit12(data);
+ }
+
/**
* @see #doubleDigest(byte[], int, int)
*
diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/OpCode.java b/ethereumj-core/src/main/java/org/ethereum/vm/OpCode.java
index e5afa5e2f3..440a2f8ef3 100644
--- a/ethereumj-core/src/main/java/org/ethereum/vm/OpCode.java
+++ b/ethereumj-core/src/main/java/org/ethereum/vm/OpCode.java
@@ -612,6 +612,11 @@ public enum OpCode {
*/
DELEGATECALL(0xf4, 6, 1, SpecialTier, CallFlags.Call, CallFlags.Stateless, CallFlags.Delegate),
+ /**
+ * (0xf5) Skinny CREATE2, same as CREATE but with deterministic address
+ */
+ CREATE2(0xf5, 4, 1, SpecialTier),
+
/**
* opcode that can be used to call another contract (or itself) while disallowing any
* modifications to the state during the call (and its subcalls, if present).
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 c94c35877f..625ee898a1 100644
--- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java
+++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java
@@ -110,6 +110,7 @@ public class VM {
put(SHL, BlockchainConfig::eip145);
put(SHR, BlockchainConfig::eip145);
put(SAR, BlockchainConfig::eip145);
+ put(CREATE2, BlockchainConfig::eip1014);
}};
private final SystemProperties config;
@@ -388,6 +389,10 @@ else if (!currentValue.isZero() && newValue.isZero()) {
gasCost = gasCosts.getCREATE() + calcMemGas(gasCosts, oldMemSize,
memNeeded(stack.get(stack.size() - 2), stack.get(stack.size() - 3)), 0);
break;
+ case CREATE2:
+ gasCost = gasCosts.getCREATE() + calcMemGas(gasCosts, oldMemSize,
+ memNeeded(stack.get(stack.size() - 2), stack.get(stack.size() - 3)), 0);
+ break;
case LOG0:
case LOG1:
case LOG2:
@@ -1279,6 +1284,25 @@ else if (!currentValue.isZero() && newValue.isZero()) {
program.step();
}
break;
+ case CREATE2: {
+ if (program.isStaticCall()) throw new Program.StaticCallModificationException();
+
+ DataWord value = program.stackPop();
+ DataWord inOffset = program.stackPop();
+ DataWord inSize = program.stackPop();
+ DataWord salt = program.stackPop();
+
+ if (logger.isInfoEnabled())
+ logger.info(logString, String.format("%5s", "[" + program.getPC() + "]"),
+ String.format("%-12s", op.name()),
+ program.getGas().value(),
+ program.getCallDeep(), hint);
+
+ program.createContract2(value, inOffset, inSize, salt);
+
+ program.step();
+ }
+ break;
case CALL:
case CALLCODE:
case DELEGATECALL:
diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java b/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java
index 68abc3d037..8c77007a7c 100644
--- a/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java
+++ b/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java
@@ -401,25 +401,76 @@ public Repository getStorage() {
return this.storage;
}
- @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ /**
+ * Create contract for {@link OpCode#CREATE}
+ * @param value Endowment
+ * @param memStart Code memory offset
+ * @param memSize Code memory size
+ */
public void createContract(DataWord value, DataWord memStart, DataWord memSize) {
returnDataBuffer = null; // reset return buffer right before the call
- if (getCallDeep() == MAX_DEPTH) {
- stackPushZero();
+ byte[] senderAddress = this.getOwnerAddress().getLast20Bytes();
+ BigInteger endowment = value.value();
+ if (!verifyCall(senderAddress, endowment))
return;
- }
+
+ byte[] nonce = getStorage().getNonce(senderAddress).toByteArray();
+ byte[] contractAddress = HashUtil.calcNewAddr(senderAddress, nonce);
+
+ byte[] programCode = memoryChunk(memStart.intValue(), memSize.intValue());
+ createContractImpl(value, programCode, contractAddress);
+ }
+
+ /**
+ * Create contract for {@link OpCode#CREATE2}
+ * @param value Endowment
+ * @param memStart Code memory offset
+ * @param memSize Code memory size
+ * @param salt Salt, used in contract address calculation
+ */
+ public void createContract2(DataWord value, DataWord memStart, DataWord memSize, DataWord salt) {
+ returnDataBuffer = null; // reset return buffer right before the call
byte[] senderAddress = this.getOwnerAddress().getLast20Bytes();
BigInteger endowment = value.value();
+ if (!verifyCall(senderAddress, endowment))
+ return;
+
+ byte[] programCode = memoryChunk(memStart.intValue(), memSize.intValue());
+ byte[] contractAddress = HashUtil.calcSaltAddr(senderAddress, programCode, salt.getData());
+
+ createContractImpl(value, programCode, contractAddress);
+ }
+
+ /**
+ * Verifies CREATE attempt
+ */
+ private boolean verifyCall(byte[] senderAddress, BigInteger endowment) {
+ if (getCallDeep() == MAX_DEPTH) {
+ stackPushZero();
+ return false;
+ }
+
if (isNotCovers(getStorage().getBalance(senderAddress), endowment)) {
stackPushZero();
- return;
+ return false;
}
- // [1] FETCH THE CODE FROM THE MEMORY
- byte[] programCode = memoryChunk(memStart.intValue(), memSize.intValue());
+ return true;
+ }
+
+ /**
+ * All stages required to create contract on provided address after initial check
+ * @param value Endowment
+ * @param programCode Contract code
+ * @param newAddress Contract address
+ */
+ @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ private void createContractImpl(DataWord value, byte[] programCode, byte[] newAddress) {
+ // [1] LOG, SPEND GAS
+ byte[] senderAddress = this.getOwnerAddress().getLast20Bytes();
if (logger.isInfoEnabled())
logger.info("creating a new contract inside contract run: [{}]", toHexString(senderAddress));
@@ -429,9 +480,6 @@ public void createContract(DataWord value, DataWord memStart, DataWord memSize)
spendGas(gasLimit.longValue(), "internal call");
// [2] CREATE THE CONTRACT ADDRESS
- byte[] nonce = getStorage().getNonce(senderAddress).toByteArray();
- byte[] newAddress = HashUtil.calcNewAddr(getOwnerAddress().getLast20Bytes(), nonce);
-
AccountState existingAddr = getStorage().getAccountState(newAddress);
boolean contractAlreadyExists = existingAddr != null && existingAddr.isContractExist(blockchainConfig);
@@ -459,6 +507,7 @@ public void createContract(DataWord value, DataWord memStart, DataWord memSize)
track.addBalance(newAddress, oldBalance);
// [4] TRANSFER THE BALANCE
+ BigInteger endowment = value.value();
BigInteger newBalance = ZERO;
if (!byTestingSuite()) {
track.addBalance(senderAddress, endowment.negate());
@@ -467,6 +516,7 @@ public void createContract(DataWord value, DataWord memStart, DataWord memSize)
// [5] COOK THE INVOKE AND EXECUTE
+ byte[] nonce = getStorage().getNonce(senderAddress).toByteArray();
InternalTransaction internalTx = addInternalTx(nonce, getGasLimit(), senderAddress, null, endowment, programCode, "create");
ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke(
this, DataWord.of(newAddress), getOwnerAddress(), value, gasLimit,
@@ -539,6 +589,7 @@ public void createContract(DataWord value, DataWord memStart, DataWord memSize)
refundGas);
}
}
+ touchedAccounts.add(newAddress);
}
/**
diff --git a/ethereumj-core/src/test/java/org/ethereum/crypto/CryptoTest.java b/ethereumj-core/src/test/java/org/ethereum/crypto/CryptoTest.java
index 0c6f79caef..68bb6a928f 100644
--- a/ethereumj-core/src/test/java/org/ethereum/crypto/CryptoTest.java
+++ b/ethereumj-core/src/test/java/org/ethereum/crypto/CryptoTest.java
@@ -45,6 +45,7 @@
import java.security.SecureRandom;
import static org.ethereum.crypto.HashUtil.sha3;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class CryptoTest {
@@ -366,4 +367,14 @@ public byte[] getEncoded(AsymmetricKeyParameter keyParameter)
log.info("orig: " + Hex.toHexString(orig));
}
+ @Test
+ public void calcSaltAddrTest() {
+ byte[] from = Hex.decode("0123456789012345678901234567890123456789");
+ byte[] salt = Hex.decode("0000000000000000000000000000000000000000000000000000000000000314");
+ // contract Demo{}
+ byte[] code = Hex.decode("6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820a63607f79a5e21cdaf424583b9686f2aa44059d70183eb9846ccfa086405716e0029");
+
+ assertArrayEquals(Hex.decode("d26e42c8a0511c19757f783402231cf82b2bdf59"), HashUtil.calcSaltAddr(from, code, salt));
+ }
+
}
diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockStateTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockStateTest.java
index baa9abe99f..e9d14598c2 100644
--- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockStateTest.java
+++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockStateTest.java
@@ -326,6 +326,18 @@ public void stShiftTest() throws IOException {
// TODO: Update all, this one passes with following settings:
// String commitSHA = "560e2cd6cf881821180d46d9cc4c542e19cfea1d";
// String treeSHA = "8457a6a49f53218575a349abc311c55939797bff";
+// targetNets += GitHubJSONTestSuite.Network.Constantinople
+ }
+
+ @Test
+ @Ignore("Update after all tests could pass latest develop")
+ public void stCreate2Test() throws IOException {
+ suite.runAll("stCreate2", new HashSet<>(Arrays.asList(
+ "create2collisionStorage_d1g0v0" // Tests excluded because they test unreal prestate
+ ))); // (nonce, balance 0, code empty, but some storage)
+// TODO: Update all, this one passes with following settings:
+// String commitSHA = "e2d84e1c00289bc259ad631efb6b42390e6a291a";
+// String treeSHA = "d74573a79cf607744759acde258bf7c3cf849bf1";
// targetNets += GitHubJSONTestSuite.Network.Constantinople
}
}
diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java
index afe47c70cf..d1207ac438 100644
--- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java
+++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java
@@ -328,6 +328,19 @@ public void stShiftTest() throws IOException {
// TODO: Update all, this one passes with following settings:
// String commit = "ad2184adca367c0b68c65b44519dba16e1d0b9e2";
// String treeSha = "4dd59a4f448dc06c3641bd5cb9c35cf6a099e438";
+// targetNets += GitHubJSONTestSuite.Network.Constantinople
+ }
+
+ @Test
+ @Ignore("Update after all tests could pass latest develop")
+ public void stCreate2Test() throws IOException {
+ suite.runAll("stCreate2", new HashSet<>(Arrays.asList(
+ "RevertInCreateInInit", // Tests excluded because they test unreal prestate
+ "create2collisionStorage" // (nonce, balance 0, code empty, but some storage)
+ )));
+// TODO: Update all, this one passes with following settings:
+// String commitSHA = "3f5febc901913ef698f1b09dda8705babd729e4a";
+// String treeSHA = "222ea3812849ed982ef0268ec41b609e5b13c412";
// targetNets += GitHubJSONTestSuite.Network.Constantinople
}
}
diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/BlockTestCase.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/BlockTestCase.java
index 88df2265a1..04e3b0ba9c 100644
--- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/BlockTestCase.java
+++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/BlockTestCase.java
@@ -39,6 +39,7 @@ public class BlockTestCase {
private String lastblockhash;
private int noBlockChainHistory;
private GitHubJSONTestSuite.Network network;
+ private SealEngine sealEngine = SealEngine.Ethash;
public BlockTestCase() {
}
@@ -115,6 +116,14 @@ public BlockchainNetConfig getConfig() {
return network.getConfig();
}
+ public SealEngine getSealEngine() {
+ return sealEngine;
+ }
+
+ public void setSealEngine(SealEngine sealEngine) {
+ this.sealEngine = sealEngine;
+ }
+
@Override
public String toString() {
return "BlockTestCase{" +
@@ -123,4 +132,9 @@ public String toString() {
", pre=" + pre +
'}';
}
+
+ enum SealEngine {
+ NoProof, // blocks in the test should not be checked for difficulty
+ Ethash
+ }
}
diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java
index b5ed78c3ad..a09db7b7f4 100644
--- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java
+++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TestRunner.java
@@ -159,9 +159,13 @@ public List runTestCase(BlockTestCase testCase) {
byte[] bestHash = Hex.decode(testCase.getLastblockhash().startsWith("0x") ?
testCase.getLastblockhash().substring(2) : testCase.getLastblockhash());
- String finalRoot = Hex.toHexString(blockStore.getBlockByHash(bestHash).getStateRoot());
- if (!finalRoot.equals(currRoot)){
+ String finalRoot = null;
+ if (blockStore.getBlockByHash(bestHash) != null) {
+ finalRoot = Hex.toHexString(blockStore.getBlockByHash(bestHash).getStateRoot());
+ }
+
+ if (!currRoot.equals(finalRoot)){
String formattedString = String.format("Root hash doesn't match best: expected: %s current: %s",
finalRoot, currRoot);
results.add(formattedString);