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);