mirror of https://github.com/hyperledger/besu
[NC-1578] EIP-1052 EXTCODEHASH Operation (#19)
Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>pull/2/head
parent
d01b29f150
commit
c65938c1e4
@ -0,0 +1,33 @@ |
||||
package net.consensys.pantheon.ethereum.vm.operations; |
||||
|
||||
import net.consensys.pantheon.ethereum.core.Account; |
||||
import net.consensys.pantheon.ethereum.core.Address; |
||||
import net.consensys.pantheon.ethereum.core.Gas; |
||||
import net.consensys.pantheon.ethereum.vm.AbstractOperation; |
||||
import net.consensys.pantheon.ethereum.vm.GasCalculator; |
||||
import net.consensys.pantheon.ethereum.vm.MessageFrame; |
||||
import net.consensys.pantheon.ethereum.vm.Words; |
||||
import net.consensys.pantheon.util.bytes.Bytes32; |
||||
|
||||
public class ExtCodeHashOperation extends AbstractOperation { |
||||
|
||||
public ExtCodeHashOperation(final GasCalculator gasCalculator) { |
||||
super(0x3F, "EXTCODEHASH", 1, 1, false, 1, gasCalculator); |
||||
} |
||||
|
||||
@Override |
||||
public Gas cost(final MessageFrame frame) { |
||||
return gasCalculator().extCodeHashOperationGasCost(); |
||||
} |
||||
|
||||
@Override |
||||
public void execute(final MessageFrame frame) { |
||||
final Address address = Words.toAddress(frame.popStackItem()); |
||||
final Account account = frame.getWorldState().get(address); |
||||
if (account == null) { |
||||
frame.pushStackItem(Bytes32.ZERO); |
||||
} else { |
||||
frame.pushStackItem(account.getCodeHash()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,15 @@ |
||||
package net.consensys.pantheon.ethereum.core; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
public class HashTest { |
||||
|
||||
@Test |
||||
public void shouldGetExpectedValueForEmptyHash() { |
||||
assertThat(Hash.EMPTY) |
||||
.isEqualTo( |
||||
Hash.fromHexString("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")); |
||||
} |
||||
} |
@ -0,0 +1,131 @@ |
||||
package net.consensys.pantheon.ethereum.vm.operations; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
import net.consensys.pantheon.ethereum.chain.Blockchain; |
||||
import net.consensys.pantheon.ethereum.core.Address; |
||||
import net.consensys.pantheon.ethereum.core.AddressHelpers; |
||||
import net.consensys.pantheon.ethereum.core.BlockHeaderTestFixture; |
||||
import net.consensys.pantheon.ethereum.core.Gas; |
||||
import net.consensys.pantheon.ethereum.core.Hash; |
||||
import net.consensys.pantheon.ethereum.core.Wei; |
||||
import net.consensys.pantheon.ethereum.core.WorldUpdater; |
||||
import net.consensys.pantheon.ethereum.db.WorldStateArchive; |
||||
import net.consensys.pantheon.ethereum.mainnet.ConstantinopleGasCalculator; |
||||
import net.consensys.pantheon.ethereum.vm.Code; |
||||
import net.consensys.pantheon.ethereum.vm.MessageFrame; |
||||
import net.consensys.pantheon.ethereum.vm.MessageFrame.Type; |
||||
import net.consensys.pantheon.ethereum.vm.Words; |
||||
import net.consensys.pantheon.ethereum.worldstate.KeyValueStorageWorldStateStorage; |
||||
import net.consensys.pantheon.services.kvstore.InMemoryKeyValueStorage; |
||||
import net.consensys.pantheon.util.bytes.Bytes32; |
||||
import net.consensys.pantheon.util.bytes.BytesValue; |
||||
import net.consensys.pantheon.util.uint.UInt256; |
||||
|
||||
import java.util.ArrayDeque; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
public class ExtCodeHashOperationTest { |
||||
|
||||
private static final Address ADDRESS1 = AddressHelpers.ofValue(11111111); |
||||
private static final Address REQUESTED_ADDRESS = AddressHelpers.ofValue(22222222); |
||||
|
||||
private final Blockchain blockchain = mock(Blockchain.class); |
||||
|
||||
private final WorldStateArchive worldStateArchive = |
||||
new WorldStateArchive(new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage())); |
||||
private final WorldUpdater worldStateUpdater = worldStateArchive.getMutable().updater(); |
||||
|
||||
private final ExtCodeHashOperation operation = |
||||
new ExtCodeHashOperation(new ConstantinopleGasCalculator()); |
||||
|
||||
@Test |
||||
public void shouldCharge400Gas() { |
||||
assertThat(operation.cost(createMessageFrame(REQUESTED_ADDRESS))).isEqualTo(Gas.of(400)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnZeroWhenAccountDoesNotExist() { |
||||
final Bytes32 result = executeOperation(REQUESTED_ADDRESS); |
||||
assertThat(result).isEqualTo(Bytes32.ZERO); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnHashOfEmptyDataWhenAccountExistsButDoesNotHaveCode() { |
||||
worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); |
||||
assertThat(executeOperation(REQUESTED_ADDRESS)).isEqualTo(Hash.EMPTY); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnZeroWhenPrecompiledContractHasNoBalance() { |
||||
assertThat(executeOperation(Address.ECREC)).isEqualTo(Bytes32.ZERO); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnEmptyCodeHashWhenPrecompileHasBalance() { |
||||
// Sending money to a precompile causes it to exist in the world state archive.
|
||||
worldStateUpdater.getOrCreate(Address.ECREC).setBalance(Wei.of(10)); |
||||
assertThat(executeOperation(Address.ECREC)).isEqualTo(Hash.EMPTY); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetHashOfAccountCodeWhenCodeIsPresent() { |
||||
final BytesValue code = BytesValue.fromHexString("0xabcdef"); |
||||
worldStateUpdater.getOrCreate(REQUESTED_ADDRESS).setCode(code); |
||||
assertThat(executeOperation(REQUESTED_ADDRESS)).isEqualTo(Hash.hash(code)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldZeroOutLeftMostBitsToGetAddress() { |
||||
// If EXTCODEHASH of A is X, then EXTCODEHASH of A + 2**160 is X.
|
||||
final BytesValue code = BytesValue.fromHexString("0xabcdef"); |
||||
worldStateUpdater.getOrCreate(REQUESTED_ADDRESS).setCode(code); |
||||
final Bytes32 value = |
||||
Words.fromAddress(REQUESTED_ADDRESS) |
||||
.asUInt256() |
||||
.plus(UInt256.of(2).pow(UInt256.of(160))) |
||||
.getBytes(); |
||||
final MessageFrame frame = createMessageFrame(value); |
||||
operation.execute(frame); |
||||
assertThat(frame.getStackItem(0)).isEqualTo(Hash.hash(code)); |
||||
} |
||||
|
||||
private Bytes32 executeOperation(final Address requestedAddress) { |
||||
final MessageFrame frame = createMessageFrame(requestedAddress); |
||||
operation.execute(frame); |
||||
return frame.getStackItem(0); |
||||
} |
||||
|
||||
private MessageFrame createMessageFrame(final Address requestedAddress) { |
||||
final Bytes32 stackItem = Words.fromAddress(requestedAddress); |
||||
return createMessageFrame(stackItem); |
||||
} |
||||
|
||||
private MessageFrame createMessageFrame(final Bytes32 stackItem) { |
||||
final MessageFrame frame = |
||||
new MessageFrame.Builder() |
||||
.type(Type.MESSAGE_CALL) |
||||
.initialGas(Gas.MAX_VALUE) |
||||
.inputData(BytesValue.EMPTY) |
||||
.depth(1) |
||||
.gasPrice(Wei.ZERO) |
||||
.contract(ADDRESS1) |
||||
.address(ADDRESS1) |
||||
.originator(ADDRESS1) |
||||
.sender(ADDRESS1) |
||||
.worldState(worldStateUpdater) |
||||
.messageFrameStack(new ArrayDeque<>()) |
||||
.blockHeader(new BlockHeaderTestFixture().buildHeader()) |
||||
.value(Wei.ZERO) |
||||
.apparentValue(Wei.ZERO) |
||||
.code(new Code(BytesValue.EMPTY)) |
||||
.blockchain(blockchain) |
||||
.completer(messageFrame -> {}) |
||||
.build(); |
||||
|
||||
frame.pushStackItem(stackItem); |
||||
return frame; |
||||
} |
||||
} |
Loading…
Reference in new issue