Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-1014 Skinny CREATE2 #1178

Merged
merged 5 commits into from
Sep 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ public boolean eip1283() {
return false;
}

@Override
public boolean eip1014() {
return false;
}

@Override
public String toString() {
return getClass().getSimpleName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
* <li>145 - Bitwise shifting instructions in EVM</li>
* <li>1014 - Skinny CREATE2</li>
* <li>1052 - EXTCODEHASH opcode</li>
* <li>1087 - Net gas metering for SSTORE operations</li>
* <li>1283 - Net gas metering for SSTORE without dirty maps</li>
* </ul>
*/
public class ConstantinopleConfig extends ByzantiumConfig {
Expand All @@ -48,4 +48,9 @@ public boolean eip145() {
public boolean eip1283() {
return true;
}

@Override
public boolean eip1014() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,9 @@ public boolean eip1052() {
public boolean eip1283() {
return false;
}

@Override
public boolean eip1014() {
return false;
}
}
26 changes: 26 additions & 0 deletions ethereumj-core/src/main/java/org/ethereum/crypto/HashUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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)
*
Expand Down
5 changes: 5 additions & 0 deletions ethereumj-core/src/main/java/org/ethereum/vm/OpCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
24 changes: 24 additions & 0 deletions ethereumj-core/src/main/java/org/ethereum/vm/VM.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
71 changes: 61 additions & 10 deletions ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand All @@ -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);

Expand Down Expand Up @@ -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());
Expand All @@ -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,
Expand Down Expand Up @@ -539,6 +589,7 @@ public void createContract(DataWord value, DataWord memStart, DataWord memSize)
refundGas);
}
}
touchedAccounts.add(newAddress);
}

/**
Expand Down
11 changes: 11 additions & 0 deletions ethereumj-core/src/test/java/org/ethereum/crypto/CryptoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class BlockTestCase {
private String lastblockhash;
private int noBlockChainHistory;
private GitHubJSONTestSuite.Network network;
private SealEngine sealEngine = SealEngine.Ethash;

public BlockTestCase() {
}
Expand Down Expand Up @@ -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{" +
Expand All @@ -123,4 +132,9 @@ public String toString() {
", pre=" + pre +
'}';
}

enum SealEngine {
NoProof, // blocks in the test should not be checked for difficulty
Ethash
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,13 @@ public List<String> 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);
Expand Down