diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetEvmRegistries.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetEvmRegistries.java index 89d6d4e35e..fd10c866f3 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetEvmRegistries.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetEvmRegistries.java @@ -12,6 +12,8 @@ */ package tech.pegasys.pantheon.ethereum.mainnet; +import static tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSpecs.ISTANBUL_ACCOUNT_VERSION; + import tech.pegasys.pantheon.ethereum.core.Account; import tech.pegasys.pantheon.ethereum.vm.EVM; import tech.pegasys.pantheon.ethereum.vm.GasCalculator; @@ -30,6 +32,7 @@ import tech.pegasys.pantheon.ethereum.vm.operations.CallOperation; import tech.pegasys.pantheon.ethereum.vm.operations.CallValueOperation; import tech.pegasys.pantheon.ethereum.vm.operations.CallerOperation; +import tech.pegasys.pantheon.ethereum.vm.operations.ChainIdOperation; import tech.pegasys.pantheon.ethereum.vm.operations.CodeCopyOperation; import tech.pegasys.pantheon.ethereum.vm.operations.CodeSizeOperation; import tech.pegasys.pantheon.ethereum.vm.operations.CoinbaseOperation; @@ -91,6 +94,10 @@ import tech.pegasys.pantheon.ethereum.vm.operations.SwapOperation; import tech.pegasys.pantheon.ethereum.vm.operations.TimestampOperation; import tech.pegasys.pantheon.ethereum.vm.operations.XorOperation; +import tech.pegasys.pantheon.util.bytes.Bytes32; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import java.math.BigInteger; /** Provides EVMs supporting the appropriate operations for mainnet hard forks. */ abstract class MainnetEvmRegistries { @@ -127,10 +134,10 @@ static EVM constantinople(final GasCalculator gasCalculator) { return new EVM(registry, new InvalidOperation(gasCalculator)); } - static EVM istanbul(final GasCalculator gasCalculator) { + static EVM istanbul(final GasCalculator gasCalculator, final BigInteger chainId) { final OperationRegistry registry = new OperationRegistry(2); - registerIstanbulOpcodes(registry, gasCalculator, Account.DEFAULT_VERSION); + registerIstanbulOpcodes(registry, gasCalculator, Account.DEFAULT_VERSION, chainId); return new EVM(registry, new InvalidOperation(gasCalculator)); } @@ -257,8 +264,13 @@ private static void registerConstantinopleOpcodes( private static void registerIstanbulOpcodes( final OperationRegistry registry, final GasCalculator gasCalculator, - final int accountVersion) { + final int accountVersion, + final BigInteger chainId) { registerConstantinopleOpcodes(registry, gasCalculator, accountVersion); - registerConstantinopleOpcodes(registry, gasCalculator, 1); + registerConstantinopleOpcodes(registry, gasCalculator, ISTANBUL_ACCOUNT_VERSION); + registry.put( + new ChainIdOperation(gasCalculator, Bytes32.leftPad(BytesValue.of(chainId.toByteArray()))), + ISTANBUL_ACCOUNT_VERSION); + registerConstantinopleOpcodes(registry, gasCalculator, ISTANBUL_ACCOUNT_VERSION); } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSpecs.java index 1c30bb6307..7b329c76cd 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSpecs.java @@ -12,6 +12,7 @@ */ package tech.pegasys.pantheon.ethereum.mainnet; +import static com.google.common.base.Preconditions.checkArgument; import static tech.pegasys.pantheon.ethereum.vm.MessageFrame.DEFAULT_MAX_STACK_SIZE; import tech.pegasys.pantheon.ethereum.MainnetBlockValidator; @@ -285,12 +286,13 @@ public static ProtocolSpecBuilder istanbulDefinition( final OptionalInt configContractSizeLimit, final OptionalInt configStackSizeLimit, final boolean enableRevertReason) { + checkArgument(chainId.isPresent(), "Istanbul requires the use of chainId"); final int contractSizeLimit = configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); final int stackSizeLimit = configStackSizeLimit.orElse(DEFAULT_MAX_STACK_SIZE); return constantinopleFixDefinition( chainId, configContractSizeLimit, configStackSizeLimit, enableRevertReason) - .evmBuilder(MainnetEvmRegistries::istanbul) + .evmBuilder(gasCalculator -> MainnetEvmRegistries.istanbul(gasCalculator, chainId.get())) .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::istanbul) .transactionProcessorBuilder( (gasCalculator, diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/vm/operations/ChainIdOperation.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/vm/operations/ChainIdOperation.java new file mode 100644 index 0000000000..1f3f6830ac --- /dev/null +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/vm/operations/ChainIdOperation.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.vm.operations; + +import tech.pegasys.pantheon.ethereum.core.Gas; +import tech.pegasys.pantheon.ethereum.vm.AbstractOperation; +import tech.pegasys.pantheon.ethereum.vm.GasCalculator; +import tech.pegasys.pantheon.ethereum.vm.MessageFrame; +import tech.pegasys.pantheon.util.bytes.Bytes32; + +public class ChainIdOperation extends AbstractOperation { + + private final Bytes32 chainId; + + public ChainIdOperation(final GasCalculator gasCalculator, final Bytes32 chainId) { + super(0x46, "CHAINID", 0, 1, false, 1, gasCalculator); + this.chainId = chainId; + } + + @Override + public Gas cost(final MessageFrame frame) { + return gasCalculator().getBaseTierGasCost(); + } + + @Override + public void execute(final MessageFrame frame) { + frame.pushStackItem(chainId); + } +} diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/operations/ChainIdOperationTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/operations/ChainIdOperationTest.java new file mode 100644 index 0000000000..4024f72021 --- /dev/null +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/operations/ChainIdOperationTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.vm.operations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import tech.pegasys.pantheon.ethereum.core.Gas; +import tech.pegasys.pantheon.ethereum.mainnet.ConstantinopleGasCalculator; +import tech.pegasys.pantheon.ethereum.vm.MessageFrame; +import tech.pegasys.pantheon.util.bytes.Bytes32; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +@RunWith(Parameterized.class) +public class ChainIdOperationTest { + + private final Bytes32 chainId; + private final int expectedGas; + private final MessageFrame messageFrame = mock(MessageFrame.class); + private final ChainIdOperation operation; + + @Parameters(name = "chainId: {0}, expectedGas: {1}") + public static Object[][] params() { + return new Object[][] { + {"0x01", 2}, + {"0x03", 2}, + {"0x04", 2}, + {"0x05", 2}, + }; + } + + public ChainIdOperationTest(final String chainIdString, final int expectedGas) { + chainId = Bytes32.fromHexString(chainIdString); + this.expectedGas = expectedGas; + operation = new ChainIdOperation(new ConstantinopleGasCalculator(), chainId); + } + + @Test + public void shouldReturnChainId() { + final ArgumentCaptor arg = ArgumentCaptor.forClass(Bytes32.class); + operation.execute(messageFrame); + Mockito.verify(messageFrame).pushStackItem(arg.capture()); + Mockito.verifyNoMoreInteractions(messageFrame); + assertThat(arg.getValue()).isEqualByComparingTo(chainId); + } + + @Test + public void shouldCalculateGasPrice() { + assertThat(operation.cost(messageFrame)).isEqualTo(Gas.of(expectedGas)); + } +}