From ab5209e3dbcce129fe1337d059cccb594e5eeaea Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Thu, 18 Apr 2024 15:59:15 +0300 Subject: [PATCH 01/11] Add ethereum transactions scenario to the acceptance tests Signed-off-by: Zhivko Kelchev --- hedera-mirror-test/build.gradle.kts | 1 + .../e2e/acceptance/client/EthereumClient.java | 225 +++++++++ .../e2e/acceptance/steps/AbstractFeature.java | 8 + .../e2e/acceptance/steps/EthCallFeature.java | 474 ++++++++++++++++++ .../acceptance/util/ethereum/EthTxData.java | 186 +++++++ .../acceptance/util/ethereum/EthTxSigs.java | 187 +++++++ .../resources/features/contract/eth.feature | 62 +++ 7 files changed, 1143 insertions(+) create mode 100644 hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/EthereumClient.java create mode 100644 hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java create mode 100644 hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/util/ethereum/EthTxData.java create mode 100644 hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/util/ethereum/EthTxSigs.java create mode 100644 hedera-mirror-test/src/test/resources/features/contract/eth.feature diff --git a/hedera-mirror-test/build.gradle.kts b/hedera-mirror-test/build.gradle.kts index d9a205f93c4..355bd56f51b 100644 --- a/hedera-mirror-test/build.gradle.kts +++ b/hedera-mirror-test/build.gradle.kts @@ -58,6 +58,7 @@ dependencies { testImplementation("org.springframework.retry:spring-retry") testImplementation("org.apache.tuweni:tuweni-bytes") testImplementation("commons-codec:commons-codec") + testImplementation("org.bouncycastle:bcprov-jdk18on:1.77") } // Disable the default test task and only run acceptance tests during the standalone "acceptance" diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/EthereumClient.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/EthereumClient.java new file mode 100644 index 00000000000..afcfed16a60 --- /dev/null +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/EthereumClient.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.mirror.test.e2e.acceptance.client; + +import com.esaulpaugh.headlong.abi.Tuple; +import com.esaulpaugh.headlong.abi.TupleType; +import com.esaulpaugh.headlong.util.Integers; +import com.hedera.hashgraph.sdk.*; +import com.hedera.mirror.test.e2e.acceptance.response.NetworkTransactionResponse; +import com.hedera.mirror.test.e2e.acceptance.util.ethereum.EthTxData; +import com.hedera.mirror.test.e2e.acceptance.util.ethereum.EthTxSigs; +import jakarta.inject.Named; +import java.math.BigInteger; +import java.util.Collection; +import java.util.HashMap; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; +import lombok.RequiredArgsConstructor; +import org.apache.tuweni.bytes.Bytes; +import org.springframework.retry.support.RetryTemplate; + +@Named +public class EthereumClient extends AbstractNetworkClient { + + private final Collection contractIds = new CopyOnWriteArrayList<>(); + + private final HashMap accountNonce = new HashMap<>(); + + public EthereumClient(SDKClient sdkClient, RetryTemplate retryTemplate) { + super(sdkClient, retryTemplate); + } + + @Override + public void clean() { + // can't delete ethereum contracts, they are immutable + log.info("Deleting {} contracts", contractIds.size()); + } + + private final TupleType LONG_TUPLE = TupleType.parse("(int64)"); + + protected byte[] gasLongToBytes(final Long gas) { + return Bytes.wrap(LONG_TUPLE.encode(Tuple.of(gas)).array()).toArray(); + } + + public static final BigInteger WEIBARS_TO_TINYBARS = BigInteger.valueOf(10_000_000_000L); + private BigInteger maxFeePerGas = WEIBARS_TO_TINYBARS.multiply(BigInteger.valueOf(50L)); + private BigInteger gasPrice = WEIBARS_TO_TINYBARS.multiply(BigInteger.valueOf(50L)); + + public NetworkTransactionResponse createContract( + PrivateKey signerKey, + FileId fileId, + String fileContents, + long gas, + Hbar payableAmount, + ContractFunctionParameters contractFunctionParameters) { + + int nonce = getNonce(signerKey); + byte[] chainId = Integers.toBytes(298); + byte[] maxPriorityGas = gasLongToBytes(20_000L); + byte[] maxGas = gasLongToBytes(maxFeePerGas.longValueExact()); + byte[] to = new byte[] {}; + BigInteger value = payableAmount != null + ? WEIBARS_TO_TINYBARS.multiply(BigInteger.valueOf(payableAmount.toTinybars())) + : BigInteger.ZERO; + // FUTURE - construct bytecode with constructor arguments + byte[] callData = Bytes.fromHexString(fileContents).toArray(); + + var ethTxData = new EthTxData( + null, + EthTxData.EthTransactionType.EIP1559, + chainId, + nonce, + gasLongToBytes(gasPrice.longValueExact()), + maxPriorityGas, + maxGas, + gas, // gasLimit + to, // to + value, // value + callData, + new byte[] {}, // accessList + 0, + null, + null, + null); + + var signedEthTxData = EthTxSigs.signMessage(ethTxData, signerKey); + signedEthTxData = signedEthTxData.replaceCallData(new byte[] {}); + + EthereumTransaction ethereumTransaction = new EthereumTransaction() + .setCallDataFileId(fileId) + .setMaxGasAllowanceHbar(Hbar.from(100L)) + .setEthereumData(signedEthTxData.encodeTx()); + + var memo = getMemo("Create contract"); + + var response = executeTransactionAndRetrieveReceipt(ethereumTransaction, null, null); + var contractId = response.getReceipt().contractId; + log.info("Created new contract {} with memo '{}' via {}", contractId, memo, response.getTransactionId()); + + TransactionRecord transactionRecord = getTransactionRecord(response.getTransactionId()); + logContractFunctionResult("constructor", transactionRecord.contractFunctionResult); + contractIds.add(contractId); + incrementNonce(signerKey); + return response; + } + + public ExecuteContractResult executeContract( + PrivateKey signerKey, + ContractId contractId, + long gas, + String functionName, + ContractFunctionParameters functionParameters, + Hbar payableAmount, + EthTxData.EthTransactionType type) { + + int nonce = getNonce(signerKey); + byte[] chainId = Integers.toBytes(298); + byte[] maxPriorityGas = gasLongToBytes(20_000L); + byte[] maxGas = gasLongToBytes(maxFeePerGas.longValueExact()); + final var address = contractId.toSolidityAddress(); + final var addressBytes = Bytes.fromHexString(address.startsWith("0x") ? address : "0x" + address); + byte[] to = addressBytes.toArray(); + var parameters = functionParameters != null ? functionParameters : new ContractFunctionParameters(); + byte[] callData = new ContractExecuteTransaction() + .setFunction(functionName, parameters) + .getFunctionParameters() + .toByteArray(); + + BigInteger value = payableAmount != null ? payableAmount.getValue().toBigInteger() : BigInteger.ZERO; + + var ethTxData = new EthTxData( + null, + type, + chainId, + nonce, + gasLongToBytes(gasPrice.longValueExact()), + maxPriorityGas, + maxGas, + gas, // gasLimit + to, // to + value, // value + callData, + new byte[] {}, // accessList + 0, + null, + null, + null); + + var signedEthTxData = EthTxSigs.signMessage(ethTxData, signerKey); + EthereumTransaction ethereumTransaction = new EthereumTransaction() + .setMaxGasAllowanceHbar(Hbar.from(100L)) + .setEthereumData(signedEthTxData.encodeTx()); + + var response = executeTransactionAndRetrieveReceipt(ethereumTransaction, null, null); + + TransactionRecord transactionRecord = getTransactionRecord(response.getTransactionId()); + logContractFunctionResult(functionName, transactionRecord.contractFunctionResult); + + log.info("Called contract {} function {} via {}", contractId, functionName, response.getTransactionId()); + incrementNonce(signerKey); + return new ExecuteContractResult(transactionRecord.contractFunctionResult, response); + } + + private void logContractFunctionResult(String functionName, ContractFunctionResult contractFunctionResult) { + if (contractFunctionResult == null) { + return; + } + + log.trace( + "ContractFunctionResult for function {}, contractId: {}, gasUsed: {}, logCount: {}", + functionName, + contractFunctionResult.contractId, + contractFunctionResult.gasUsed, + contractFunctionResult.logs.size()); + } + + @RequiredArgsConstructor + public enum NodeNameEnum { + CONSENSUS("consensus"), + MIRROR("mirror"); + + private final String name; + + static Optional of(String name) { + try { + return Optional.ofNullable(name).map(NodeNameEnum::valueOf); + } catch (Exception e) { + return Optional.empty(); + } + } + } + + public String getClientAddress() { + return sdkClient.getClient().getOperatorAccountId().toSolidityAddress(); + } + + public record ExecuteContractResult( + ContractFunctionResult contractFunctionResult, NetworkTransactionResponse networkTransactionResponse) {} + + private Integer getNonce(PrivateKey accountKey) { + return accountNonce.getOrDefault(accountKey, 0); + } + + private void incrementNonce(PrivateKey accountKey) { + if (accountNonce.containsKey(accountKey)) { + accountNonce.put(accountKey, accountNonce.get(accountKey) + 1); + } else { + accountNonce.put(accountKey, 1); + } + } +} diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java index a20bcee4ef2..11d48c6d77f 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java @@ -30,9 +30,11 @@ import com.hedera.mirror.rest.model.NetworkExchangeRateSetResponse; import com.hedera.mirror.rest.model.TransactionByIdResponse; import com.hedera.mirror.rest.model.TransactionDetail; +import com.hedera.mirror.test.e2e.acceptance.client.AccountClient; import com.hedera.mirror.test.e2e.acceptance.client.ContractClient; import com.hedera.mirror.test.e2e.acceptance.client.ContractClient.NodeNameEnum; import com.hedera.mirror.test.e2e.acceptance.client.EncoderDecoderFacade; +import com.hedera.mirror.test.e2e.acceptance.client.EthereumClient; import com.hedera.mirror.test.e2e.acceptance.client.FileClient; import com.hedera.mirror.test.e2e.acceptance.client.MirrorNodeClient; import com.hedera.mirror.test.e2e.acceptance.client.NetworkAdapter; @@ -61,6 +63,12 @@ public abstract class AbstractFeature extends EncoderDecoderFacade { @Autowired protected ContractClient contractClient; + @Autowired + protected EthereumClient ethereumClient; + + @Autowired + protected AccountClient accountClient; + @Autowired protected FileClient fileClient; diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java new file mode 100644 index 00000000000..c6e21cfc31d --- /dev/null +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.mirror.test.e2e.acceptance.steps; + +import static com.hedera.mirror.rest.model.TransactionTypes.*; +import static com.hedera.mirror.rest.model.TransactionTypes.CONTRACTCREATEINSTANCE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import com.hedera.hashgraph.sdk.*; +import com.hedera.mirror.rest.model.ContractResponse; +import com.hedera.mirror.rest.model.ContractResult; +import com.hedera.mirror.rest.model.TransactionByIdResponse; +import com.hedera.mirror.rest.model.TransactionDetail; +import com.hedera.mirror.test.e2e.acceptance.client.EthereumClient; +import com.hedera.mirror.test.e2e.acceptance.client.MirrorNodeClient; +import com.hedera.mirror.test.e2e.acceptance.props.CompiledSolidityArtifact; +import com.hedera.mirror.test.e2e.acceptance.util.FeatureInputHandler; +import com.hedera.mirror.test.e2e.acceptance.util.ethereum.EthTxData; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Comparator; +import java.util.HexFormat; +import java.util.List; +import lombok.CustomLog; +import org.springframework.http.HttpStatus; +import org.springframework.web.client.HttpClientErrorException; + +@CustomLog +public class EthCallFeature extends AbstractFeature { + + private static final int EVM_ADDRESS_SALT = 42; + private static final String ACCOUNT_EMPTY_KEYLIST = "3200"; + protected AccountId ethereum_signer_account; + protected PrivateKey ethereum_signer_private_key; + private String account; + private DeployedContract deployedParentContract; + private byte[] childContractBytecodeFromParent; + private String create2ChildContractEvmAddress; + private String create2ChildContractEntityId; + private AccountId create2ChildContractAccountId; + private ContractId create2ChildContractContractId; + + @Given("I successfully created a signer account with an EVM address alias") + public void createAccountWithEvmAddressAlias() { + ethereum_signer_private_key = PrivateKey.generateECDSA(); + ethereum_signer_account = ethereum_signer_private_key.getPublicKey().toAccountId(0, 0); + + networkTransactionResponse = accountClient.sendCryptoTransfer(ethereum_signer_account, Hbar.from(500L), null); + + assertNotNull(networkTransactionResponse.getTransactionId()); + assertNotNull(networkTransactionResponse.getReceipt()); + } + + @Then("validate the signer account and its balance") + public void verifyAccountCreated() { + var accountInfo = mirrorClient.getAccountDetailsUsingAlias(ethereum_signer_account); + account = accountInfo.getAccount(); + var transactions = mirrorClient + .getTransactions(networkTransactionResponse.getTransactionIdStringNoCheckSum()) + .getTransactions() + .stream() + .sorted(Comparator.comparing(TransactionDetail::getConsensusTimestamp)) + .toList(); + + assertThat(accountInfo.getAccount()).isNotNull(); + assertThat(accountInfo.getBalance().getBalance()) + .isEqualTo(Hbar.from(500L).toTinybars()); + + assertThat(accountInfo.getTransactions()).hasSize(1); + assertThat(transactions).hasSize(2); + + var createAccountTransaction = transactions.get(0); + var transferTransaction = transactions.get(1); + + assertThat(transferTransaction) + .usingRecursiveComparison() + .ignoringFields("assessedCustomFees") + .isEqualTo(accountInfo.getTransactions().get(0)); + + assertThat(createAccountTransaction.getName()).isEqualTo(CRYPTOCREATEACCOUNT); + assertThat(createAccountTransaction.getConsensusTimestamp()).isEqualTo(accountInfo.getCreatedTimestamp()); + } + + @Given("I successfully create parent contract by ethereum transaction") + public void createNewERCtestContract() { + deployedParentContract = ethereumContractCreate(ContractResource.PARENT_CONTRACT); + deployedParentContract.contractId().toSolidityAddress(); + } + + @Then("the mirror node REST API should return status {int} for the eth contract creation transaction") + public void verifyMirrorAPIContractCreationResponses(int status) { + var mirrorTransaction = verifyEthereumContractCreate(mirrorClient, status, true); + assertThat(mirrorTransaction.getEntityId()) + .isEqualTo(deployedParentContract.contractId().toString()); + } + + @Then("the mirror node REST API should return status {int} for the ethereum transaction") + public void verifyMirrorAPIContractResponses(int status) { + var mirrorTransaction = verifyMirrorTransactionsResponse(mirrorClient, status); + assertThat(mirrorTransaction.getEntityId()) + .isEqualTo(deployedParentContract.contractId().toString()); + } + + @Then("the mirror node REST API should verify the deployed contract entity by eth call") + public void verifyDeployedContractMirror() { + verifyContractFromMirror(false); + verifyContractExecutionResultsById(true); + verifyContractExecutionResultsByTransactionId(true); + } + + @Given("I successfully call the child creation function using EIP-1559 ethereum transaction") + public void callContract() { + ContractFunctionParameters parameters = new ContractFunctionParameters().addUint256(BigInteger.valueOf(1000)); + + executeEthereumTransaction( + deployedParentContract.contractId(), + "createChild", + parameters, + null, + EthTxData.EthTransactionType.EIP1559); + } + + @Then("the mirror node REST API should verify the child creation ethereum transaction") + public void verifyChildCreationEthereumCall() { + verifyContractFromMirror(false); + verifyContractExecutionResultsById(true); + verifyContractExecutionResultsByTransactionId(true); + } + + @Then("the mirror node REST API should verify the ethereum called contract function") + public void verifyContractFunctionCallMirror() { + verifyContractFromMirror(false); + verifyContractExecutionResultsById(false); + verifyContractExecutionResultsByTransactionId(false); + } + + @Given("I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction") + public void getChildBytecode() { + var executeContractResult = executeEthereumTransaction( + deployedParentContract.contractId(), + "getBytecode", + null, + null, + EthTxData.EthTransactionType.LEGACY_ETHEREUM); + + childContractBytecodeFromParent = + executeContractResult.contractFunctionResult().getBytes(0); + assertNotNull(childContractBytecodeFromParent); + } + + @When( + "I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction") + public void getChildAddress() { + ContractFunctionParameters parameters = new ContractFunctionParameters() + .addBytes(childContractBytecodeFromParent) + .addUint256(BigInteger.valueOf(EVM_ADDRESS_SALT)); + + var executeContractResult = executeEthereumTransaction( + deployedParentContract.contractId(), + "getAddress", + parameters, + null, + EthTxData.EthTransactionType.EIP2930); + + create2ChildContractEvmAddress = + executeContractResult.contractFunctionResult().getAddress(0); + create2ChildContractAccountId = AccountId.fromEvmAddress(create2ChildContractEvmAddress); + create2ChildContractContractId = ContractId.fromEvmAddress(0, 0, create2ChildContractEvmAddress); + } + + @And("I create a hollow account using CryptoTransfer of {int} to the child's evm address") + public void createHollowAccountWithCryptoTransfertoEvmAddress(int amount) { + networkTransactionResponse = + accountClient.sendCryptoTransfer(create2ChildContractAccountId, Hbar.fromTinybars(amount), null); + + assertNotNull(networkTransactionResponse.getTransactionId()); + assertNotNull(networkTransactionResponse.getReceipt()); + } + + @And("the mirror node REST API should verify the account is hollow and has {int}") + public void verifyMirrorAPIHollowAccountResponse(int amount) { + var mirrorAccountResponse = mirrorClient.getAccountDetailsUsingEvmAddress(create2ChildContractAccountId); + create2ChildContractEntityId = mirrorAccountResponse.getAccount(); + + var transactions = mirrorClient + .getTransactions(networkTransactionResponse.getTransactionIdStringNoCheckSum()) + .getTransactions() + .stream() + .sorted(Comparator.comparing(TransactionDetail::getConsensusTimestamp)) + .toList(); + + assertEquals(2, transactions.size()); + assertEquals(CRYPTOCREATEACCOUNT, transactions.get(0).getName()); + assertEquals(CRYPTOTRANSFER, transactions.get(1).getName()); + + assertNotNull(mirrorAccountResponse.getAccount()); + assertEquals(amount, mirrorAccountResponse.getBalance().getBalance()); + // Hollow account indicated by not having a public key defined. + assertEquals(ACCOUNT_EMPTY_KEYLIST, mirrorAccountResponse.getKey().getKey()); + } + + @And("the mirror node REST API should not find a contract when using child's evm address") + public void verifyMirrorAPIContractNotFoundResponse() { + try { + mirrorClient.getContractInfo(create2ChildContractEvmAddress); + fail("Did not expect to find contract at EVM address"); + } catch (HttpClientErrorException e) { + assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode()); + } + } + + @When("I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559") + public void createChildContractUsingCreate2() { + ContractFunctionParameters parameters = new ContractFunctionParameters() + .addBytes(childContractBytecodeFromParent) + .addUint256(BigInteger.valueOf(EVM_ADDRESS_SALT)); + executeEthereumTransaction( + deployedParentContract.contractId(), + "create2Deploy", + parameters, + null, + EthTxData.EthTransactionType.EIP1559); + } + + @And("the mirror node REST API should retrieve the contract when using child's evm address") + public void verifyMirrorAPIContractFoundResponse() { + var mirrorContractResponse = mirrorClient.getContractInfo(create2ChildContractEvmAddress); + var transactions = mirrorClient + .getTransactions(networkTransactionResponse.getTransactionIdStringNoCheckSum()) + .getTransactions() + .stream() + .sorted(Comparator.comparing(TransactionDetail::getConsensusTimestamp)) + .toList(); + + assertNotNull(transactions); + assertEquals(2, transactions.size()); + assertEquals( + deployedParentContract.contractId().toString(), + transactions.get(0).getEntityId()); + assertEquals(ETHEREUMTRANSACTION, transactions.get(0).getName()); + assertEquals(create2ChildContractEntityId, transactions.get(1).getEntityId()); + assertEquals(CONTRACTCREATEINSTANCE, transactions.get(1).getName()); + + String childContractBytecodeFromParentHex = HexFormat.of().formatHex(childContractBytecodeFromParent); + assertEquals( + childContractBytecodeFromParentHex, + mirrorContractResponse.getBytecode().replaceFirst("0x", "")); + assertEquals( + create2ChildContractEvmAddress, + mirrorContractResponse.getEvmAddress().replaceFirst("0x", "")); + } + + @And("the mirror node REST API should verify that the account is not hollow") + public void verifyMirrorAPIFullAccountResponse() { + var mirrorAccountResponse = mirrorClient.getAccountDetailsUsingEvmAddress(create2ChildContractAccountId); + assertNotNull(mirrorAccountResponse.getAccount()); + assertNotEquals(ACCOUNT_EMPTY_KEYLIST, mirrorAccountResponse.getKey().getKey()); + } + + @When( + "I successfully delete the child contract by calling vacateAddress function using EIP-1559 ethereum transaction") + public void deleteChildContractUsingSelfDestruct() { + executeEthereumTransaction( + create2ChildContractContractId, "vacateAddress", null, null, EthTxData.EthTransactionType.EIP1559); + } + + @Then("the mirror node REST API should return status {int} for the vacateAddress call transaction") + public void verifyMirrorAPIContractChildSelfDestructResponses(int status) { + var mirrorTransaction = verifyMirrorTransactionsResponse(mirrorClient, status); + assertThat(mirrorTransaction.getEntityId()).isEqualTo(create2ChildContractEntityId); + } + + @Given("I successfully delete the parent bytecode file") + public void deleteParentContractFile() { + networkTransactionResponse = fileClient.deleteFile(deployedParentContract.fileId()); + + assertNotNull(networkTransactionResponse.getTransactionId()); + assertNotNull(networkTransactionResponse.getReceipt()); + } + + @And("the mirror node REST API should return status {int} for the transfer transaction") + public void verifyMirrorAPIAccountResponses(int status) { + verifyMirrorTransactionsResponse(mirrorClient, status); + } + + public DeployedContract ethereumContractCreate(ContractResource contractResource) { + var resource = resourceLoader.getResource(contractResource.getPath()); + try (var in = resource.getInputStream()) { + CompiledSolidityArtifact compiledSolidityArtifact = readCompiledArtifact(in); + var fileContent = compiledSolidityArtifact.getBytecode().replaceFirst("0x", ""); + var fileId = persistContractBytes(fileContent); + + networkTransactionResponse = ethereumClient.createContract( + ethereum_signer_private_key, + fileId, + fileContent, + ethereumClient + .getSdkClient() + .getAcceptanceTestProperties() + .getFeatureProperties() + .getMaxContractFunctionGas(), + contractResource.getInitialBalance() == 0 + ? null + : Hbar.fromTinybars(contractResource.getInitialBalance()), + null); + ContractId contractId = verifyCreateContractNetworkResponse(); + return new DeployedContract(fileId, contractId, compiledSolidityArtifact); + } catch (IOException e) { + log.warn("Issue creating contract: {}, ex: {}", contractResource, e); + throw new RuntimeException(e); + } + } + + private EthereumClient.ExecuteContractResult executeEthereumTransaction( + ContractId contractId, + String functionName, + ContractFunctionParameters parameters, + Hbar payableAmount, + EthTxData.EthTransactionType type) { + + EthereumClient.ExecuteContractResult executeContractResult = ethereumClient.executeContract( + ethereum_signer_private_key, + contractId, + contractClient + .getSdkClient() + .getAcceptanceTestProperties() + .getFeatureProperties() + .getMaxContractFunctionGas(), + functionName, + parameters, + payableAmount, + type); + + networkTransactionResponse = executeContractResult.networkTransactionResponse(); + assertNotNull(networkTransactionResponse.getTransactionId()); + assertNotNull(networkTransactionResponse.getReceipt()); + assertNotNull(executeContractResult.contractFunctionResult()); + + return executeContractResult; + } + + private ContractResponse verifyContractFromMirror(boolean isDeleted) { + var mirrorContract = + mirrorClient.getContractInfo(deployedParentContract.contractId().toString()); + + assertNotNull(mirrorContract); + assertThat(mirrorContract.getAutoRenewPeriod()).isNotNull(); + assertThat(mirrorContract.getBytecode()).isNotBlank(); + assertThat(mirrorContract.getContractId()) + .isEqualTo(deployedParentContract.contractId().toString()); + assertThat(mirrorContract.getCreatedTimestamp()).isNotBlank(); + assertThat(mirrorContract.getDeleted()).isEqualTo(isDeleted); + assertThat(mirrorContract.getFileId()) + .isEqualTo(deployedParentContract.fileId().toString()); + assertThat(mirrorContract.getMemo()).isNotBlank(); + String address = mirrorContract.getEvmAddress(); + assertThat(address).isNotBlank().isNotEqualTo("0x").isNotEqualTo("0x0000000000000000000000000000000000000000"); + assertThat(mirrorContract.getTimestamp()).isNotNull(); + assertThat(mirrorContract.getTimestamp().getFrom()).isNotNull(); + + if (contractClient + .getSdkClient() + .getAcceptanceTestProperties() + .getFeatureProperties() + .isSidecars()) { + assertThat(mirrorContract.getRuntimeBytecode()).isNotNull(); + } + + assertThat(mirrorContract.getBytecode()) + .isEqualTo(deployedParentContract.compiledSolidityArtifact().getBytecode()); + + if (isDeleted) { + assertThat(mirrorContract.getObtainerId()) + .isEqualTo(contractClient + .getSdkClient() + .getExpandedOperatorAccountId() + .getAccountId() + .toString()); + } else { + assertThat(mirrorContract.getObtainerId()).isNull(); + } + + return mirrorContract; + } + + private void verifyContractExecutionResultsById(boolean isCreation) { + List contractResults = mirrorClient + .getContractResultsById(deployedParentContract.contractId().toString()) + .getResults(); + + assertThat(contractResults) + .isNotEmpty() + .allSatisfy(result -> verifyEthereumContractExecutionResults(result, isCreation)); + } + + private void verifyContractExecutionResultsByTransactionId(boolean isCreation) { + ContractResult contractResult = mirrorClient.getContractResultByTransactionId( + networkTransactionResponse.getTransactionIdStringNoCheckSum()); + + verifyEthereumContractExecutionResults(contractResult, isCreation); + assertThat(contractResult.getBlockHash()).isNotBlank(); + assertThat(contractResult.getBlockNumber()).isPositive(); + assertThat(contractResult.getHash()).isNotBlank(); + } + + private void verifyEthereumContractExecutionResults(ContractResult contractResult, boolean isCreation) { + assertThat(contractResult.getCallResult()).isNotBlank(); + assertThat(contractResult.getContractId()) + .isEqualTo(deployedParentContract.contractId().toString()); + var createdIds = contractResult.getCreatedContractIds(); + if (isCreation) { + assertThat(createdIds).isNotEmpty(); + } + assertThat(contractResult.getErrorMessage()).isBlank(); + assertThat(contractResult.getFailedInitcode()).isBlank(); + assertThat(contractResult.getFrom()).isEqualTo(FeatureInputHandler.evmAddress(AccountId.fromString(account))); + assertThat(contractResult.getGasLimit()) + .isEqualTo(contractClient + .getSdkClient() + .getAcceptanceTestProperties() + .getFeatureProperties() + .getMaxContractFunctionGas()); + assertThat(contractResult.getGasUsed()).isPositive(); + assertThat(contractResult.getTo()) + .isEqualTo(FeatureInputHandler.evmAddress(deployedParentContract.contractId())); + } + + protected TransactionDetail verifyEthereumContractCreate( + MirrorNodeClient mirrorClient, int status, boolean finalizeHollowAccount) { + String transactionId = networkTransactionResponse.getTransactionIdStringNoCheckSum(); + TransactionByIdResponse mirrorTransactionsResponse = mirrorClient.getTransactions(transactionId); + + List transactions = mirrorTransactionsResponse.getTransactions(); + assertNotNull(transactions); + assertThat(transactions).isNotEmpty(); + TransactionDetail mirrorTransaction; + + mirrorTransaction = finalizeHollowAccount ? transactions.get(1) : transactions.get(0); + + if (status == HttpStatus.OK.value()) { + assertThat(mirrorTransaction.getResult()).isEqualTo("SUCCESS"); + } + + assertThat(mirrorTransaction.getValidStartTimestamp()).isNotNull(); + assertThat(mirrorTransaction.getName()).isNotNull(); + assertThat(mirrorTransaction.getResult()).isNotNull(); + assertThat(mirrorTransaction.getConsensusTimestamp()).isNotNull(); + + assertThat(mirrorTransaction.getValidStartTimestamp()) + .isEqualTo(networkTransactionResponse.getValidStartString()); + assertThat(mirrorTransaction.getTransactionId()).isEqualTo(transactionId); + + return mirrorTransaction; + } +} diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/util/ethereum/EthTxData.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/util/ethereum/EthTxData.java new file mode 100644 index 00000000000..0e8e04be19a --- /dev/null +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/util/ethereum/EthTxData.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.mirror.test.e2e.acceptance.util.ethereum; + +import com.esaulpaugh.headlong.rlp.RLPEncoder; +import com.esaulpaugh.headlong.util.Integers; +import com.google.common.base.MoreObjects; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import org.apache.commons.codec.binary.Hex; + +public record EthTxData( + byte[] rawTx, + EthTransactionType type, + byte[] chainId, + long nonce, + byte[] gasPrice, + byte[] maxPriorityGas, + byte[] maxGas, + long gasLimit, + byte[] to, + BigInteger value, + byte[] callData, + byte[] accessList, + int recId, + byte[] v, + byte[] r, + byte[] s) { + + public EthTxData replaceCallData(final byte[] newCallData) { + return new EthTxData( + null, + type, + chainId, + nonce, + gasPrice, + maxPriorityGas, + maxGas, + gasLimit, + to, + value, + newCallData, + accessList, + recId, + v, + r, + s); + } + + public byte[] encodeTx() { + if (accessList != null && accessList.length > 0) { + throw new IllegalStateException("Re-encoding access list is unsupported"); + } + return switch (type) { + case LEGACY_ETHEREUM -> RLPEncoder.list( + Integers.toBytes(nonce), + gasPrice, + Integers.toBytes(gasLimit), + to, + Integers.toBytesUnsigned(value), + callData, + v, + r, + s); + case EIP2930 -> RLPEncoder.sequence( + Integers.toBytes(0x01), + List.of( + chainId, + Integers.toBytes(nonce), + gasPrice, + Integers.toBytes(gasLimit), + to, + Integers.toBytesUnsigned(value), + callData, + List.of(/*accessList*/ ), + Integers.toBytes(recId), + r, + s)); + case EIP1559 -> RLPEncoder.sequence( + Integers.toBytes(0x02), + List.of( + chainId, + Integers.toBytes(nonce), + maxPriorityGas, + maxGas, + Integers.toBytes(gasLimit), + to, + Integers.toBytesUnsigned(value), + callData, + List.of(/*accessList*/ ), + Integers.toBytes(recId), + r, + s)); + }; + } + + public enum EthTransactionType { + LEGACY_ETHEREUM, + EIP2930, + EIP1559, + } + + @Override + public boolean equals(final Object other) { + if (this == other) return true; + if (other == null || getClass() != other.getClass()) return false; + + final EthTxData ethTxData = (EthTxData) other; + + return (nonce == ethTxData.nonce) + && (gasLimit == ethTxData.gasLimit) + && (recId == ethTxData.recId) + && (Arrays.equals(rawTx, ethTxData.rawTx)) + && (type == ethTxData.type) + && (Arrays.equals(chainId, ethTxData.chainId)) + && (Arrays.equals(gasPrice, ethTxData.gasPrice)) + && (Arrays.equals(maxPriorityGas, ethTxData.maxPriorityGas)) + && (Arrays.equals(maxGas, ethTxData.maxGas)) + && (Arrays.equals(to, ethTxData.to)) + && (Objects.equals(value, ethTxData.value)) + && (Arrays.equals(callData, ethTxData.callData)) + && (Arrays.equals(accessList, ethTxData.accessList)) + && (Arrays.equals(v, ethTxData.v)) + && (Arrays.equals(r, ethTxData.r)) + && (Arrays.equals(s, ethTxData.s)); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(rawTx); + result = 31 * result + (type != null ? type.hashCode() : 0); + result = 31 * result + Arrays.hashCode(chainId); + result = 31 * result + (int) (nonce ^ (nonce >>> 32)); + result = 31 * result + Arrays.hashCode(gasPrice); + result = 31 * result + Arrays.hashCode(maxPriorityGas); + result = 31 * result + Arrays.hashCode(maxGas); + result = 31 * result + (int) (gasLimit ^ (gasLimit >>> 32)); + result = 31 * result + Arrays.hashCode(to); + result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + Arrays.hashCode(callData); + result = 31 * result + Arrays.hashCode(accessList); + result = 31 * result + recId; + result = 31 * result + Arrays.hashCode(v); + result = 31 * result + Arrays.hashCode(r); + result = 31 * result + Arrays.hashCode(s); + return result; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("rawTx", rawTx == null ? null : Hex.encodeHexString(rawTx)) + .add("type", type) + .add("chainId", chainId == null ? null : Hex.encodeHexString(chainId)) + .add("nonce", nonce) + .add("gasPrice", gasPrice == null ? null : Hex.encodeHexString(gasPrice)) + .add("maxPriorityGas", maxPriorityGas == null ? null : Hex.encodeHexString(maxPriorityGas)) + .add("maxGas", maxGas == null ? null : Hex.encodeHexString(maxGas)) + .add("gasLimit", gasLimit) + .add("to", to == null ? null : Hex.encodeHexString(to)) + .add("value", value) + .add("callData", Hex.encodeHexString(callData)) + .add("accessList", accessList == null ? null : Hex.encodeHexString(accessList)) + .add("recId", recId) + .add("v", v == null ? null : Hex.encodeHexString(v)) + .add("r", Hex.encodeHexString(r)) + .add("s", Hex.encodeHexString(s)) + .toString(); + } +} diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/util/ethereum/EthTxSigs.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/util/ethereum/EthTxSigs.java new file mode 100644 index 00000000000..b2b94730707 --- /dev/null +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/util/ethereum/EthTxSigs.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.mirror.test.e2e.acceptance.util.ethereum; + +import static com.hedera.mirror.test.e2e.acceptance.util.ethereum.EthTxData.EthTransactionType.LEGACY_ETHEREUM; + +import com.esaulpaugh.headlong.rlp.RLPEncoder; +import com.esaulpaugh.headlong.util.Integers; +import com.hedera.hashgraph.sdk.PrivateKey; +import java.math.BigInteger; +import java.util.Arrays; +import org.apache.tuweni.bytes.Bytes32; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9IntegerConverter; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.jcajce.provider.digest.Keccak; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECPoint; + +public record EthTxSigs(byte[] publicKey, byte[] address) { + + static final X9ECParameters ECDSA_SECP256K1_CURVE = SECNamedCurves.getByName("secp256k1"); + static final ECDomainParameters ECDSA_SECP256K1_DOMAIN = new ECDomainParameters( + ECDSA_SECP256K1_CURVE.getCurve(), + ECDSA_SECP256K1_CURVE.getG(), + ECDSA_SECP256K1_CURVE.getN(), + ECDSA_SECP256K1_CURVE.getH()); + + public static EthTxData signMessage(EthTxData ethTx, PrivateKey privateKey) { + byte[] message = calculateSignableMessage(ethTx); + + final byte[] sig = privateKey.sign(message); + // wrap in signature object + final byte[] r = new byte[32]; + System.arraycopy(sig, 0, r, 0, 32); + final byte[] s = new byte[32]; + System.arraycopy(sig, 32, s, 0, 32); + + // FUTURE - this part of recId calculation is not present in the SDK, we should move it there + var hash = new Keccak.Digest256().digest(message); + int recId = 0; + var publicKey = privateKey.getPublicKey().toBytesRaw(); + for (int i = 0; i < 4; i++) { + byte[] k = recoverFromSignature(i, new BigInteger(1, r), new BigInteger(1, s), Bytes32.wrap(hash)); + if (k != null && Arrays.equals(k, publicKey)) { + recId = i; + break; + } + } + + BigInteger val; + // calulations originate from https://eips.ethereum.org/EIPS/eip-155 + if (ethTx.type() == LEGACY_ETHEREUM) { + if (ethTx.chainId() == null || ethTx.chainId().length == 0) { + val = BigInteger.valueOf(27L + recId); + } else { + val = BigInteger.valueOf(35L + recId).add(new BigInteger(1, ethTx.chainId()).multiply(BigInteger.TWO)); + } + } else { + val = null; + } + + return new EthTxData( + ethTx.rawTx(), + ethTx.type(), + ethTx.chainId(), + ethTx.nonce(), + ethTx.gasPrice(), + ethTx.maxPriorityGas(), + ethTx.maxGas(), + ethTx.gasLimit(), + ethTx.to(), + ethTx.value(), + ethTx.callData(), + ethTx.accessList(), + (byte) recId, + val == null ? null : val.toByteArray(), + r, + s); + } + + public static byte[] calculateSignableMessage(EthTxData ethTx) { + return switch (ethTx.type()) { + case LEGACY_ETHEREUM -> (ethTx.chainId() != null && ethTx.chainId().length > 0) + ? RLPEncoder.list( + Integers.toBytes(ethTx.nonce()), + ethTx.gasPrice(), + Integers.toBytes(ethTx.gasLimit()), + ethTx.to(), + Integers.toBytesUnsigned(ethTx.value()), + ethTx.callData(), + ethTx.chainId(), + Integers.toBytes(0), + Integers.toBytes(0)) + : RLPEncoder.list( + Integers.toBytes(ethTx.nonce()), + ethTx.gasPrice(), + Integers.toBytes(ethTx.gasLimit()), + ethTx.to(), + Integers.toBytesUnsigned(ethTx.value()), + ethTx.callData()); + case EIP1559 -> RLPEncoder.sequence(Integers.toBytes(2), new Object[] { + ethTx.chainId(), + Integers.toBytes(ethTx.nonce()), + ethTx.maxPriorityGas(), + ethTx.maxGas(), + Integers.toBytes(ethTx.gasLimit()), + ethTx.to(), + Integers.toBytesUnsigned(ethTx.value()), + ethTx.callData(), + new Object[0] + }); + case EIP2930 -> RLPEncoder.sequence(Integers.toBytes(1), new Object[] { + ethTx.chainId(), + Integers.toBytes(ethTx.nonce()), + ethTx.gasPrice(), + Integers.toBytes(ethTx.gasLimit()), + ethTx.to(), + Integers.toBytesUnsigned(ethTx.value()), + ethTx.callData(), + new Object[0] + }); + }; + } + + /** + * Given the components of a signature and a selector value, recover and return the public key that generated the + * signature according to the algorithm in SEC1v2 section 4.1.6. + * + * @param recId Which possible key to recover. + * @param r The R component of the signature. + * @param s The S component of the signature. + * @param messageHash Hash of the data that was signed. + * @return A ECKey containing only the public part, or {@code null} if recovery wasn't possible. + */ + public static byte[] recoverFromSignature(int recId, BigInteger r, BigInteger s, Bytes32 messageHash) { + assert (recId == 0 || recId == 1); + assert (r.signum() >= 0); + assert (s.signum() >= 0); + assert (messageHash != null); + + ECPoint R = decompressKey(r, (recId & 1) == 1); + if (R == null || !R.multiply(ECDSA_SECP256K1_DOMAIN.getN()).isInfinity()) { + return null; + } + + BigInteger e = messageHash.toUnsignedBigInteger(); + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(ECDSA_SECP256K1_DOMAIN.getN()); + BigInteger rInv = r.modInverse(ECDSA_SECP256K1_DOMAIN.getN()); + BigInteger srInv = rInv.multiply(s).mod(ECDSA_SECP256K1_DOMAIN.getN()); + BigInteger eInvrInv = rInv.multiply(eInv).mod(ECDSA_SECP256K1_DOMAIN.getN()); + ECPoint q = ECAlgorithms.sumOfTwoMultiplies(ECDSA_SECP256K1_DOMAIN.getG(), eInvrInv, R, srInv); + + if (q.isInfinity()) { + return null; + } + + return q.getEncoded(true); + } + + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + var X_9_INTEGER_CONVERTER = new X9IntegerConverter(); + byte[] compEnc = X_9_INTEGER_CONVERTER.integerToBytes( + xBN, 1 + X_9_INTEGER_CONVERTER.getByteLength(ECDSA_SECP256K1_DOMAIN.getCurve())); + compEnc[0] = (byte) (yBit ? 0x03 : 0x02); + try { + return ECDSA_SECP256K1_DOMAIN.getCurve().decodePoint(compEnc); + } catch (IllegalArgumentException e) { + return null; + } + } +} diff --git a/hedera-mirror-test/src/test/resources/features/contract/eth.feature b/hedera-mirror-test/src/test/resources/features/contract/eth.feature new file mode 100644 index 00000000000..574e2cb2bde --- /dev/null +++ b/hedera-mirror-test/src/test/resources/features/contract/eth.feature @@ -0,0 +1,62 @@ +@contractbase @fullsuite +Feature: Ethereum transactions Coverage Feature + + @critical @release @acceptance + Scenario Outline: Validate Ethereum Contract create and call + + The steps in this scenario are very similar to those in contract feature. The update and the deletion + steps are skipped because of the immutability of the contracts that are created by ethereum transactions. + + Using the Java SDK and the mirror node REST API, create and call a smart contract and + verify proper operation. Use that same parent contract to create a second child contract using + the CREATE2 flow, namely by retrieving the child contract bytecode and using it, and a salt value, + to compute the child's EVM address and then deploying the contract to that address. + + Once the child EVM address is known, issue a crypto transfer transaction to that EVM address, creating + a hollow account. Verify the account exists and indeed is hollow, but that a contract does not yet + exist at that EVM address. Then, using a parent contract function, deploy the child contract using + the CREATE2 opcode and with the same bytecode and salt, thus this is done to the child EVM address. + + With the contract now present, and the account no longer hollow, invoke a function on the child contract + to cause its self destruction, vacating the EVM address for future use. Also, Finally, delete the original parent + contract and its underlying bytecode file. + + Given I successfully created a signer account with an EVM address alias + Then validate the signer account and its balance + + Given I successfully create parent contract by ethereum transaction + Then the mirror node REST API should return status for the eth contract creation transaction + And the mirror node REST API should verify the deployed contract entity by eth call + + When I successfully call the child creation function using EIP-1559 ethereum transaction + Then the mirror node REST API should return status for the ethereum transaction + And the mirror node REST API should verify the child creation ethereum transaction + + Given I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction + Then the mirror node REST API should return status for the ethereum transaction + And the mirror node REST API should verify the ethereum called contract function + + When I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction + Then the mirror node REST API should return status for the ethereum transaction + And the mirror node REST API should verify the ethereum called contract function + + And I create a hollow account using CryptoTransfer of to the child's evm address + Then the mirror node REST API should return status for the transfer transaction + And the mirror node REST API should verify the account is hollow and has + And the mirror node REST API should not find a contract when using child's evm address + + When I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559 + Then the mirror node REST API should return status for the ethereum transaction + And the mirror node REST API should retrieve the contract when using child's evm address + And the mirror node REST API should verify that the account is not hollow + + When I successfully delete the child contract by calling vacateAddress function using EIP-1559 ethereum transaction + Then the mirror node REST API should return status for the vacateAddress call transaction + Then I successfully delete the parent bytecode file + + + + + Examples: + | httpStatusCode | transferAmount | + | 200 | 1000 | \ No newline at end of file From ef65fc1b24883ee21dbc0a32ec66a73b50da884f Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Tue, 23 Apr 2024 09:44:02 +0300 Subject: [PATCH 02/11] upgrade bouncycastle Signed-off-by: Zhivko Kelchev --- hedera-mirror-test/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hedera-mirror-test/build.gradle.kts b/hedera-mirror-test/build.gradle.kts index 355bd56f51b..75ba6b095f3 100644 --- a/hedera-mirror-test/build.gradle.kts +++ b/hedera-mirror-test/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { testImplementation("org.springframework.retry:spring-retry") testImplementation("org.apache.tuweni:tuweni-bytes") testImplementation("commons-codec:commons-codec") - testImplementation("org.bouncycastle:bcprov-jdk18on:1.77") + testImplementation("org.bouncycastle:bcprov-jdk18on:1.78") } // Disable the default test task and only run acceptance tests during the standalone "acceptance" From afa7974f4e9027747fa8416a432087616a4ef3a9 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Tue, 30 Apr 2024 14:26:09 +0300 Subject: [PATCH 03/11] test: add gasConsumed tests Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../steps/AbstractEstimateFeature.java | 72 ++++++++++++++++++ .../e2e/acceptance/steps/AbstractFeature.java | 12 ++- .../e2e/acceptance/steps/EstimateFeature.java | 76 ++----------------- .../e2e/acceptance/steps/EthCallFeature.java | 53 +++++++++++-- .../resources/features/contract/eth.feature | 12 +-- 5 files changed, 139 insertions(+), 86 deletions(-) diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java index ba3e9c8a26b..afdfb595547 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java @@ -19,16 +19,26 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.hedera.mirror.rest.model.ContractAction; +import com.hedera.mirror.rest.model.ContractActionsResponse; import com.hedera.mirror.rest.model.ContractCallResponse; +import com.hedera.mirror.rest.model.ContractResult; import com.hedera.mirror.test.e2e.acceptance.client.MirrorNodeClient; import com.hedera.mirror.test.e2e.acceptance.util.ModelBuilder; +import java.util.List; import java.util.Optional; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; import org.apache.tuweni.bytes.Bytes; +import org.assertj.core.api.AssertionsForClassTypes; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.client.HttpClientErrorException; abstract class AbstractEstimateFeature extends AbstractFeature { + private static final int BASE_GAS_FEE = 21_000; + private static final int ADDITIONAL_FEE_FOR_CREATE = 32_000; + protected int lowerDeviation; protected int upperDeviation; @@ -115,4 +125,66 @@ protected void assertEthCallReturnsBadRequest(String block, String data, String assertThatThrownBy(() -> mirrorClient.contractsCall(contractCallRequest)) .isInstanceOf(HttpClientErrorException.BadRequest.class); } + + protected void verifyGasConsumed(String txId, Object data) { + int totalGasFee; + try { + totalGasFee = calculateIntrinsicValue(data); + } catch (DecoderException e) { + throw new RuntimeException("Failed to decode hexadecimal string.", e); + } + var gasConsumed = getGasConsumedByTransactionId(txId); + var gasUsed = getGasFromActions(txId); + AssertionsForClassTypes.assertThat(gasConsumed).isEqualTo(gasUsed + totalGasFee); + } + + /** + * Calculates the total intrinsic gas required for a given operation, taking into account the + * operation type and the data involved. This method adjusts the gas calculation based + * on the type of operation: contract creation (CREATE) operations, indicated by a hexadecimal + * string input, include an additional fee on top of the base gas fee. The intrinsic gas for + * the data payload is calculated by adding a specific gas amount for each byte in the payload, + * with different amounts for zero and non-zero bytes. + * + * @param data The operation data, which can be a hexadecimal string for CREATE operations or + * a byte array for contract call operations. + * @return The total intrinsic gas calculated for the operation + * @throws DecoderException If the data parameter is a String and cannot be decoded from hexadecimal + * format, indicating an issue with the input format. + * @throws IllegalArgumentException If the data parameter is not an instance of String or byte[], + * indicating that the provided data type is unsupported for gas + * calculation in the context of this method and tests. + */ + private int calculateIntrinsicValue(Object data) throws DecoderException { + int total = BASE_GAS_FEE; + byte[] values; + if (data instanceof String) { + values = Hex.decodeHex(((String) data).replaceFirst("0x", "")); + total += ADDITIONAL_FEE_FOR_CREATE; + } else if (data instanceof byte[]) { + values = (byte[]) data; + } else { + throw new IllegalArgumentException("Unsupported data type for gas calculation."); + } + + // Calculates the intrinsic value by adding 4 for each 0 bytes and 16 for non-zero bytes + for (byte value : values) { + total += (value == 0) ? 4 : 16; + } + return total; + } + + private long getGasFromActions(String transactionId) { + return Optional.ofNullable(mirrorClient.getContractResultActionsByTransactionId(transactionId)) + .map(ContractActionsResponse::getActions) + .filter(actions -> !actions.isEmpty()) + .map(List::getFirst) + .map(ContractAction::getGasUsed) + .orElse(0L); // Provide a default value in case any step results in null + } + + private Long getGasConsumedByTransactionId(String transactionId) { + ContractResult contractResult = mirrorClient.getContractResultByTransactionId(transactionId); + return contractResult.getGasConsumed(); + } } diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java index 11d48c6d77f..34a3cf6e7cc 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java @@ -46,6 +46,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import lombok.CustomLog; import lombok.Getter; @@ -141,7 +142,11 @@ public DeployedContract getContract(ContractResource contractResource) { : Hbar.fromTinybars(contractResource.initialBalance), null); ContractId contractId = verifyCreateContractNetworkResponse(); - return new DeployedContract(fileId, contractId, compiledSolidityArtifact); + return new DeployedContract( + fileId, + contractId, + compiledSolidityArtifact, + Optional.of(networkTransactionResponse.getTransactionIdStringNoCheckSum())); } catch (IOException e) { log.warn("Issue creating contract: {}, ex: {}", contractResource, e); throw new RuntimeException(e); @@ -261,5 +266,8 @@ public interface ContractMethodInterface extends SelectorInterface { } public record DeployedContract( - FileId fileId, ContractId contractId, CompiledSolidityArtifact compiledSolidityArtifact) {} + FileId fileId, + ContractId contractId, + CompiledSolidityArtifact compiledSolidityArtifact, + Optional txId) {} } diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java index 5d15b70a285..17838ce1cc9 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java @@ -61,15 +61,11 @@ import static com.hedera.mirror.test.e2e.acceptance.util.TestUtil.asAddress; import static com.hedera.mirror.test.e2e.acceptance.util.TestUtil.extractTransactionId; import static com.hedera.mirror.test.e2e.acceptance.util.TestUtil.to32BytesString; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import com.hedera.hashgraph.sdk.Hbar; import com.hedera.hashgraph.sdk.PrivateKey; import com.hedera.hashgraph.sdk.TokenId; -import com.hedera.mirror.rest.model.ContractAction; -import com.hedera.mirror.rest.model.ContractActionsResponse; -import com.hedera.mirror.rest.model.ContractResult; import com.hedera.mirror.test.e2e.acceptance.client.AccountClient; import com.hedera.mirror.test.e2e.acceptance.client.AccountClient.AccountNameEnum; import com.hedera.mirror.test.e2e.acceptance.client.ContractClient.ExecuteContractResult; @@ -82,14 +78,11 @@ import io.cucumber.java.en.Then; import java.io.IOException; import java.math.BigInteger; -import java.util.List; import java.util.Objects; import java.util.Optional; import lombok.CustomLog; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.RandomStringUtils; import org.assertj.core.api.Assertions; @@ -681,18 +674,6 @@ public void deployEstimateContractWithLowGas() { verifyGasConsumed(txId, successfulInitByteCode); } - private void verifyGasConsumed(String txId, Object data) { - int totalGasFee; - try { - totalGasFee = calculateIntrinsicValue(data); - } catch (DecoderException e) { - throw new RuntimeException("Failed to decode hexadecimal string.", e); - } - var gasConsumed = getGasConsumedByTransactionId(txId); - var gasUsed = getGasFromActions(txId); - assertThat(gasConsumed).isEqualTo(gasUsed + totalGasFee); - } - private String executeContractTransaction( DeployedContract deployedContract, SelectorInterface contractMethods, byte[] parameters) { @@ -774,56 +755,6 @@ private String createContractAndReturnTransactionId(String resourcePath) { .getMaxContractFunctionGas()); } - private Long getGasConsumedByTransactionId(String transactionId) { - ContractResult contractResult = mirrorClient.getContractResultByTransactionId(transactionId); - return contractResult.getGasConsumed(); - } - - private long getGasFromActions(String transactionId) { - return Optional.ofNullable(mirrorClient.getContractResultActionsByTransactionId(transactionId)) - .map(ContractActionsResponse::getActions) - .filter(actions -> !actions.isEmpty()) - .map(List::getFirst) - .map(ContractAction::getGasUsed) - .orElse(0L); // Provide a default value in case any step results in null - } - - /** - * Calculates the total intrinsic gas required for a given operation, taking into account the - * operation type and the data involved. This method adjusts the gas calculation based - * on the type of operation: contract creation (CREATE) operations, indicated by a hexadecimal - * string input, include an additional fee on top of the base gas fee. The intrinsic gas for - * the data payload is calculated by adding a specific gas amount for each byte in the payload, - * with different amounts for zero and non-zero bytes. - * - * @param data The operation data, which can be a hexadecimal string for CREATE operations or - * a byte array for contract call operations. - * @return The total intrinsic gas calculated for the operation - * @throws DecoderException If the data parameter is a String and cannot be decoded from hexadecimal - * format, indicating an issue with the input format. - * @throws IllegalArgumentException If the data parameter is not an instance of String or byte[], - * indicating that the provided data type is unsupported for gas - * calculation in the context of this method and tests. - */ - private int calculateIntrinsicValue(Object data) throws DecoderException { - int total = BASE_GAS_FEE; - byte[] values; - if (data instanceof String) { - values = Hex.decodeHex(((String) data).replaceFirst("0x", "")); - total += ADDITIONAL_FEE_FOR_CREATE; - } else if (data instanceof byte[]) { - values = (byte[]) data; - } else { - throw new IllegalArgumentException("Unsupported data type for gas calculation."); - } - - // Calculates the intrinsic value by adding 4 for each 0 bytes and 16 for non-zero bytes - for (byte value : values) { - total += (value == 0) ? 4 : 16; - } - return total; - } - /** * Estimate gas values are hardcoded at this moment until we get better solution such as actual gas used returned * from the consensus node. It will be changed in future PR when actualGasUsed field is added to the protobufs. @@ -868,7 +799,12 @@ enum ContractMethods implements ContractMethodInterface { IERC20_TOKEN_TRANSFER("transfer(address,uint256)", 38193), IERC20_TOKEN_APPROVE("approve(address,uint256)", 728550), IERC20_TOKEN_ASSOCIATE("associate()", 728033), - IERC20_TOKEN_DISSOCIATE("dissociate()", 728033); + IERC20_TOKEN_DISSOCIATE("dissociate()", 728033), + CREATE_CHILD("createChild", 0), + GET_BYE_CODE("getBytecode", 0), + GET_ADDRESS("getAddress", 0), + CREATE_2_DEPLOY("create2Deploy", 0), + VACATE_ADDRESS("vacateAddress", 0); private final String selector; private final int actualGas; diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java index c6e21cfc31d..061eff87ad9 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java @@ -18,6 +18,11 @@ import static com.hedera.mirror.rest.model.TransactionTypes.*; import static com.hedera.mirror.rest.model.TransactionTypes.CONTRACTCREATEINSTANCE; +import static com.hedera.mirror.test.e2e.acceptance.steps.AbstractFeature.ContractResource.PARENT_CONTRACT; +import static com.hedera.mirror.test.e2e.acceptance.steps.EstimateFeature.ContractMethods.CREATE_2_DEPLOY; +import static com.hedera.mirror.test.e2e.acceptance.steps.EstimateFeature.ContractMethods.CREATE_CHILD; +import static com.hedera.mirror.test.e2e.acceptance.steps.EstimateFeature.ContractMethods.GET_ADDRESS; +import static com.hedera.mirror.test.e2e.acceptance.steps.EstimateFeature.ContractMethods.GET_BYE_CODE; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -40,12 +45,14 @@ import java.util.Comparator; import java.util.HexFormat; import java.util.List; +import java.util.Objects; +import java.util.Optional; import lombok.CustomLog; import org.springframework.http.HttpStatus; import org.springframework.web.client.HttpClientErrorException; @CustomLog -public class EthCallFeature extends AbstractFeature { +public class EthCallFeature extends AbstractEstimateFeature { private static final int EVM_ADDRESS_SALT = 42; private static final String ACCOUNT_EMPTY_KEYLIST = "3200"; @@ -100,10 +107,17 @@ public void verifyAccountCreated() { assertThat(createAccountTransaction.getConsensusTimestamp()).isEqualTo(accountInfo.getCreatedTimestamp()); } - @Given("I successfully create parent contract by ethereum transaction") + @Given("I successfully create parent contract by ethereum transaction and verify gasConsumed") public void createNewERCtestContract() { - deployedParentContract = ethereumContractCreate(ContractResource.PARENT_CONTRACT); + deployedParentContract = ethereumContractCreate(PARENT_CONTRACT); deployedParentContract.contractId().toSolidityAddress(); + + String txId = deployedParentContract.txId().get(); + var successfulInitByteCode = Objects.requireNonNull(mirrorClient + .getContractInfo(deployedParentContract.contractId().toSolidityAddress()) + .getBytecode()); + + verifyGasConsumed(txId, successfulInitByteCode); } @Then("the mirror node REST API should return status {int} for the eth contract creation transaction") @@ -127,7 +141,7 @@ public void verifyDeployedContractMirror() { verifyContractExecutionResultsByTransactionId(true); } - @Given("I successfully call the child creation function using EIP-1559 ethereum transaction") + @Given("I successfully call the child creation function using EIP-1559 ethereum transaction and verify gasConsumed") public void callContract() { ContractFunctionParameters parameters = new ContractFunctionParameters().addUint256(BigInteger.valueOf(1000)); @@ -137,6 +151,9 @@ public void callContract() { parameters, null, EthTxData.EthTransactionType.EIP1559); + + var selector = encodeDataToByteArray(PARENT_CONTRACT, CREATE_CHILD, BigInteger.valueOf(1000)); + verifyGasConsumed(networkTransactionResponse.getTransactionIdStringNoCheckSum(), selector); } @Then("the mirror node REST API should verify the child creation ethereum transaction") @@ -153,7 +170,8 @@ public void verifyContractFunctionCallMirror() { verifyContractExecutionResultsByTransactionId(false); } - @Given("I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction") + @Given( + "I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction and verify gasConsumed") public void getChildBytecode() { var executeContractResult = executeEthereumTransaction( deployedParentContract.contractId(), @@ -165,10 +183,13 @@ public void getChildBytecode() { childContractBytecodeFromParent = executeContractResult.contractFunctionResult().getBytes(0); assertNotNull(childContractBytecodeFromParent); + + var selector = encodeDataToByteArray(PARENT_CONTRACT, GET_BYE_CODE); + verifyGasConsumed(networkTransactionResponse.getTransactionIdStringNoCheckSum(), selector); } @When( - "I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction") + "I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction and verify gasConsumed") public void getChildAddress() { ContractFunctionParameters parameters = new ContractFunctionParameters() .addBytes(childContractBytecodeFromParent) @@ -185,6 +206,10 @@ public void getChildAddress() { executeContractResult.contractFunctionResult().getAddress(0); create2ChildContractAccountId = AccountId.fromEvmAddress(create2ChildContractEvmAddress); create2ChildContractContractId = ContractId.fromEvmAddress(0, 0, create2ChildContractEvmAddress); + + var selector = encodeDataToByteArray( + PARENT_CONTRACT, GET_ADDRESS, childContractBytecodeFromParent, BigInteger.valueOf(EVM_ADDRESS_SALT)); + verifyGasConsumed(networkTransactionResponse.getTransactionIdStringNoCheckSum(), selector); } @And("I create a hollow account using CryptoTransfer of {int} to the child's evm address") @@ -228,7 +253,8 @@ public void verifyMirrorAPIContractNotFoundResponse() { } } - @When("I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559") + @When( + "I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559 and verify gasConsumed") public void createChildContractUsingCreate2() { ContractFunctionParameters parameters = new ContractFunctionParameters() .addBytes(childContractBytecodeFromParent) @@ -239,6 +265,13 @@ public void createChildContractUsingCreate2() { parameters, null, EthTxData.EthTransactionType.EIP1559); + + var selector = encodeDataToByteArray( + PARENT_CONTRACT, + CREATE_2_DEPLOY, + childContractBytecodeFromParent, + BigInteger.valueOf(EVM_ADDRESS_SALT)); + verifyGasConsumed(networkTransactionResponse.getTransactionIdStringNoCheckSum(), selector); } @And("the mirror node REST API should retrieve the contract when using child's evm address") @@ -323,7 +356,11 @@ public DeployedContract ethereumContractCreate(ContractResource contractResource : Hbar.fromTinybars(contractResource.getInitialBalance()), null); ContractId contractId = verifyCreateContractNetworkResponse(); - return new DeployedContract(fileId, contractId, compiledSolidityArtifact); + return new DeployedContract( + fileId, + contractId, + compiledSolidityArtifact, + Optional.of(networkTransactionResponse.getTransactionIdStringNoCheckSum())); } catch (IOException e) { log.warn("Issue creating contract: {}, ex: {}", contractResource, e); throw new RuntimeException(e); diff --git a/hedera-mirror-test/src/test/resources/features/contract/eth.feature b/hedera-mirror-test/src/test/resources/features/contract/eth.feature index 574e2cb2bde..a8d7ffd99bc 100644 --- a/hedera-mirror-test/src/test/resources/features/contract/eth.feature +++ b/hedera-mirror-test/src/test/resources/features/contract/eth.feature @@ -24,28 +24,28 @@ Feature: Ethereum transactions Coverage Feature Given I successfully created a signer account with an EVM address alias Then validate the signer account and its balance - Given I successfully create parent contract by ethereum transaction + Given I successfully create parent contract by ethereum transaction and verify gasConsumed Then the mirror node REST API should return status for the eth contract creation transaction And the mirror node REST API should verify the deployed contract entity by eth call - When I successfully call the child creation function using EIP-1559 ethereum transaction + When I successfully call the child creation function using EIP-1559 ethereum transaction and verify gasConsumed Then the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the child creation ethereum transaction - Given I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction + Given I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction and verify gasConsumed Then the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the ethereum called contract function - When I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction + When I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction and verify gasConsumed Then the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the ethereum called contract function - + And I create a hollow account using CryptoTransfer of to the child's evm address Then the mirror node REST API should return status for the transfer transaction And the mirror node REST API should verify the account is hollow and has And the mirror node REST API should not find a contract when using child's evm address - When I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559 + When I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559 and verify gasConsumed Then the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should retrieve the contract when using child's evm address And the mirror node REST API should verify that the account is not hollow From 27dd57cc59580af8a9d554b3b0cc84fc39c5ba8c Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Thu, 9 May 2024 16:24:48 +0300 Subject: [PATCH 04/11] refactor: remove txId from DeployedContract Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../test/e2e/acceptance/steps/AbstractFeature.java | 12 ++---------- .../test/e2e/acceptance/steps/EthCallFeature.java | 10 +++------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java index 34a3cf6e7cc..11d48c6d77f 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java @@ -46,7 +46,6 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import lombok.CustomLog; import lombok.Getter; @@ -142,11 +141,7 @@ public DeployedContract getContract(ContractResource contractResource) { : Hbar.fromTinybars(contractResource.initialBalance), null); ContractId contractId = verifyCreateContractNetworkResponse(); - return new DeployedContract( - fileId, - contractId, - compiledSolidityArtifact, - Optional.of(networkTransactionResponse.getTransactionIdStringNoCheckSum())); + return new DeployedContract(fileId, contractId, compiledSolidityArtifact); } catch (IOException e) { log.warn("Issue creating contract: {}, ex: {}", contractResource, e); throw new RuntimeException(e); @@ -266,8 +261,5 @@ public interface ContractMethodInterface extends SelectorInterface { } public record DeployedContract( - FileId fileId, - ContractId contractId, - CompiledSolidityArtifact compiledSolidityArtifact, - Optional txId) {} + FileId fileId, ContractId contractId, CompiledSolidityArtifact compiledSolidityArtifact) {} } diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java index 061eff87ad9..179f546efac 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java @@ -46,7 +46,6 @@ import java.util.HexFormat; import java.util.List; import java.util.Objects; -import java.util.Optional; import lombok.CustomLog; import org.springframework.http.HttpStatus; import org.springframework.web.client.HttpClientErrorException; @@ -112,7 +111,8 @@ public void createNewERCtestContract() { deployedParentContract = ethereumContractCreate(PARENT_CONTRACT); deployedParentContract.contractId().toSolidityAddress(); - String txId = deployedParentContract.txId().get(); + String txId = networkTransactionResponse.getTransactionIdStringNoCheckSum(); + var successfulInitByteCode = Objects.requireNonNull(mirrorClient .getContractInfo(deployedParentContract.contractId().toSolidityAddress()) .getBytecode()); @@ -356,11 +356,7 @@ public DeployedContract ethereumContractCreate(ContractResource contractResource : Hbar.fromTinybars(contractResource.getInitialBalance()), null); ContractId contractId = verifyCreateContractNetworkResponse(); - return new DeployedContract( - fileId, - contractId, - compiledSolidityArtifact, - Optional.of(networkTransactionResponse.getTransactionIdStringNoCheckSum())); + return new DeployedContract(fileId, contractId, compiledSolidityArtifact); } catch (IOException e) { log.warn("Issue creating contract: {}, ex: {}", contractResource, e); throw new RuntimeException(e); From 6381c491a51285c891a57db0ef99df52eb813967 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Thu, 9 May 2024 16:43:38 +0300 Subject: [PATCH 05/11] refactor: separate the gasVerify check into a separate step Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../e2e/acceptance/steps/EthCallFeature.java | 35 +++++++++---------- .../resources/features/contract/eth.feature | 25 +++++++------ 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java index 179f546efac..dde3fa93b97 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java @@ -64,6 +64,7 @@ public class EthCallFeature extends AbstractEstimateFeature { private String create2ChildContractEntityId; private AccountId create2ChildContractAccountId; private ContractId create2ChildContractContractId; + private Object gasConsumedSelector; @Given("I successfully created a signer account with an EVM address alias") public void createAccountWithEvmAddressAlias() { @@ -106,18 +107,20 @@ public void verifyAccountCreated() { assertThat(createAccountTransaction.getConsensusTimestamp()).isEqualTo(accountInfo.getCreatedTimestamp()); } - @Given("I successfully create parent contract by ethereum transaction and verify gasConsumed") + @Given("I successfully create parent contract by ethereum transaction") public void createNewERCtestContract() { deployedParentContract = ethereumContractCreate(PARENT_CONTRACT); deployedParentContract.contractId().toSolidityAddress(); - String txId = networkTransactionResponse.getTransactionIdStringNoCheckSum(); - - var successfulInitByteCode = Objects.requireNonNull(mirrorClient + gasConsumedSelector = Objects.requireNonNull(mirrorClient .getContractInfo(deployedParentContract.contractId().toSolidityAddress()) .getBytecode()); + } - verifyGasConsumed(txId, successfulInitByteCode); + @Then("the gasConsumed is correct") + public void verifyGasConsumedIsCorrect() { + String txId = networkTransactionResponse.getTransactionIdStringNoCheckSum(); + verifyGasConsumed(txId, gasConsumedSelector); } @Then("the mirror node REST API should return status {int} for the eth contract creation transaction") @@ -141,7 +144,7 @@ public void verifyDeployedContractMirror() { verifyContractExecutionResultsByTransactionId(true); } - @Given("I successfully call the child creation function using EIP-1559 ethereum transaction and verify gasConsumed") + @Given("I successfully call the child creation function using EIP-1559 ethereum transaction") public void callContract() { ContractFunctionParameters parameters = new ContractFunctionParameters().addUint256(BigInteger.valueOf(1000)); @@ -152,8 +155,7 @@ public void callContract() { null, EthTxData.EthTransactionType.EIP1559); - var selector = encodeDataToByteArray(PARENT_CONTRACT, CREATE_CHILD, BigInteger.valueOf(1000)); - verifyGasConsumed(networkTransactionResponse.getTransactionIdStringNoCheckSum(), selector); + gasConsumedSelector = encodeDataToByteArray(PARENT_CONTRACT, CREATE_CHILD, BigInteger.valueOf(1000)); } @Then("the mirror node REST API should verify the child creation ethereum transaction") @@ -170,8 +172,7 @@ public void verifyContractFunctionCallMirror() { verifyContractExecutionResultsByTransactionId(false); } - @Given( - "I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction and verify gasConsumed") + @Given("I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction") public void getChildBytecode() { var executeContractResult = executeEthereumTransaction( deployedParentContract.contractId(), @@ -184,12 +185,11 @@ public void getChildBytecode() { executeContractResult.contractFunctionResult().getBytes(0); assertNotNull(childContractBytecodeFromParent); - var selector = encodeDataToByteArray(PARENT_CONTRACT, GET_BYE_CODE); - verifyGasConsumed(networkTransactionResponse.getTransactionIdStringNoCheckSum(), selector); + gasConsumedSelector = encodeDataToByteArray(PARENT_CONTRACT, GET_BYE_CODE); } @When( - "I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction and verify gasConsumed") + "I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction") public void getChildAddress() { ContractFunctionParameters parameters = new ContractFunctionParameters() .addBytes(childContractBytecodeFromParent) @@ -207,9 +207,8 @@ public void getChildAddress() { create2ChildContractAccountId = AccountId.fromEvmAddress(create2ChildContractEvmAddress); create2ChildContractContractId = ContractId.fromEvmAddress(0, 0, create2ChildContractEvmAddress); - var selector = encodeDataToByteArray( + gasConsumedSelector = encodeDataToByteArray( PARENT_CONTRACT, GET_ADDRESS, childContractBytecodeFromParent, BigInteger.valueOf(EVM_ADDRESS_SALT)); - verifyGasConsumed(networkTransactionResponse.getTransactionIdStringNoCheckSum(), selector); } @And("I create a hollow account using CryptoTransfer of {int} to the child's evm address") @@ -253,8 +252,7 @@ public void verifyMirrorAPIContractNotFoundResponse() { } } - @When( - "I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559 and verify gasConsumed") + @When("I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559") public void createChildContractUsingCreate2() { ContractFunctionParameters parameters = new ContractFunctionParameters() .addBytes(childContractBytecodeFromParent) @@ -266,12 +264,11 @@ public void createChildContractUsingCreate2() { null, EthTxData.EthTransactionType.EIP1559); - var selector = encodeDataToByteArray( + gasConsumedSelector = encodeDataToByteArray( PARENT_CONTRACT, CREATE_2_DEPLOY, childContractBytecodeFromParent, BigInteger.valueOf(EVM_ADDRESS_SALT)); - verifyGasConsumed(networkTransactionResponse.getTransactionIdStringNoCheckSum(), selector); } @And("the mirror node REST API should retrieve the contract when using child's evm address") diff --git a/hedera-mirror-test/src/test/resources/features/contract/eth.feature b/hedera-mirror-test/src/test/resources/features/contract/eth.feature index a8d7ffd99bc..97241b3faa5 100644 --- a/hedera-mirror-test/src/test/resources/features/contract/eth.feature +++ b/hedera-mirror-test/src/test/resources/features/contract/eth.feature @@ -24,20 +24,24 @@ Feature: Ethereum transactions Coverage Feature Given I successfully created a signer account with an EVM address alias Then validate the signer account and its balance - Given I successfully create parent contract by ethereum transaction and verify gasConsumed - Then the mirror node REST API should return status for the eth contract creation transaction + Given I successfully create parent contract by ethereum transaction + Then the gasConsumed is correct + And the mirror node REST API should return status for the eth contract creation transaction And the mirror node REST API should verify the deployed contract entity by eth call - When I successfully call the child creation function using EIP-1559 ethereum transaction and verify gasConsumed - Then the mirror node REST API should return status for the ethereum transaction + When I successfully call the child creation function using EIP-1559 ethereum transaction + Then the gasConsumed is correct + And the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the child creation ethereum transaction - Given I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction and verify gasConsumed - Then the mirror node REST API should return status for the ethereum transaction + Given I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction + Then the gasConsumed is correct + And the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the ethereum called contract function - When I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction and verify gasConsumed - Then the mirror node REST API should return status for the ethereum transaction + When I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction + Then the gasConsumed is correct + And the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the ethereum called contract function And I create a hollow account using CryptoTransfer of to the child's evm address @@ -45,8 +49,9 @@ Feature: Ethereum transactions Coverage Feature And the mirror node REST API should verify the account is hollow and has And the mirror node REST API should not find a contract when using child's evm address - When I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559 and verify gasConsumed - Then the mirror node REST API should return status for the ethereum transaction + When I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559 + Then the gasConsumed is correct + And the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should retrieve the contract when using child's evm address And the mirror node REST API should verify that the account is not hollow From 1b96c7031c672af4496796fa09057f80a5c6c1cc Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Fri, 10 May 2024 09:56:31 +0300 Subject: [PATCH 06/11] refactor: move gasConsumedSelector in the abstract class Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../steps/AbstractEstimateFeature.java | 5 +- .../e2e/acceptance/steps/EstimateFeature.java | 153 +++++++++--------- .../e2e/acceptance/steps/EthCallFeature.java | 3 +- 3 files changed, 80 insertions(+), 81 deletions(-) diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java index afdfb595547..c5bf72cf309 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java @@ -41,6 +41,7 @@ abstract class AbstractEstimateFeature extends AbstractFeature { protected int lowerDeviation; protected int upperDeviation; + protected Object gasConsumedSelector; @Autowired protected MirrorNodeClient mirrorClient; @@ -126,10 +127,10 @@ protected void assertEthCallReturnsBadRequest(String block, String data, String .isInstanceOf(HttpClientErrorException.BadRequest.class); } - protected void verifyGasConsumed(String txId, Object data) { + protected void verifyGasConsumed(String txId) { int totalGasFee; try { - totalGasFee = calculateIntrinsicValue(data); + totalGasFee = calculateIntrinsicValue(gasConsumedSelector); } catch (DecoderException e) { throw new RuntimeException("Failed to decode hexadecimal string.", e); } diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java index 17838ce1cc9..f5149dbdebe 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java @@ -474,179 +474,179 @@ public void contractDeployEstimateGasWithInvalidSender() { @Then("I execute contractCall for function that changes the contract slot and verify gasConsumed") public void updateContractSlotGasConsumed() { - var data = encodeDataToByteArray(ESTIMATE_GAS, UPDATE_COUNTER, new BigInteger("5")); - var txId = executeContractTransaction(deployedContract, UPDATE_COUNTER, data); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeDataToByteArray(ESTIMATE_GAS, UPDATE_COUNTER, new BigInteger("5")); + var txId = executeContractTransaction(deployedContract, UPDATE_COUNTER); + verifyGasConsumed(txId); } @Then("I execute contractCall with delegatecall and function that changes contract slot and verify gasConsumed") public void updateContractSlotGasConsumedViaDelegateCall() { var selector = encodeDataToByteArray(ESTIMATE_GAS, INCREMENT_COUNTER); - var data = encodeDataToByteArray( + gasConsumedSelector = encodeDataToByteArray( ESTIMATE_GAS, DELEGATE_CALL_TO_CONTRACT, asAddress(contractSolidityAddress), selector); - var txId = executeContractTransaction(deployedContract, DELEGATE_CALL_TO_CONTRACT, data); - verifyGasConsumed(txId, data); + var txId = executeContractTransaction(deployedContract, DELEGATE_CALL_TO_CONTRACT); + verifyGasConsumed(txId); } @Then( "I execute contractCall with delegatecall with low gas and function that changes contract slot and verify gasConsumed") public void updateContractSlotGasConsumedViaDelegateCallWithLowGas() { var selector = encodeDataToByteArray(ESTIMATE_GAS, INCREMENT_COUNTER); - var data = encodeDataToByteArray( + gasConsumedSelector = encodeDataToByteArray( ESTIMATE_GAS, DELEGATE_CALL_TO_CONTRACT, asAddress(contractSolidityAddress), selector); - var txId = executeContractTransaction(deployedContract, 21500L, DELEGATE_CALL_TO_CONTRACT, data); - verifyGasConsumed(txId, data); + var txId = executeContractTransaction(deployedContract, 21500L, DELEGATE_CALL_TO_CONTRACT); + verifyGasConsumed(txId); } @Then("I execute contractCall with callcode and function that changes contract slot and verify gasConsumed") public void updateContractSlotGasConsumedViaCallCode() { var selector = encodeDataToByteArray(ESTIMATE_GAS, INCREMENT_COUNTER); - var data = encodeDataToByteArray( + gasConsumedSelector = encodeDataToByteArray( ESTIMATE_GAS, CALL_CODE_TO_CONTRACT, asAddress(contractSolidityAddress), selector); - var txId = executeContractTransaction(deployedContract, DELEGATE_CALL_TO_CONTRACT, data); - verifyGasConsumed(txId, data); + var txId = executeContractTransaction(deployedContract, DELEGATE_CALL_TO_CONTRACT); + verifyGasConsumed(txId); } @Then( "I execute contractCall with callcode with low gas and function that changes contract slot and verify gasConsumed") public void updateContractSlotGasConsumedViaCallCodeWithLowGas() { var selector = encodeDataToByteArray(ESTIMATE_GAS, INCREMENT_COUNTER); - var data = encodeDataToByteArray( + gasConsumedSelector = encodeDataToByteArray( ESTIMATE_GAS, CALL_CODE_TO_CONTRACT, asAddress(contractSolidityAddress), selector); - var txId = executeContractTransaction(deployedContract, 21500L, DELEGATE_CALL_TO_CONTRACT, data); - verifyGasConsumed(txId, data); + var txId = executeContractTransaction(deployedContract, 21500L, DELEGATE_CALL_TO_CONTRACT); + verifyGasConsumed(txId); } @Then("I execute contractCall with static call and verify gasConsumed") public void getAddressViaStaticCall() { var selector = encodeDataToByteArray(ESTIMATE_GAS, GET_MOCK_ADDRESS); - var data = encodeDataToByteArray( + gasConsumedSelector = encodeDataToByteArray( ESTIMATE_GAS, STATIC_CALL_TO_CONTRACT, asAddress(contractSolidityAddress), selector); - var txId = executeContractTransaction(deployedContract, STATIC_CALL_TO_CONTRACT, data); - verifyGasConsumed(txId, data); + var txId = executeContractTransaction(deployedContract, STATIC_CALL_TO_CONTRACT); + verifyGasConsumed(txId); } @Then("I execute contractCall with static call with low gas and verify gasConsumed") public void getAddressViaStaticCallWithLowGas() { var selector = encodeDataToByteArray(ESTIMATE_GAS, GET_MOCK_ADDRESS); - var data = encodeDataToByteArray( + gasConsumedSelector = encodeDataToByteArray( ESTIMATE_GAS, STATIC_CALL_TO_CONTRACT, asAddress(contractSolidityAddress), selector); - var txId = executeContractTransaction(deployedContract, 21500L, STATIC_CALL_TO_CONTRACT, data); - verifyGasConsumed(txId, data); + var txId = executeContractTransaction(deployedContract, 21500L, STATIC_CALL_TO_CONTRACT); + verifyGasConsumed(txId); } @Then("I trigger fallback function with transfer and verify gasConsumed") public void triggerFallbackAndTransferFundsToContract() { - var data = encodeData(WRONG_METHOD_SIGNATURE).getBytes(); - var txId = executeContractTransaction(deployedContract, WRONG_METHOD_SIGNATURE, data, Hbar.fromTinybars(100)); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeData(WRONG_METHOD_SIGNATURE).getBytes(); + var txId = executeContractTransaction(deployedContract, WRONG_METHOD_SIGNATURE, Hbar.fromTinybars(100)); + verifyGasConsumed(txId); } @Then("I trigger fallback function with send and verify gasConsumed") public void triggerFallbackAndSendFundsToContract() { - var initialData = encodeDataToByteArray(ESTIMATE_GAS, UPDATE_TYPE, new BigInteger("2")); - executeContractTransaction(deployedContract, UPDATE_TYPE, initialData, null); + gasConsumedSelector = encodeDataToByteArray(ESTIMATE_GAS, UPDATE_TYPE, new BigInteger("2")); + executeContractTransaction(deployedContract, UPDATE_TYPE, null); - var data = encodeData(WRONG_METHOD_SIGNATURE).getBytes(); - var txId = executeContractTransaction(deployedContract, WRONG_METHOD_SIGNATURE, data, Hbar.fromTinybars(100)); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeData(WRONG_METHOD_SIGNATURE).getBytes(); + var txId = executeContractTransaction(deployedContract, WRONG_METHOD_SIGNATURE, Hbar.fromTinybars(100)); + verifyGasConsumed(txId); } @Then("I trigger fallback function with call and verify gasConsumed") public void triggerFallWithCallToContract() { - var initialData = encodeDataToByteArray(ESTIMATE_GAS, UPDATE_TYPE, new BigInteger("3")); - executeContractTransaction(deployedContract, UPDATE_TYPE, initialData, null); + gasConsumedSelector = encodeDataToByteArray(ESTIMATE_GAS, UPDATE_TYPE, new BigInteger("3")); + executeContractTransaction(deployedContract, UPDATE_TYPE, null); - var data = encodeData(WRONG_METHOD_SIGNATURE).getBytes(); - var txId = executeContractTransaction(deployedContract, WRONG_METHOD_SIGNATURE, data, Hbar.fromTinybars(100)); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeData(WRONG_METHOD_SIGNATURE).getBytes(); + var txId = executeContractTransaction(deployedContract, WRONG_METHOD_SIGNATURE, Hbar.fromTinybars(100)); + verifyGasConsumed(txId); } @Then("I execute contractCall for nested function and verify gasConsumed") public void nestedCalls() { - var data = encodeDataToByteArray( + gasConsumedSelector = encodeDataToByteArray( ESTIMATE_GAS, CALL_CODE_TO_EXTERNAL_CONTRACT_FUNCTION, new BigInteger("2"), asAddress(contractSolidityAddress)); - var txId = executeContractTransaction(deployedContract, CALL_CODE_TO_EXTERNAL_CONTRACT_FUNCTION, data); - verifyGasConsumed(txId, data); + var txId = executeContractTransaction(deployedContract, CALL_CODE_TO_EXTERNAL_CONTRACT_FUNCTION); + verifyGasConsumed(txId); } @Then("I execute contractCall for nested functions with lower gas limit and verify gasConsumed") public void manyNestedLowerGasLimitCalls() { - var data = encodeDataToByteArray( + gasConsumedSelector = encodeDataToByteArray( ESTIMATE_GAS, CALL_CODE_TO_EXTERNAL_CONTRACT_FUNCTION, new BigInteger("21"), asAddress(contractSolidityAddress)); - var txId = executeContractTransaction(deployedContract, 50000L, CALL_CODE_TO_EXTERNAL_CONTRACT_FUNCTION, data); - verifyGasConsumed(txId, data); + var txId = executeContractTransaction(deployedContract, 50000L, CALL_CODE_TO_EXTERNAL_CONTRACT_FUNCTION); + verifyGasConsumed(txId); } @Then("I execute contractCall for failing nested functions and verify gasConsumed") public void failingNestedFunction() { - var data = encodeDataToByteArray(ESTIMATE_GAS, CALL_TO_INVALID_CONTRACT, asAddress(mockAddress)); - var txId = executeContractTransaction(deployedContract, CALL_TO_INVALID_CONTRACT, data); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeDataToByteArray(ESTIMATE_GAS, CALL_TO_INVALID_CONTRACT, asAddress(mockAddress)); + var txId = executeContractTransaction(deployedContract, CALL_TO_INVALID_CONTRACT); + verifyGasConsumed(txId); } @Then("I execute contractCall for failing precompile function and verify gasConsumed") public void failingPrecompileFunction() { - var data = encodeDataToByteArray(PRECOMPILE, IS_TOKEN_SELECTOR, asAddress(fungibleTokenId)); - var txId = executeContractTransaction(deployedPrecompileContract, 25400L, IS_TOKEN_SELECTOR, data); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeDataToByteArray(PRECOMPILE, IS_TOKEN_SELECTOR, asAddress(fungibleTokenId)); + var txId = executeContractTransaction(deployedPrecompileContract, 25400L, IS_TOKEN_SELECTOR); + verifyGasConsumed(txId); } @Then("I execute contractCall for contract deploy function via create and verify gasConsumed") public void deployContractViaCreate() { - var data = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_CONTRACT_VIA_CREATE_OPCODE); - var txId = executeContractTransaction(deployedContract, DEPLOY_CONTRACT_VIA_CREATE_OPCODE, data); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_CONTRACT_VIA_CREATE_OPCODE); + var txId = executeContractTransaction(deployedContract, DEPLOY_CONTRACT_VIA_CREATE_OPCODE); + verifyGasConsumed(txId); } @Then("I execute contractCall for contract deploy function via create2 and verify gasConsumed") public void deployContractViaCreateTwo() { - var data = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_CONTRACT_VIA_CREATE_2_OPCODE); - var txId = executeContractTransaction(deployedContract, DEPLOY_CONTRACT_VIA_CREATE_2_OPCODE, data); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_CONTRACT_VIA_CREATE_2_OPCODE); + var txId = executeContractTransaction(deployedContract, DEPLOY_CONTRACT_VIA_CREATE_2_OPCODE); + verifyGasConsumed(txId); } @Then("I execute contractCall failing to deploy contract due to low gas and verify gasConsumed") public void failDeployComplexContract() { - var data = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_NEW_INSTANCE); - var txId = executeContractTransaction(deployedContract, 40000L, DEPLOY_NEW_INSTANCE, data); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_NEW_INSTANCE); + var txId = executeContractTransaction(deployedContract, 40000L, DEPLOY_NEW_INSTANCE); + verifyGasConsumed(txId); } @Then("I execute deploy and call contract and verify gasConsumed") public void deployAndCallContract() { - var data = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_AND_CALL_CONTRACT, new BigInteger("5")); - var txId = executeContractTransaction(deployedContract, DEPLOY_AND_CALL_CONTRACT, data); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_AND_CALL_CONTRACT, new BigInteger("5")); + var txId = executeContractTransaction(deployedContract, DEPLOY_AND_CALL_CONTRACT); + verifyGasConsumed(txId); } @Then("I execute deploy and call contract that fails and verify gasConsumed") public void deployAndCallFailContract() { - var data = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_AND_CALL_CONTRACT, new BigInteger("11")); - var txId = executeContractTransaction(deployedContract, DEPLOY_AND_CALL_CONTRACT, data); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_AND_CALL_CONTRACT, new BigInteger("11")); + var txId = executeContractTransaction(deployedContract, DEPLOY_AND_CALL_CONTRACT); + verifyGasConsumed(txId); } @Then("I execute deploy and selfdestruct and verify gasConsumed") public void deployAndSelfDestructContract() { - var data = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_AND_DESTROY); - var txId = executeContractTransaction(deployedContract, DEPLOY_AND_DESTROY, data); - verifyGasConsumed(txId, data); + gasConsumedSelector = encodeDataToByteArray(ESTIMATE_GAS, DEPLOY_AND_DESTROY); + var txId = executeContractTransaction(deployedContract, DEPLOY_AND_DESTROY); + verifyGasConsumed(txId); } @Then("I execute create operation with bad contract and verify gasConsumed") public void deployBadContract() { var contractPath = "classpath:solidity/artifacts/contracts/EstimateGasContract.sol/DummyContract.json"; var txId = createContractAndReturnTransactionId(contractPath); - var initByteCode = Objects.requireNonNull( + gasConsumedSelector = Objects.requireNonNull( mirrorClient.getContractResultByTransactionId(txId).getFailedInitcode()); - verifyGasConsumed(txId, initByteCode); + verifyGasConsumed(txId); } @Then("I execute create operation with complex contract and verify gasConsumed") @@ -657,9 +657,9 @@ public void deployEstimateContract() { mirrorClient.getTransactions(txId).getTransactions()) .getFirst() .getEntityId(); - var successfulInitByteCode = + gasConsumedSelector = Objects.requireNonNull(mirrorClient.getContractInfo(contractId).getBytecode()); - verifyGasConsumed(txId, successfulInitByteCode); + verifyGasConsumed(txId); } @Then("I execute create operation with complex contract and lower gas limit and verify gasConsumed") @@ -669,13 +669,12 @@ public void deployEstimateContractWithLowGas() { var transactions = Objects.requireNonNull(mirrorClient.getTransactions(txId).getTransactions()); var contractId = transactions.getFirst().getEntityId(); - var successfulInitByteCode = + gasConsumedSelector = Objects.requireNonNull(mirrorClient.getContractInfo(contractId).getBytecode()); - verifyGasConsumed(txId, successfulInitByteCode); + verifyGasConsumed(txId); } - private String executeContractTransaction( - DeployedContract deployedContract, SelectorInterface contractMethods, byte[] parameters) { + private String executeContractTransaction(DeployedContract deployedContract, SelectorInterface contractMethods) { return executeContractTransaction( deployedContract, @@ -685,12 +684,12 @@ private String executeContractTransaction( .getFeatureProperties() .getMaxContractFunctionGas(), contractMethods, - parameters, + (byte[]) gasConsumedSelector, null); } private String executeContractTransaction( - DeployedContract deployedContract, SelectorInterface contractMethods, byte[] parameters, Hbar amount) { + DeployedContract deployedContract, SelectorInterface contractMethods, Hbar amount) { return executeContractTransaction( deployedContract, @@ -700,14 +699,14 @@ private String executeContractTransaction( .getFeatureProperties() .getMaxContractFunctionGas(), contractMethods, - parameters, + (byte[]) gasConsumedSelector, amount); } private String executeContractTransaction( - DeployedContract deployedContract, Long gas, SelectorInterface contractMethods, byte[] parameters) { + DeployedContract deployedContract, Long gas, SelectorInterface contractMethods) { - return executeContractTransaction(deployedContract, gas, contractMethods, parameters, null); + return executeContractTransaction(deployedContract, gas, contractMethods, (byte[]) gasConsumedSelector, null); } private String executeContractTransaction( diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java index dde3fa93b97..66f4faccbae 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java @@ -64,7 +64,6 @@ public class EthCallFeature extends AbstractEstimateFeature { private String create2ChildContractEntityId; private AccountId create2ChildContractAccountId; private ContractId create2ChildContractContractId; - private Object gasConsumedSelector; @Given("I successfully created a signer account with an EVM address alias") public void createAccountWithEvmAddressAlias() { @@ -120,7 +119,7 @@ public void createNewERCtestContract() { @Then("the gasConsumed is correct") public void verifyGasConsumedIsCorrect() { String txId = networkTransactionResponse.getTransactionIdStringNoCheckSum(); - verifyGasConsumed(txId, gasConsumedSelector); + verifyGasConsumed(txId); } @Then("the mirror node REST API should return status {int} for the eth contract creation transaction") From 87cc09051c3f65a03e88469df0e138b2ac0a6836 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Fri, 10 May 2024 10:09:01 +0300 Subject: [PATCH 07/11] refactor: rename the verify gasConsumed step Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../test/e2e/acceptance/steps/EthCallFeature.java | 2 +- .../src/test/resources/features/contract/eth.feature | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java index 66f4faccbae..1e0dcecee36 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java @@ -116,7 +116,7 @@ public void createNewERCtestContract() { .getBytecode()); } - @Then("the gasConsumed is correct") + @Then("the mirror node contract results API should return an accurate gas consumed") public void verifyGasConsumedIsCorrect() { String txId = networkTransactionResponse.getTransactionIdStringNoCheckSum(); verifyGasConsumed(txId); diff --git a/hedera-mirror-test/src/test/resources/features/contract/eth.feature b/hedera-mirror-test/src/test/resources/features/contract/eth.feature index 97241b3faa5..a2d87c1e168 100644 --- a/hedera-mirror-test/src/test/resources/features/contract/eth.feature +++ b/hedera-mirror-test/src/test/resources/features/contract/eth.feature @@ -25,22 +25,22 @@ Feature: Ethereum transactions Coverage Feature Then validate the signer account and its balance Given I successfully create parent contract by ethereum transaction - Then the gasConsumed is correct + Then the mirror node contract results API should return an accurate gas consumed And the mirror node REST API should return status for the eth contract creation transaction And the mirror node REST API should verify the deployed contract entity by eth call When I successfully call the child creation function using EIP-1559 ethereum transaction - Then the gasConsumed is correct + Then the mirror node contract results API should return an accurate gas consumed And the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the child creation ethereum transaction Given I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction - Then the gasConsumed is correct + Then the mirror node contract results API should return an accurate gas consumed And the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the ethereum called contract function When I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction - Then the gasConsumed is correct + Then the mirror node contract results API should return an accurate gas consumed And the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the ethereum called contract function @@ -50,7 +50,7 @@ Feature: Ethereum transactions Coverage Feature And the mirror node REST API should not find a contract when using child's evm address When I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559 - Then the gasConsumed is correct + Then the mirror node contract results API should return an accurate gas consumed And the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should retrieve the contract when using child's evm address And the mirror node REST API should verify that the account is not hollow From 5876ed17f9551ca4bda0ee1acd6ff4fd36e68b14 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Thu, 30 May 2024 09:57:46 +0300 Subject: [PATCH 08/11] fix: merge conflicts Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../acceptance/steps/BaseContractFeature.java | 2 +- .../e2e/acceptance/steps/EstimateFeature.java | 2 +- .../e2e/acceptance/steps/EthCallFeature.java | 503 ------------------ .../e2e/acceptance/steps/EthereumFeature.java | 20 +- .../resources/features/contract/eth.feature | 67 --- .../features/contract/ethereum.feature | 9 +- 6 files changed, 27 insertions(+), 576 deletions(-) delete mode 100644 hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java delete mode 100644 hedera-mirror-test/src/test/resources/features/contract/eth.feature diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/BaseContractFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/BaseContractFeature.java index 3c113e4511f..7f5457fd24d 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/BaseContractFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/BaseContractFeature.java @@ -23,7 +23,7 @@ import com.hedera.mirror.test.e2e.acceptance.util.FeatureInputHandler; import java.util.List; -public abstract class BaseContractFeature extends AbstractFeature { +public abstract class BaseContractFeature extends AbstractEstimateFeature { protected DeployedContract deployedParentContract; protected ContractResponse verifyContractFromMirror(boolean isDeleted) { diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java index f5149dbdebe..9364adb93ec 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java @@ -800,7 +800,7 @@ enum ContractMethods implements ContractMethodInterface { IERC20_TOKEN_ASSOCIATE("associate()", 728033), IERC20_TOKEN_DISSOCIATE("dissociate()", 728033), CREATE_CHILD("createChild", 0), - GET_BYE_CODE("getBytecode", 0), + GET_BYTE_CODE("getBytecode", 0), GET_ADDRESS("getAddress", 0), CREATE_2_DEPLOY("create2Deploy", 0), VACATE_ADDRESS("vacateAddress", 0); diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java deleted file mode 100644 index 1e0dcecee36..00000000000 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthCallFeature.java +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * 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 com.hedera.mirror.test.e2e.acceptance.steps; - -import static com.hedera.mirror.rest.model.TransactionTypes.*; -import static com.hedera.mirror.rest.model.TransactionTypes.CONTRACTCREATEINSTANCE; -import static com.hedera.mirror.test.e2e.acceptance.steps.AbstractFeature.ContractResource.PARENT_CONTRACT; -import static com.hedera.mirror.test.e2e.acceptance.steps.EstimateFeature.ContractMethods.CREATE_2_DEPLOY; -import static com.hedera.mirror.test.e2e.acceptance.steps.EstimateFeature.ContractMethods.CREATE_CHILD; -import static com.hedera.mirror.test.e2e.acceptance.steps.EstimateFeature.ContractMethods.GET_ADDRESS; -import static com.hedera.mirror.test.e2e.acceptance.steps.EstimateFeature.ContractMethods.GET_BYE_CODE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -import com.hedera.hashgraph.sdk.*; -import com.hedera.mirror.rest.model.ContractResponse; -import com.hedera.mirror.rest.model.ContractResult; -import com.hedera.mirror.rest.model.TransactionByIdResponse; -import com.hedera.mirror.rest.model.TransactionDetail; -import com.hedera.mirror.test.e2e.acceptance.client.EthereumClient; -import com.hedera.mirror.test.e2e.acceptance.client.MirrorNodeClient; -import com.hedera.mirror.test.e2e.acceptance.props.CompiledSolidityArtifact; -import com.hedera.mirror.test.e2e.acceptance.util.FeatureInputHandler; -import com.hedera.mirror.test.e2e.acceptance.util.ethereum.EthTxData; -import io.cucumber.java.en.And; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; -import java.io.IOException; -import java.math.BigInteger; -import java.util.Comparator; -import java.util.HexFormat; -import java.util.List; -import java.util.Objects; -import lombok.CustomLog; -import org.springframework.http.HttpStatus; -import org.springframework.web.client.HttpClientErrorException; - -@CustomLog -public class EthCallFeature extends AbstractEstimateFeature { - - private static final int EVM_ADDRESS_SALT = 42; - private static final String ACCOUNT_EMPTY_KEYLIST = "3200"; - protected AccountId ethereum_signer_account; - protected PrivateKey ethereum_signer_private_key; - private String account; - private DeployedContract deployedParentContract; - private byte[] childContractBytecodeFromParent; - private String create2ChildContractEvmAddress; - private String create2ChildContractEntityId; - private AccountId create2ChildContractAccountId; - private ContractId create2ChildContractContractId; - - @Given("I successfully created a signer account with an EVM address alias") - public void createAccountWithEvmAddressAlias() { - ethereum_signer_private_key = PrivateKey.generateECDSA(); - ethereum_signer_account = ethereum_signer_private_key.getPublicKey().toAccountId(0, 0); - - networkTransactionResponse = accountClient.sendCryptoTransfer(ethereum_signer_account, Hbar.from(500L), null); - - assertNotNull(networkTransactionResponse.getTransactionId()); - assertNotNull(networkTransactionResponse.getReceipt()); - } - - @Then("validate the signer account and its balance") - public void verifyAccountCreated() { - var accountInfo = mirrorClient.getAccountDetailsUsingAlias(ethereum_signer_account); - account = accountInfo.getAccount(); - var transactions = mirrorClient - .getTransactions(networkTransactionResponse.getTransactionIdStringNoCheckSum()) - .getTransactions() - .stream() - .sorted(Comparator.comparing(TransactionDetail::getConsensusTimestamp)) - .toList(); - - assertThat(accountInfo.getAccount()).isNotNull(); - assertThat(accountInfo.getBalance().getBalance()) - .isEqualTo(Hbar.from(500L).toTinybars()); - - assertThat(accountInfo.getTransactions()).hasSize(1); - assertThat(transactions).hasSize(2); - - var createAccountTransaction = transactions.get(0); - var transferTransaction = transactions.get(1); - - assertThat(transferTransaction) - .usingRecursiveComparison() - .ignoringFields("assessedCustomFees") - .isEqualTo(accountInfo.getTransactions().get(0)); - - assertThat(createAccountTransaction.getName()).isEqualTo(CRYPTOCREATEACCOUNT); - assertThat(createAccountTransaction.getConsensusTimestamp()).isEqualTo(accountInfo.getCreatedTimestamp()); - } - - @Given("I successfully create parent contract by ethereum transaction") - public void createNewERCtestContract() { - deployedParentContract = ethereumContractCreate(PARENT_CONTRACT); - deployedParentContract.contractId().toSolidityAddress(); - - gasConsumedSelector = Objects.requireNonNull(mirrorClient - .getContractInfo(deployedParentContract.contractId().toSolidityAddress()) - .getBytecode()); - } - - @Then("the mirror node contract results API should return an accurate gas consumed") - public void verifyGasConsumedIsCorrect() { - String txId = networkTransactionResponse.getTransactionIdStringNoCheckSum(); - verifyGasConsumed(txId); - } - - @Then("the mirror node REST API should return status {int} for the eth contract creation transaction") - public void verifyMirrorAPIContractCreationResponses(int status) { - var mirrorTransaction = verifyEthereumContractCreate(mirrorClient, status, true); - assertThat(mirrorTransaction.getEntityId()) - .isEqualTo(deployedParentContract.contractId().toString()); - } - - @Then("the mirror node REST API should return status {int} for the ethereum transaction") - public void verifyMirrorAPIContractResponses(int status) { - var mirrorTransaction = verifyMirrorTransactionsResponse(mirrorClient, status); - assertThat(mirrorTransaction.getEntityId()) - .isEqualTo(deployedParentContract.contractId().toString()); - } - - @Then("the mirror node REST API should verify the deployed contract entity by eth call") - public void verifyDeployedContractMirror() { - verifyContractFromMirror(false); - verifyContractExecutionResultsById(true); - verifyContractExecutionResultsByTransactionId(true); - } - - @Given("I successfully call the child creation function using EIP-1559 ethereum transaction") - public void callContract() { - ContractFunctionParameters parameters = new ContractFunctionParameters().addUint256(BigInteger.valueOf(1000)); - - executeEthereumTransaction( - deployedParentContract.contractId(), - "createChild", - parameters, - null, - EthTxData.EthTransactionType.EIP1559); - - gasConsumedSelector = encodeDataToByteArray(PARENT_CONTRACT, CREATE_CHILD, BigInteger.valueOf(1000)); - } - - @Then("the mirror node REST API should verify the child creation ethereum transaction") - public void verifyChildCreationEthereumCall() { - verifyContractFromMirror(false); - verifyContractExecutionResultsById(true); - verifyContractExecutionResultsByTransactionId(true); - } - - @Then("the mirror node REST API should verify the ethereum called contract function") - public void verifyContractFunctionCallMirror() { - verifyContractFromMirror(false); - verifyContractExecutionResultsById(false); - verifyContractExecutionResultsByTransactionId(false); - } - - @Given("I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction") - public void getChildBytecode() { - var executeContractResult = executeEthereumTransaction( - deployedParentContract.contractId(), - "getBytecode", - null, - null, - EthTxData.EthTransactionType.LEGACY_ETHEREUM); - - childContractBytecodeFromParent = - executeContractResult.contractFunctionResult().getBytes(0); - assertNotNull(childContractBytecodeFromParent); - - gasConsumedSelector = encodeDataToByteArray(PARENT_CONTRACT, GET_BYE_CODE); - } - - @When( - "I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction") - public void getChildAddress() { - ContractFunctionParameters parameters = new ContractFunctionParameters() - .addBytes(childContractBytecodeFromParent) - .addUint256(BigInteger.valueOf(EVM_ADDRESS_SALT)); - - var executeContractResult = executeEthereumTransaction( - deployedParentContract.contractId(), - "getAddress", - parameters, - null, - EthTxData.EthTransactionType.EIP2930); - - create2ChildContractEvmAddress = - executeContractResult.contractFunctionResult().getAddress(0); - create2ChildContractAccountId = AccountId.fromEvmAddress(create2ChildContractEvmAddress); - create2ChildContractContractId = ContractId.fromEvmAddress(0, 0, create2ChildContractEvmAddress); - - gasConsumedSelector = encodeDataToByteArray( - PARENT_CONTRACT, GET_ADDRESS, childContractBytecodeFromParent, BigInteger.valueOf(EVM_ADDRESS_SALT)); - } - - @And("I create a hollow account using CryptoTransfer of {int} to the child's evm address") - public void createHollowAccountWithCryptoTransfertoEvmAddress(int amount) { - networkTransactionResponse = - accountClient.sendCryptoTransfer(create2ChildContractAccountId, Hbar.fromTinybars(amount), null); - - assertNotNull(networkTransactionResponse.getTransactionId()); - assertNotNull(networkTransactionResponse.getReceipt()); - } - - @And("the mirror node REST API should verify the account is hollow and has {int}") - public void verifyMirrorAPIHollowAccountResponse(int amount) { - var mirrorAccountResponse = mirrorClient.getAccountDetailsUsingEvmAddress(create2ChildContractAccountId); - create2ChildContractEntityId = mirrorAccountResponse.getAccount(); - - var transactions = mirrorClient - .getTransactions(networkTransactionResponse.getTransactionIdStringNoCheckSum()) - .getTransactions() - .stream() - .sorted(Comparator.comparing(TransactionDetail::getConsensusTimestamp)) - .toList(); - - assertEquals(2, transactions.size()); - assertEquals(CRYPTOCREATEACCOUNT, transactions.get(0).getName()); - assertEquals(CRYPTOTRANSFER, transactions.get(1).getName()); - - assertNotNull(mirrorAccountResponse.getAccount()); - assertEquals(amount, mirrorAccountResponse.getBalance().getBalance()); - // Hollow account indicated by not having a public key defined. - assertEquals(ACCOUNT_EMPTY_KEYLIST, mirrorAccountResponse.getKey().getKey()); - } - - @And("the mirror node REST API should not find a contract when using child's evm address") - public void verifyMirrorAPIContractNotFoundResponse() { - try { - mirrorClient.getContractInfo(create2ChildContractEvmAddress); - fail("Did not expect to find contract at EVM address"); - } catch (HttpClientErrorException e) { - assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode()); - } - } - - @When("I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559") - public void createChildContractUsingCreate2() { - ContractFunctionParameters parameters = new ContractFunctionParameters() - .addBytes(childContractBytecodeFromParent) - .addUint256(BigInteger.valueOf(EVM_ADDRESS_SALT)); - executeEthereumTransaction( - deployedParentContract.contractId(), - "create2Deploy", - parameters, - null, - EthTxData.EthTransactionType.EIP1559); - - gasConsumedSelector = encodeDataToByteArray( - PARENT_CONTRACT, - CREATE_2_DEPLOY, - childContractBytecodeFromParent, - BigInteger.valueOf(EVM_ADDRESS_SALT)); - } - - @And("the mirror node REST API should retrieve the contract when using child's evm address") - public void verifyMirrorAPIContractFoundResponse() { - var mirrorContractResponse = mirrorClient.getContractInfo(create2ChildContractEvmAddress); - var transactions = mirrorClient - .getTransactions(networkTransactionResponse.getTransactionIdStringNoCheckSum()) - .getTransactions() - .stream() - .sorted(Comparator.comparing(TransactionDetail::getConsensusTimestamp)) - .toList(); - - assertNotNull(transactions); - assertEquals(2, transactions.size()); - assertEquals( - deployedParentContract.contractId().toString(), - transactions.get(0).getEntityId()); - assertEquals(ETHEREUMTRANSACTION, transactions.get(0).getName()); - assertEquals(create2ChildContractEntityId, transactions.get(1).getEntityId()); - assertEquals(CONTRACTCREATEINSTANCE, transactions.get(1).getName()); - - String childContractBytecodeFromParentHex = HexFormat.of().formatHex(childContractBytecodeFromParent); - assertEquals( - childContractBytecodeFromParentHex, - mirrorContractResponse.getBytecode().replaceFirst("0x", "")); - assertEquals( - create2ChildContractEvmAddress, - mirrorContractResponse.getEvmAddress().replaceFirst("0x", "")); - } - - @And("the mirror node REST API should verify that the account is not hollow") - public void verifyMirrorAPIFullAccountResponse() { - var mirrorAccountResponse = mirrorClient.getAccountDetailsUsingEvmAddress(create2ChildContractAccountId); - assertNotNull(mirrorAccountResponse.getAccount()); - assertNotEquals(ACCOUNT_EMPTY_KEYLIST, mirrorAccountResponse.getKey().getKey()); - } - - @When( - "I successfully delete the child contract by calling vacateAddress function using EIP-1559 ethereum transaction") - public void deleteChildContractUsingSelfDestruct() { - executeEthereumTransaction( - create2ChildContractContractId, "vacateAddress", null, null, EthTxData.EthTransactionType.EIP1559); - } - - @Then("the mirror node REST API should return status {int} for the vacateAddress call transaction") - public void verifyMirrorAPIContractChildSelfDestructResponses(int status) { - var mirrorTransaction = verifyMirrorTransactionsResponse(mirrorClient, status); - assertThat(mirrorTransaction.getEntityId()).isEqualTo(create2ChildContractEntityId); - } - - @Given("I successfully delete the parent bytecode file") - public void deleteParentContractFile() { - networkTransactionResponse = fileClient.deleteFile(deployedParentContract.fileId()); - - assertNotNull(networkTransactionResponse.getTransactionId()); - assertNotNull(networkTransactionResponse.getReceipt()); - } - - @And("the mirror node REST API should return status {int} for the transfer transaction") - public void verifyMirrorAPIAccountResponses(int status) { - verifyMirrorTransactionsResponse(mirrorClient, status); - } - - public DeployedContract ethereumContractCreate(ContractResource contractResource) { - var resource = resourceLoader.getResource(contractResource.getPath()); - try (var in = resource.getInputStream()) { - CompiledSolidityArtifact compiledSolidityArtifact = readCompiledArtifact(in); - var fileContent = compiledSolidityArtifact.getBytecode().replaceFirst("0x", ""); - var fileId = persistContractBytes(fileContent); - - networkTransactionResponse = ethereumClient.createContract( - ethereum_signer_private_key, - fileId, - fileContent, - ethereumClient - .getSdkClient() - .getAcceptanceTestProperties() - .getFeatureProperties() - .getMaxContractFunctionGas(), - contractResource.getInitialBalance() == 0 - ? null - : Hbar.fromTinybars(contractResource.getInitialBalance()), - null); - ContractId contractId = verifyCreateContractNetworkResponse(); - return new DeployedContract(fileId, contractId, compiledSolidityArtifact); - } catch (IOException e) { - log.warn("Issue creating contract: {}, ex: {}", contractResource, e); - throw new RuntimeException(e); - } - } - - private EthereumClient.ExecuteContractResult executeEthereumTransaction( - ContractId contractId, - String functionName, - ContractFunctionParameters parameters, - Hbar payableAmount, - EthTxData.EthTransactionType type) { - - EthereumClient.ExecuteContractResult executeContractResult = ethereumClient.executeContract( - ethereum_signer_private_key, - contractId, - contractClient - .getSdkClient() - .getAcceptanceTestProperties() - .getFeatureProperties() - .getMaxContractFunctionGas(), - functionName, - parameters, - payableAmount, - type); - - networkTransactionResponse = executeContractResult.networkTransactionResponse(); - assertNotNull(networkTransactionResponse.getTransactionId()); - assertNotNull(networkTransactionResponse.getReceipt()); - assertNotNull(executeContractResult.contractFunctionResult()); - - return executeContractResult; - } - - private ContractResponse verifyContractFromMirror(boolean isDeleted) { - var mirrorContract = - mirrorClient.getContractInfo(deployedParentContract.contractId().toString()); - - assertNotNull(mirrorContract); - assertThat(mirrorContract.getAutoRenewPeriod()).isNotNull(); - assertThat(mirrorContract.getBytecode()).isNotBlank(); - assertThat(mirrorContract.getContractId()) - .isEqualTo(deployedParentContract.contractId().toString()); - assertThat(mirrorContract.getCreatedTimestamp()).isNotBlank(); - assertThat(mirrorContract.getDeleted()).isEqualTo(isDeleted); - assertThat(mirrorContract.getFileId()) - .isEqualTo(deployedParentContract.fileId().toString()); - assertThat(mirrorContract.getMemo()).isNotBlank(); - String address = mirrorContract.getEvmAddress(); - assertThat(address).isNotBlank().isNotEqualTo("0x").isNotEqualTo("0x0000000000000000000000000000000000000000"); - assertThat(mirrorContract.getTimestamp()).isNotNull(); - assertThat(mirrorContract.getTimestamp().getFrom()).isNotNull(); - - if (contractClient - .getSdkClient() - .getAcceptanceTestProperties() - .getFeatureProperties() - .isSidecars()) { - assertThat(mirrorContract.getRuntimeBytecode()).isNotNull(); - } - - assertThat(mirrorContract.getBytecode()) - .isEqualTo(deployedParentContract.compiledSolidityArtifact().getBytecode()); - - if (isDeleted) { - assertThat(mirrorContract.getObtainerId()) - .isEqualTo(contractClient - .getSdkClient() - .getExpandedOperatorAccountId() - .getAccountId() - .toString()); - } else { - assertThat(mirrorContract.getObtainerId()).isNull(); - } - - return mirrorContract; - } - - private void verifyContractExecutionResultsById(boolean isCreation) { - List contractResults = mirrorClient - .getContractResultsById(deployedParentContract.contractId().toString()) - .getResults(); - - assertThat(contractResults) - .isNotEmpty() - .allSatisfy(result -> verifyEthereumContractExecutionResults(result, isCreation)); - } - - private void verifyContractExecutionResultsByTransactionId(boolean isCreation) { - ContractResult contractResult = mirrorClient.getContractResultByTransactionId( - networkTransactionResponse.getTransactionIdStringNoCheckSum()); - - verifyEthereumContractExecutionResults(contractResult, isCreation); - assertThat(contractResult.getBlockHash()).isNotBlank(); - assertThat(contractResult.getBlockNumber()).isPositive(); - assertThat(contractResult.getHash()).isNotBlank(); - } - - private void verifyEthereumContractExecutionResults(ContractResult contractResult, boolean isCreation) { - assertThat(contractResult.getCallResult()).isNotBlank(); - assertThat(contractResult.getContractId()) - .isEqualTo(deployedParentContract.contractId().toString()); - var createdIds = contractResult.getCreatedContractIds(); - if (isCreation) { - assertThat(createdIds).isNotEmpty(); - } - assertThat(contractResult.getErrorMessage()).isBlank(); - assertThat(contractResult.getFailedInitcode()).isBlank(); - assertThat(contractResult.getFrom()).isEqualTo(FeatureInputHandler.evmAddress(AccountId.fromString(account))); - assertThat(contractResult.getGasLimit()) - .isEqualTo(contractClient - .getSdkClient() - .getAcceptanceTestProperties() - .getFeatureProperties() - .getMaxContractFunctionGas()); - assertThat(contractResult.getGasUsed()).isPositive(); - assertThat(contractResult.getTo()) - .isEqualTo(FeatureInputHandler.evmAddress(deployedParentContract.contractId())); - } - - protected TransactionDetail verifyEthereumContractCreate( - MirrorNodeClient mirrorClient, int status, boolean finalizeHollowAccount) { - String transactionId = networkTransactionResponse.getTransactionIdStringNoCheckSum(); - TransactionByIdResponse mirrorTransactionsResponse = mirrorClient.getTransactions(transactionId); - - List transactions = mirrorTransactionsResponse.getTransactions(); - assertNotNull(transactions); - assertThat(transactions).isNotEmpty(); - TransactionDetail mirrorTransaction; - - mirrorTransaction = finalizeHollowAccount ? transactions.get(1) : transactions.get(0); - - if (status == HttpStatus.OK.value()) { - assertThat(mirrorTransaction.getResult()).isEqualTo("SUCCESS"); - } - - assertThat(mirrorTransaction.getValidStartTimestamp()).isNotNull(); - assertThat(mirrorTransaction.getName()).isNotNull(); - assertThat(mirrorTransaction.getResult()).isNotNull(); - assertThat(mirrorTransaction.getConsensusTimestamp()).isNotNull(); - - assertThat(mirrorTransaction.getValidStartTimestamp()) - .isEqualTo(networkTransactionResponse.getValidStartString()); - assertThat(mirrorTransaction.getTransactionId()).isEqualTo(transactionId); - - return mirrorTransaction; - } -} diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthereumFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthereumFeature.java index 5ac8f2d852f..ba00cf50ca9 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthereumFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthereumFeature.java @@ -17,6 +17,9 @@ package com.hedera.mirror.test.e2e.acceptance.steps; import static com.hedera.mirror.rest.model.TransactionTypes.CRYPTOCREATEACCOUNT; +import static com.hedera.mirror.test.e2e.acceptance.steps.AbstractFeature.ContractResource.PARENT_CONTRACT; +import static com.hedera.mirror.test.e2e.acceptance.steps.EstimateFeature.ContractMethods.CREATE_CHILD; +import static com.hedera.mirror.test.e2e.acceptance.steps.EstimateFeature.ContractMethods.GET_BYTE_CODE; import static org.assertj.core.api.Assertions.assertThat; import com.hedera.hashgraph.sdk.AccountId; @@ -39,6 +42,7 @@ import java.io.IOException; import java.math.BigInteger; import java.util.Comparator; +import java.util.Objects; import lombok.CustomLog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -101,7 +105,11 @@ public void verifyAccountCreated() { @Given("I successfully create contract by Legacy ethereum transaction") public void createNewERCtestContract() { - deployedParentContract = ethereumContractCreate(ContractResource.PARENT_CONTRACT); + deployedParentContract = ethereumContractCreate(PARENT_CONTRACT); + + gasConsumedSelector = Objects.requireNonNull(mirrorClient + .getContractInfo(deployedParentContract.contractId().toSolidityAddress()) + .getBytecode()); } @Then("the mirror node REST API should return status {int} for the eth contract creation transaction") @@ -135,6 +143,8 @@ public void callContract() { parameters, null, EthTxData.EthTransactionType.EIP1559); + + gasConsumedSelector = encodeDataToByteArray(PARENT_CONTRACT, CREATE_CHILD, BigInteger.valueOf(1000)); } @Then("the mirror node REST API should verify the ethereum called contract function") @@ -152,6 +162,14 @@ public void getChildBytecode() { childContractBytecodeFromParent = executeContractResult.contractFunctionResult().getBytes(0); assertThat(childContractBytecodeFromParent).isNotNull(); + + gasConsumedSelector = encodeDataToByteArray(PARENT_CONTRACT, GET_BYTE_CODE); + } + + @Then("the mirror node contract results API should return an accurate gas consumed") + public void verifyGasConsumedIsCorrect() { + String txId = networkTransactionResponse.getTransactionIdStringNoCheckSum(); + verifyGasConsumed(txId); } public DeployedContract ethereumContractCreate(ContractResource contractResource) { diff --git a/hedera-mirror-test/src/test/resources/features/contract/eth.feature b/hedera-mirror-test/src/test/resources/features/contract/eth.feature deleted file mode 100644 index a2d87c1e168..00000000000 --- a/hedera-mirror-test/src/test/resources/features/contract/eth.feature +++ /dev/null @@ -1,67 +0,0 @@ -@contractbase @fullsuite -Feature: Ethereum transactions Coverage Feature - - @critical @release @acceptance - Scenario Outline: Validate Ethereum Contract create and call - - The steps in this scenario are very similar to those in contract feature. The update and the deletion - steps are skipped because of the immutability of the contracts that are created by ethereum transactions. - - Using the Java SDK and the mirror node REST API, create and call a smart contract and - verify proper operation. Use that same parent contract to create a second child contract using - the CREATE2 flow, namely by retrieving the child contract bytecode and using it, and a salt value, - to compute the child's EVM address and then deploying the contract to that address. - - Once the child EVM address is known, issue a crypto transfer transaction to that EVM address, creating - a hollow account. Verify the account exists and indeed is hollow, but that a contract does not yet - exist at that EVM address. Then, using a parent contract function, deploy the child contract using - the CREATE2 opcode and with the same bytecode and salt, thus this is done to the child EVM address. - - With the contract now present, and the account no longer hollow, invoke a function on the child contract - to cause its self destruction, vacating the EVM address for future use. Also, Finally, delete the original parent - contract and its underlying bytecode file. - - Given I successfully created a signer account with an EVM address alias - Then validate the signer account and its balance - - Given I successfully create parent contract by ethereum transaction - Then the mirror node contract results API should return an accurate gas consumed - And the mirror node REST API should return status for the eth contract creation transaction - And the mirror node REST API should verify the deployed contract entity by eth call - - When I successfully call the child creation function using EIP-1559 ethereum transaction - Then the mirror node contract results API should return an accurate gas consumed - And the mirror node REST API should return status for the ethereum transaction - And the mirror node REST API should verify the child creation ethereum transaction - - Given I call the parent contract to retrieve child's bytecode by Legacy ethereum transaction - Then the mirror node contract results API should return an accurate gas consumed - And the mirror node REST API should return status for the ethereum transaction - And the mirror node REST API should verify the ethereum called contract function - - When I call the parent contract evm address function with the bytecode of the child with EIP-2930 ethereum transaction - Then the mirror node contract results API should return an accurate gas consumed - And the mirror node REST API should return status for the ethereum transaction - And the mirror node REST API should verify the ethereum called contract function - - And I create a hollow account using CryptoTransfer of to the child's evm address - Then the mirror node REST API should return status for the transfer transaction - And the mirror node REST API should verify the account is hollow and has - And the mirror node REST API should not find a contract when using child's evm address - - When I create a child contract by calling the parent contract function to deploy using CREATE2 with EIP-1559 - Then the mirror node contract results API should return an accurate gas consumed - And the mirror node REST API should return status for the ethereum transaction - And the mirror node REST API should retrieve the contract when using child's evm address - And the mirror node REST API should verify that the account is not hollow - - When I successfully delete the child contract by calling vacateAddress function using EIP-1559 ethereum transaction - Then the mirror node REST API should return status for the vacateAddress call transaction - Then I successfully delete the parent bytecode file - - - - - Examples: - | httpStatusCode | transferAmount | - | 200 | 1000 | \ No newline at end of file diff --git a/hedera-mirror-test/src/test/resources/features/contract/ethereum.feature b/hedera-mirror-test/src/test/resources/features/contract/ethereum.feature index 95a619a0a13..91d7ae66d65 100644 --- a/hedera-mirror-test/src/test/resources/features/contract/ethereum.feature +++ b/hedera-mirror-test/src/test/resources/features/contract/ethereum.feature @@ -8,15 +8,18 @@ Feature: Ethereum transactions Coverage Feature Then validate the signer account and its balance Given I successfully create contract by Legacy ethereum transaction - Then the mirror node REST API should return status for the eth contract creation transaction + Then the mirror node contract results API should return an accurate gas consumed + And the mirror node REST API should return status for the eth contract creation transaction And the mirror node REST API should verify the deployed contract entity by eth call When I successfully call function using EIP-1559 ethereum transaction - Then the mirror node REST API should return status for the ethereum transaction + Then the mirror node contract results API should return an accurate gas consumed + And the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the ethereum called contract function Given I successfully call function using EIP-2930 ethereum transaction - Then the mirror node REST API should return status for the ethereum transaction + Then the mirror node contract results API should return an accurate gas consumed + And the mirror node REST API should return status for the ethereum transaction And the mirror node REST API should verify the ethereum called contract function Examples: From 00ca84c23742a39d2373ae8137f67abfebf1a554 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Thu, 30 May 2024 09:58:23 +0300 Subject: [PATCH 09/11] fix: merge conflicts Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- hedera-mirror-test/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/hedera-mirror-test/build.gradle.kts b/hedera-mirror-test/build.gradle.kts index 75ba6b095f3..d9a205f93c4 100644 --- a/hedera-mirror-test/build.gradle.kts +++ b/hedera-mirror-test/build.gradle.kts @@ -58,7 +58,6 @@ dependencies { testImplementation("org.springframework.retry:spring-retry") testImplementation("org.apache.tuweni:tuweni-bytes") testImplementation("commons-codec:commons-codec") - testImplementation("org.bouncycastle:bcprov-jdk18on:1.78") } // Disable the default test task and only run acceptance tests during the standalone "acceptance" From 88e58ca3dd892dd4f32c6747d43e974102e1d30e Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Thu, 30 May 2024 10:01:54 +0300 Subject: [PATCH 10/11] nit: remove not used fields Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../mirror/test/e2e/acceptance/steps/AbstractFeature.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java index 11d48c6d77f..a20bcee4ef2 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractFeature.java @@ -30,11 +30,9 @@ import com.hedera.mirror.rest.model.NetworkExchangeRateSetResponse; import com.hedera.mirror.rest.model.TransactionByIdResponse; import com.hedera.mirror.rest.model.TransactionDetail; -import com.hedera.mirror.test.e2e.acceptance.client.AccountClient; import com.hedera.mirror.test.e2e.acceptance.client.ContractClient; import com.hedera.mirror.test.e2e.acceptance.client.ContractClient.NodeNameEnum; import com.hedera.mirror.test.e2e.acceptance.client.EncoderDecoderFacade; -import com.hedera.mirror.test.e2e.acceptance.client.EthereumClient; import com.hedera.mirror.test.e2e.acceptance.client.FileClient; import com.hedera.mirror.test.e2e.acceptance.client.MirrorNodeClient; import com.hedera.mirror.test.e2e.acceptance.client.NetworkAdapter; @@ -63,12 +61,6 @@ public abstract class AbstractFeature extends EncoderDecoderFacade { @Autowired protected ContractClient contractClient; - @Autowired - protected EthereumClient ethereumClient; - - @Autowired - protected AccountClient accountClient; - @Autowired protected FileClient fileClient; From 8757a96f74dba92add3f755a77d32d7a8e602623 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Thu, 30 May 2024 17:49:30 +0300 Subject: [PATCH 11/11] refactor: address PR comments Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../acceptance/steps/AbstractEstimateFeature.java | 2 +- .../e2e/acceptance/steps/BaseContractFeature.java | 2 +- .../test/e2e/acceptance/steps/EstimateFeature.java | 5 +---- .../test/e2e/acceptance/steps/EthereumFeature.java | 2 +- .../resources/features/contract/ethereum.feature | 12 ++++++------ 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java index c5bf72cf309..b0556dc0221 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AbstractEstimateFeature.java @@ -34,7 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.client.HttpClientErrorException; -abstract class AbstractEstimateFeature extends AbstractFeature { +abstract class AbstractEstimateFeature extends BaseContractFeature { private static final int BASE_GAS_FEE = 21_000; private static final int ADDITIONAL_FEE_FOR_CREATE = 32_000; diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/BaseContractFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/BaseContractFeature.java index 7f5457fd24d..3c113e4511f 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/BaseContractFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/BaseContractFeature.java @@ -23,7 +23,7 @@ import com.hedera.mirror.test.e2e.acceptance.util.FeatureInputHandler; import java.util.List; -public abstract class BaseContractFeature extends AbstractEstimateFeature { +public abstract class BaseContractFeature extends AbstractFeature { protected DeployedContract deployedParentContract; protected ContractResponse verifyContractFromMirror(boolean isDeleted) { diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java index 9364adb93ec..6c1657582b6 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EstimateFeature.java @@ -800,10 +800,7 @@ enum ContractMethods implements ContractMethodInterface { IERC20_TOKEN_ASSOCIATE("associate()", 728033), IERC20_TOKEN_DISSOCIATE("dissociate()", 728033), CREATE_CHILD("createChild", 0), - GET_BYTE_CODE("getBytecode", 0), - GET_ADDRESS("getAddress", 0), - CREATE_2_DEPLOY("create2Deploy", 0), - VACATE_ADDRESS("vacateAddress", 0); + GET_BYTE_CODE("getBytecode", 0); private final String selector; private final int actualGas; diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthereumFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthereumFeature.java index ba00cf50ca9..ccc7afe4fd6 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthereumFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/EthereumFeature.java @@ -48,7 +48,7 @@ import org.springframework.http.HttpStatus; @CustomLog -public class EthereumFeature extends BaseContractFeature { +public class EthereumFeature extends AbstractEstimateFeature { @Autowired protected EthereumClient ethereumClient; diff --git a/hedera-mirror-test/src/test/resources/features/contract/ethereum.feature b/hedera-mirror-test/src/test/resources/features/contract/ethereum.feature index 91d7ae66d65..9059f56d15a 100644 --- a/hedera-mirror-test/src/test/resources/features/contract/ethereum.feature +++ b/hedera-mirror-test/src/test/resources/features/contract/ethereum.feature @@ -8,18 +8,18 @@ Feature: Ethereum transactions Coverage Feature Then validate the signer account and its balance Given I successfully create contract by Legacy ethereum transaction - Then the mirror node contract results API should return an accurate gas consumed - And the mirror node REST API should return status for the eth contract creation transaction + Then the mirror node REST API should return status for the eth contract creation transaction + And the mirror node contract results API should return an accurate gas consumed And the mirror node REST API should verify the deployed contract entity by eth call When I successfully call function using EIP-1559 ethereum transaction - Then the mirror node contract results API should return an accurate gas consumed - And the mirror node REST API should return status for the ethereum transaction + Then the mirror node REST API should return status for the ethereum transaction + And the mirror node contract results API should return an accurate gas consumed And the mirror node REST API should verify the ethereum called contract function Given I successfully call function using EIP-2930 ethereum transaction - Then the mirror node contract results API should return an accurate gas consumed - And the mirror node REST API should return status for the ethereum transaction + Then the mirror node REST API should return status for the ethereum transaction + And the mirror node contract results API should return an accurate gas consumed And the mirror node REST API should verify the ethereum called contract function Examples: