diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceOpcodeTracerTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceOpcodeTracerTest.java new file mode 100644 index 00000000000..0cc5d9a0924 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceOpcodeTracerTest.java @@ -0,0 +1,134 @@ +/* + * 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.web3.service; + +import static com.hedera.mirror.web3.utils.OpcodeTracerUtil.OPTIONS; +import static com.hedera.mirror.web3.utils.OpcodeTracerUtil.gasComparator; +import static com.hedera.mirror.web3.utils.OpcodeTracerUtil.toHumanReadableMessage; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.doAnswer; + +import com.hedera.mirror.web3.common.ContractCallContext; +import com.hedera.mirror.web3.convert.BytesDecoder; +import com.hedera.mirror.web3.evm.contracts.execution.OpcodesProcessingResult; +import com.hedera.mirror.web3.service.model.ContractDebugParameters; +import com.hedera.mirror.web3.utils.ContractFunctionProviderRecord; +import com.hedera.node.app.service.evm.contracts.execution.HederaEvmTransactionProcessingResult; +import jakarta.annotation.Resource; +import lombok.SneakyThrows; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.web3j.tx.Contract; + +abstract class AbstractContractCallServiceOpcodeTracerTest extends AbstractContractCallServiceTest { + + @Resource + protected ContractDebugService contractDebugService; + + @Captor + private ArgumentCaptor paramsCaptor; + + @Captor + private ArgumentCaptor gasCaptor; + + private HederaEvmTransactionProcessingResult resultCaptor; + private ContractCallContext contextCaptor; + + @BeforeEach + void setUpArgumentCaptors() { + doAnswer(invocation -> { + final var transactionProcessingResult = + (HederaEvmTransactionProcessingResult) invocation.callRealMethod(); + resultCaptor = transactionProcessingResult; + contextCaptor = ContractCallContext.get(); + return transactionProcessingResult; + }) + .when(processor) + .execute(paramsCaptor.capture(), gasCaptor.capture()); + } + + protected void verifyOpcodeTracerCall( + final String callData, final ContractFunctionProviderRecord functionProvider) { + final var callDataBytes = Bytes.fromHexString(callData); + final var debugParameters = getDebugParameters(functionProvider, callDataBytes); + + if (functionProvider.expectedErrorMessage() != null) { + verifyThrowingOpcodeTracerCall(debugParameters, functionProvider); + } else { + verifySuccessfulOpcodeTracerCall(debugParameters); + } + assertThat(paramsCaptor.getValue()).isEqualTo(debugParameters); + assertThat(gasCaptor.getValue()).isEqualTo(debugParameters.getGas()); + } + + protected void verifyOpcodeTracerCall(final String callData, final Contract contract) { + ContractFunctionProviderRecord functionProvider = ContractFunctionProviderRecord.builder() + .contractAddress(Address.fromHexString(contract.getContractAddress())) + .build(); + + final var callDataBytes = Bytes.fromHexString(callData); + final var debugParameters = getDebugParameters(functionProvider, callDataBytes); + + if (functionProvider.expectedErrorMessage() != null) { + verifyThrowingOpcodeTracerCall(debugParameters, functionProvider); + } else { + verifySuccessfulOpcodeTracerCall(debugParameters); + } + assertThat(paramsCaptor.getValue()).isEqualTo(debugParameters); + assertThat(gasCaptor.getValue()).isEqualTo(debugParameters.getGas()); + } + + @SneakyThrows + protected void verifyThrowingOpcodeTracerCall( + final ContractDebugParameters params, final ContractFunctionProviderRecord function) { + final var actual = contractDebugService.processOpcodeCall(params, OPTIONS); + assertThat(actual.transactionProcessingResult().isSuccessful()).isFalse(); + assertThat(actual.transactionProcessingResult().getOutput()).isEqualTo(Bytes.EMPTY); + assertThat(actual.transactionProcessingResult()) + .satisfiesAnyOf( + result -> assertThat(result.getRevertReason()) + .isPresent() + .map(BytesDecoder::maybeDecodeSolidityErrorStringToReadableMessage) + .hasValue(function.expectedErrorMessage()), + result -> assertThat(result.getHaltReason()) + .isPresent() + .map(ExceptionalHaltReason::getDescription) + .hasValue(function.expectedErrorMessage())); + assertThat(actual.opcodes().size()).isNotZero(); + assertThat(toHumanReadableMessage(actual.opcodes().getLast().reason())) + .isEqualTo(function.expectedErrorMessage()); + } + + protected void verifySuccessfulOpcodeTracerCall(final ContractDebugParameters params) { + final var actual = contractDebugService.processOpcodeCall(params, OPTIONS); + final var expected = new OpcodesProcessingResult(resultCaptor, contextCaptor.getOpcodes()); + // Compare transaction processing result + assertThat(actual.transactionProcessingResult()) + .usingRecursiveComparison() + .ignoringFields("logs") + .isEqualTo(expected.transactionProcessingResult()); + // Compare opcodes with gas tolerance + assertThat(actual.opcodes()) + .usingRecursiveComparison() + .withComparatorForFields(gasComparator(), "gas") + .isEqualTo(expected.opcodes()); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallDynamicCallsTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallDynamicCallsTest.java index 3ec338d7559..8cf73e31c27 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallDynamicCallsTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallDynamicCallsTest.java @@ -34,6 +34,7 @@ import com.hedera.mirror.web3.common.ContractCallContext; import com.hedera.mirror.web3.evm.store.Store.OnMissing; import com.hedera.mirror.web3.exception.MirrorEvmTransactionException; +import com.hedera.mirror.web3.utils.ContractFunctionProviderRecord; import com.hedera.mirror.web3.web3j.generated.DynamicEthCalls; import com.hedera.mirror.web3.web3j.generated.DynamicEthCalls.AccountAmount; import com.hedera.mirror.web3.web3j.generated.DynamicEthCalls.NftTransfer; @@ -46,15 +47,15 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -class ContractCallDynamicCallsTest extends AbstractContractCallServiceTest { +class ContractCallDynamicCallsTest extends AbstractContractCallServiceOpcodeTracerTest { @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 100, - NON_FUNGIBLE_UNIQUE, 0, NftMetadata - """) + FUNGIBLE_COMMON, 100, + NON_FUNGIBLE_UNIQUE, 0, NftMetadata + """) void mintTokenGetTotalSupplyAndBalanceOfTreasury( final TokenTypeEnum tokenType, final long amount, final String metadata) { // Given @@ -79,15 +80,16 @@ void mintTokenGetTotalSupplyAndBalanceOfTreasury( // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 1, 0 - NON_FUNGIBLE_UNIQUE, 0, 1 - """) + FUNGIBLE_COMMON, 1, 0 + NON_FUNGIBLE_UNIQUE, 0, 1 + """) void burnTokenGetTotalSupplyAndBalanceOfTreasury( final TokenTypeEnum tokenType, final long amount, final long serialNumber) { // Given @@ -112,15 +114,16 @@ void burnTokenGetTotalSupplyAndBalanceOfTreasury( // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 1, 0 - NON_FUNGIBLE_UNIQUE, 0, 1 - """) + FUNGIBLE_COMMON, 1, 0 + NON_FUNGIBLE_UNIQUE, 0, 1 + """) void wipeTokenGetTotalSupplyAndBalanceOfTreasury( final TokenTypeEnum tokenType, final long amount, final long serialNumber) { // Given @@ -147,13 +150,14 @@ void wipeTokenGetTotalSupplyAndBalanceOfTreasury( // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource(textBlock = """ - FUNGIBLE_COMMON, - NON_FUNGIBLE_UNIQUE - """) + FUNGIBLE_COMMON, + NON_FUNGIBLE_UNIQUE + """) void pauseTokenGetPauseStatusUnpauseGetPauseStatus(final TokenTypeEnum tokenType) { // Given final var treasuryEntityId = accountPersist(); @@ -173,13 +177,14 @@ void pauseTokenGetPauseStatusUnpauseGetPauseStatus(final TokenTypeEnum tokenType // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource(textBlock = """ - FUNGIBLE_COMMON, - NON_FUNGIBLE_UNIQUE - """) + FUNGIBLE_COMMON, + NON_FUNGIBLE_UNIQUE + """) void freezeTokenGetPauseStatusUnpauseGetPauseStatus(final TokenTypeEnum tokenType) { // Given final var treasuryEntityId = accountPersist(); @@ -200,6 +205,7 @@ void freezeTokenGetPauseStatusUnpauseGetPauseStatus(final TokenTypeEnum tokenTyp // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @Test @@ -220,6 +226,11 @@ void associateTokenTransferEthCallFail() { BigInteger.ZERO, BigInteger.ONE); + final var contractFunctionProvider = ContractFunctionProviderRecord.builder() + .contractAddress(Address.fromHexString(contract.getContractAddress())) + .expectedErrorMessage("Failed to associate tokens") + .build(); + // Then assertThatThrownBy(functionCall::send) .isInstanceOf(MirrorEvmTransactionException.class) @@ -227,15 +238,17 @@ void associateTokenTransferEthCallFail() { MirrorEvmTransactionException exception = (MirrorEvmTransactionException) ex; assertEquals("Failed to associate tokens", exception.getDetail()); }); + + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contractFunctionProvider); } @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 1, 0 - NON_FUNGIBLE_UNIQUE, 0, 1 - """) + FUNGIBLE_COMMON, 1, 0 + NON_FUNGIBLE_UNIQUE, 0, 1 + """) void associateTokenTransfer(final TokenTypeEnum tokenType, final long amount, final long serialNumber) { // Given final var treasuryEntityId = accountPersist(); @@ -261,15 +274,16 @@ void associateTokenTransfer(final TokenTypeEnum tokenType, final long amount, fi BigInteger.valueOf(serialNumber)); // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 1, 0, IERC20: failed to transfer - NON_FUNGIBLE_UNIQUE, 0, 1, IERC721: failed to transfer - """) + FUNGIBLE_COMMON, 1, 0, IERC20: failed to transfer + NON_FUNGIBLE_UNIQUE, 0, 1, IERC721: failed to transfer + """) void associateTokenDissociateFailTransferEthCall( final TokenTypeEnum tokenType, final long amount, @@ -297,6 +311,11 @@ void associateTokenDissociateFailTransferEthCall( BigInteger.valueOf(amount), BigInteger.valueOf(serialNumber)); + final var contractFunctionProvider = ContractFunctionProviderRecord.builder() + .contractAddress(Address.fromHexString(contract.getContractAddress())) + .expectedErrorMessage(expectedErrorMessage) + .build(); + // Then assertThatThrownBy(functionCall::send) .isInstanceOf(MirrorEvmTransactionException.class) @@ -304,15 +323,17 @@ void associateTokenDissociateFailTransferEthCall( MirrorEvmTransactionException exception = (MirrorEvmTransactionException) ex; assertEquals(expectedErrorMessage, exception.getDetail()); }); + + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contractFunctionProvider); } @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 1, 0 - NON_FUNGIBLE_UNIQUE, 0, 1 - """) + FUNGIBLE_COMMON, 1, 0 + NON_FUNGIBLE_UNIQUE, 0, 1 + """) void approveTokenGetAllowance(final TokenTypeEnum tokenType, final long amount, final long serialNumber) { // Given final var treasuryEntityId = accountPersist(); @@ -348,15 +369,16 @@ void approveTokenGetAllowance(final TokenTypeEnum tokenType, final long amount, // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 1, 0 - NON_FUNGIBLE_UNIQUE, 0, 1 - """) + FUNGIBLE_COMMON, 1, 0 + NON_FUNGIBLE_UNIQUE, 0, 1 + """) void approveTokenTransferFromGetAllowanceGetBalance( final TokenTypeEnum tokenType, final long amount, final long serialNumber) { // Given @@ -391,15 +413,16 @@ void approveTokenTransferFromGetAllowanceGetBalance( // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 1, 0 - NON_FUNGIBLE_UNIQUE, 0, 1 - """) + FUNGIBLE_COMMON, 1, 0 + NON_FUNGIBLE_UNIQUE, 0, 1 + """) void approveTokenTransferGetAllowanceGetBalance( final TokenTypeEnum tokenType, final long amount, final long serialNumber) { // Given @@ -428,15 +451,16 @@ void approveTokenTransferGetAllowanceGetBalance( // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 1, 0 - NON_FUNGIBLE_UNIQUE, 0, 1 - """) + FUNGIBLE_COMMON, 1, 0 + NON_FUNGIBLE_UNIQUE, 0, 1 + """) void approveTokenCryptoTransferGetAllowanceGetBalance( final TokenTypeEnum tokenType, final long amount, final long serialNumber) { // Given @@ -481,6 +505,7 @@ void approveTokenCryptoTransferGetAllowanceGetBalance( // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @Test @@ -506,6 +531,7 @@ void approveForAllTokenTransferFromGetAllowance() { // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @Test @@ -537,15 +563,16 @@ void approveForAllCryptoTransferGetAllowance() { // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 1, 0, false - NON_FUNGIBLE_UNIQUE, 0, 1, true - """) + FUNGIBLE_COMMON, 1, 0, false + NON_FUNGIBLE_UNIQUE, 0, 1, true + """) void cryptoTransferFromGetAllowanceGetBalance( final TokenTypeEnum tokenType, final long amount, final long serialNumber, final boolean approvalForAll) { // Given @@ -591,6 +618,7 @@ void cryptoTransferFromGetAllowanceGetBalance( // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @Test @@ -614,15 +642,16 @@ void transferFromNFTGetAllowance() { // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource( textBlock = """ - FUNGIBLE_COMMON, 1, 0 - NON_FUNGIBLE_UNIQUE, 0, 1 - """) + FUNGIBLE_COMMON, 1, 0 + NON_FUNGIBLE_UNIQUE, 0, 1 + """) void transferFromGetAllowanceGetBalance(final TokenTypeEnum tokenType, final long amount, final long serialNumber) { // Given final var treasuryEntityId = accountPersist(); @@ -651,13 +680,14 @@ void transferFromGetAllowanceGetBalance(final TokenTypeEnum tokenType, final lon // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @ParameterizedTest @CsvSource(textBlock = """ - FUNGIBLE_COMMON - NON_FUNGIBLE_UNIQUE - """) + FUNGIBLE_COMMON + NON_FUNGIBLE_UNIQUE + """) void grantKycRevokeKyc(final TokenTypeEnum tokenType) { // Given final var treasuryEntityId = accountPersist(); @@ -679,6 +709,7 @@ void grantKycRevokeKyc(final TokenTypeEnum tokenType) { // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @Test @@ -691,6 +722,7 @@ void getAddressThis() { // Then verifyEthCallAndEstimateGas(functionCall, contract); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @Test @@ -706,10 +738,12 @@ void getAddressThisWithEvmAliasRecipient() throws Exception { }); // When - final String result = contract.call_getAddressThis().send(); + final var functionCall = contract.call_getAddressThis(); + final String result = functionCall.send(); // Then assertEquals(contractAlias.toHexString(), result); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } @Test @@ -725,10 +759,12 @@ void getAddressThisWithLongZeroRecipientThatHasEvmAlias() throws Exception { }); // When - final String result = contract.call_getAddressThis().send(); + final var functionCall = contract.call_getAddressThis(); + final String result = functionCall.send(); // Then assertEquals(contractAlias.toHexString(), result); + verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contract); } private Token fungibleTokenPersist(final EntityId treasuryEntityId) { diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNestedCallsHistoricalTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNestedCallsHistoricalTest.java index f91e25a73cd..7d7f9380dea 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNestedCallsHistoricalTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNestedCallsHistoricalTest.java @@ -42,7 +42,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class ContractCallNestedCallsHistoricalTest extends AbstractContractCallServiceTest { +class ContractCallNestedCallsHistoricalTest extends AbstractContractCallServiceOpcodeTracerTest { private RecordFile recordFileBeforeEvm34; @@ -80,13 +80,14 @@ void testGetHistoricalInfo() throws Exception { final var contract = testWeb3jService.deploy(NestedCallsHistorical::deploy); // When - final var result = - contract.call_nestedGetTokenInfo(tokenAddress.toHexString()).send(); + final var function = contract.call_nestedGetTokenInfo(tokenAddress.toHexString()); + final var result = function.send(); // Then assertThat(result).isNotNull(); assertThat(result.token).isNotNull(); assertThat(result.deleted).isFalse(); assertThat(result.token.memo).isEqualTo(tokenMemo); + verifyOpcodeTracerCall(function.encodeFunctionCall(), contract); } @Test @@ -111,8 +112,8 @@ void testGetApprovedHistorical() throws Exception { final var contract = testWeb3jService.deploy(NestedCallsHistorical::deploy); // When - final var result = contract.call_nestedHtsGetApproved(tokenAddress.toHexString(), BigInteger.ONE) - .send(); + final var function = contract.call_nestedHtsGetApproved(tokenAddress.toHexString(), BigInteger.ONE); + final var result = function.send(); // Then final var key = ByteString.fromHex(spenderPublicKey); @@ -120,6 +121,7 @@ void testGetApprovedHistorical() throws Exception { Bytes.wrap(recoverAddressFromPubKey(key.substring(2).toByteArray()))) .toString(); assertThat(result).isEqualTo(expectedOutput); + verifyOpcodeTracerCall(function.encodeFunctionCall(), contract); } @Test @@ -154,16 +156,16 @@ void testMintTokenHistorical() throws Exception { final var contract = testWeb3jService.deploy(NestedCallsHistorical::deploy); // When - final var result = contract.call_nestedMintToken( - tokenAddress.toHexString(), - BigInteger.ZERO, - Collections.singletonList( - ByteString.copyFromUtf8("firstMeta").toByteArray())) - .send(); + final var function = contract.call_nestedMintToken( + tokenAddress.toHexString(), + BigInteger.ZERO, + Collections.singletonList(ByteString.copyFromUtf8("firstMeta").toByteArray())); + final var result = function.send(); // Then int expectedTotalSupply = nftAmountToMint + 1; assertThat(result).isEqualTo(BigInteger.valueOf(expectedTotalSupply)); + verifyOpcodeTracerCall(function.encodeFunctionCall(), contract); } private EntityId nftPersistHistorical( diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNestedCallsTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNestedCallsTest.java index 26ea6a855de..e3e8d285f91 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNestedCallsTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNestedCallsTest.java @@ -40,7 +40,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -class ContractCallNestedCallsTest extends AbstractContractCallServiceTest { +class ContractCallNestedCallsTest extends AbstractContractCallServiceOpcodeTracerTest { private static final long CREATE_TOKEN_VALUE = 3070 * 100_000_000L; private static final String EXPECTED_RESULT_NEGATIVE_TESTS = "hardcodedResult"; @@ -49,35 +49,35 @@ class ContractCallNestedCallsTest extends AbstractContractCallServiceTest { @CsvSource( textBlock = """ - CONTRACT_ID, ADMIN_KEY, - CONTRACT_ID, KYC_KEY, - CONTRACT_ID, FREEZE_KEY, - CONTRACT_ID, WIPE_KEY, - CONTRACT_ID, SUPPLY_KEY, - CONTRACT_ID, FEE_SCHEDULE_KEY, - CONTRACT_ID, PAUSE_KEY, - ED25519, ADMIN_KEY, - ED25519, KYC_KEY, - ED25519, FREEZE_KEY, - ED25519, WIPE_KEY, - ED25519, SUPPLY_KEY, - ED25519, FEE_SCHEDULE_KEY, - ED25519, PAUSE_KEY, - ECDSA_SECPK256K1, ADMIN_KEY, - ECDSA_SECPK256K1, KYC_KEY, - ECDSA_SECPK256K1, FREEZE_KEY, - ECDSA_SECPK256K1, WIPE_KEY, - ECDSA_SECPK256K1, SUPPLY_KEY, - ECDSA_SECPK256K1, FEE_SCHEDULE_KEY, - ECDSA_SECPK256K1, PAUSE_KEY, - DELEGATABLE_CONTRACT_ID, ADMIN_KEY, - DELEGATABLE_CONTRACT_ID, KYC_KEY, - DELEGATABLE_CONTRACT_ID, FREEZE_KEY, - DELEGATABLE_CONTRACT_ID, WIPE_KEY, - DELEGATABLE_CONTRACT_ID, SUPPLY_KEY, - DELEGATABLE_CONTRACT_ID, FEE_SCHEDULE_KEY, - DELEGATABLE_CONTRACT_ID, PAUSE_KEY, - """) + CONTRACT_ID, ADMIN_KEY, + CONTRACT_ID, KYC_KEY, + CONTRACT_ID, FREEZE_KEY, + CONTRACT_ID, WIPE_KEY, + CONTRACT_ID, SUPPLY_KEY, + CONTRACT_ID, FEE_SCHEDULE_KEY, + CONTRACT_ID, PAUSE_KEY, + ED25519, ADMIN_KEY, + ED25519, KYC_KEY, + ED25519, FREEZE_KEY, + ED25519, WIPE_KEY, + ED25519, SUPPLY_KEY, + ED25519, FEE_SCHEDULE_KEY, + ED25519, PAUSE_KEY, + ECDSA_SECPK256K1, ADMIN_KEY, + ECDSA_SECPK256K1, KYC_KEY, + ECDSA_SECPK256K1, FREEZE_KEY, + ECDSA_SECPK256K1, WIPE_KEY, + ECDSA_SECPK256K1, SUPPLY_KEY, + ECDSA_SECPK256K1, FEE_SCHEDULE_KEY, + ECDSA_SECPK256K1, PAUSE_KEY, + DELEGATABLE_CONTRACT_ID, ADMIN_KEY, + DELEGATABLE_CONTRACT_ID, KYC_KEY, + DELEGATABLE_CONTRACT_ID, FREEZE_KEY, + DELEGATABLE_CONTRACT_ID, WIPE_KEY, + DELEGATABLE_CONTRACT_ID, SUPPLY_KEY, + DELEGATABLE_CONTRACT_ID, FEE_SCHEDULE_KEY, + DELEGATABLE_CONTRACT_ID, PAUSE_KEY, + """) void updateTokenKeysAndGetUpdatedTokenKeyForFungibleToken(final KeyValueType keyValueType, final KeyType keyType) throws Exception { // Given @@ -107,35 +107,35 @@ void updateTokenKeysAndGetUpdatedTokenKeyForFungibleToken(final KeyValueType key @CsvSource( textBlock = """ - CONTRACT_ID, ADMIN_KEY, - CONTRACT_ID, KYC_KEY, - CONTRACT_ID, FREEZE_KEY, - CONTRACT_ID, WIPE_KEY, - CONTRACT_ID, SUPPLY_KEY, - CONTRACT_ID, FEE_SCHEDULE_KEY, - CONTRACT_ID, PAUSE_KEY, - ED25519, ADMIN_KEY, - ED25519, KYC_KEY, - ED25519, FREEZE_KEY, - ED25519, WIPE_KEY, - ED25519, SUPPLY_KEY, - ED25519, FEE_SCHEDULE_KEY, - ED25519, PAUSE_KEY, - ECDSA_SECPK256K1, ADMIN_KEY, - ECDSA_SECPK256K1, KYC_KEY, - ECDSA_SECPK256K1, FREEZE_KEY, - ECDSA_SECPK256K1, WIPE_KEY, - ECDSA_SECPK256K1, SUPPLY_KEY, - ECDSA_SECPK256K1, FEE_SCHEDULE_KEY, - ECDSA_SECPK256K1, PAUSE_KEY, - DELEGATABLE_CONTRACT_ID, ADMIN_KEY, - DELEGATABLE_CONTRACT_ID, KYC_KEY, - DELEGATABLE_CONTRACT_ID, FREEZE_KEY, - DELEGATABLE_CONTRACT_ID, WIPE_KEY, - DELEGATABLE_CONTRACT_ID, SUPPLY_KEY, - DELEGATABLE_CONTRACT_ID, FEE_SCHEDULE_KEY, - DELEGATABLE_CONTRACT_ID, PAUSE_KEY - """) + CONTRACT_ID, ADMIN_KEY, + CONTRACT_ID, KYC_KEY, + CONTRACT_ID, FREEZE_KEY, + CONTRACT_ID, WIPE_KEY, + CONTRACT_ID, SUPPLY_KEY, + CONTRACT_ID, FEE_SCHEDULE_KEY, + CONTRACT_ID, PAUSE_KEY, + ED25519, ADMIN_KEY, + ED25519, KYC_KEY, + ED25519, FREEZE_KEY, + ED25519, WIPE_KEY, + ED25519, SUPPLY_KEY, + ED25519, FEE_SCHEDULE_KEY, + ED25519, PAUSE_KEY, + ECDSA_SECPK256K1, ADMIN_KEY, + ECDSA_SECPK256K1, KYC_KEY, + ECDSA_SECPK256K1, FREEZE_KEY, + ECDSA_SECPK256K1, WIPE_KEY, + ECDSA_SECPK256K1, SUPPLY_KEY, + ECDSA_SECPK256K1, FEE_SCHEDULE_KEY, + ECDSA_SECPK256K1, PAUSE_KEY, + DELEGATABLE_CONTRACT_ID, ADMIN_KEY, + DELEGATABLE_CONTRACT_ID, KYC_KEY, + DELEGATABLE_CONTRACT_ID, FREEZE_KEY, + DELEGATABLE_CONTRACT_ID, WIPE_KEY, + DELEGATABLE_CONTRACT_ID, SUPPLY_KEY, + DELEGATABLE_CONTRACT_ID, FEE_SCHEDULE_KEY, + DELEGATABLE_CONTRACT_ID, PAUSE_KEY + """) void updateTokenKeysAndGetUpdatedTokenKeyForNFT(final KeyValueType keyValueType, final KeyType keyType) throws Exception { // Given @@ -163,9 +163,9 @@ void updateTokenKeysAndGetUpdatedTokenKeyForNFT(final KeyValueType keyValueType, @ParameterizedTest @CsvSource(textBlock = """ - FUNGIBLE_COMMON - NON_FUNGIBLE_UNIQUE - """) + FUNGIBLE_COMMON + NON_FUNGIBLE_UNIQUE + """) void updateTokenExpiryAndGetUpdatedTokenExpiry(final TokenTypeEnum tokenType) throws Exception { // Given final var tokenEntityId = tokenType == TokenTypeEnum.FUNGIBLE_COMMON ? fungibleTokenPersist() : nftPersist(); @@ -190,9 +190,9 @@ void updateTokenExpiryAndGetUpdatedTokenExpiry(final TokenTypeEnum tokenType) th @ParameterizedTest @CsvSource(textBlock = """ - FUNGIBLE_COMMON - NON_FUNGIBLE_UNIQUE - """) + FUNGIBLE_COMMON + NON_FUNGIBLE_UNIQUE + """) void updateTokenInfoAndGetUpdatedTokenInfoSymbol(final TokenTypeEnum tokenType) throws Exception { // Given final var treasuryEntity = accountPersist(); @@ -220,9 +220,9 @@ void updateTokenInfoAndGetUpdatedTokenInfoSymbol(final TokenTypeEnum tokenType) @ParameterizedTest @CsvSource(textBlock = """ - FUNGIBLE_COMMON - NON_FUNGIBLE_UNIQUE - """) + FUNGIBLE_COMMON + NON_FUNGIBLE_UNIQUE + """) void updateTokenInfoAndGetUpdatedTokenInfoName(final TokenTypeEnum tokenType) throws Exception { // Given final var treasuryEntity = accountPersist(); @@ -250,9 +250,9 @@ void updateTokenInfoAndGetUpdatedTokenInfoName(final TokenTypeEnum tokenType) th @ParameterizedTest @CsvSource(textBlock = """ - FUNGIBLE_COMMON - NON_FUNGIBLE_UNIQUE - """) + FUNGIBLE_COMMON + NON_FUNGIBLE_UNIQUE + """) void updateTokenInfoAndGetUpdatedTokenInfoMemo(final TokenTypeEnum tokenType) throws Exception { // Given final var treasuryEntity = accountPersist(); @@ -280,9 +280,9 @@ void updateTokenInfoAndGetUpdatedTokenInfoMemo(final TokenTypeEnum tokenType) th @ParameterizedTest @CsvSource(textBlock = """ - FUNGIBLE_COMMON - NON_FUNGIBLE_UNIQUE - """) + FUNGIBLE_COMMON + NON_FUNGIBLE_UNIQUE + """) void deleteTokenAndGetTokenInfoIsDeleted(final TokenTypeEnum tokenType) throws Exception { // Given final var tokenEntityId = tokenType == TokenTypeEnum.FUNGIBLE_COMMON ? fungibleTokenPersist() : nftPersist(); @@ -305,10 +305,10 @@ void deleteTokenAndGetTokenInfoIsDeleted(final TokenTypeEnum tokenType) throws E @CsvSource( textBlock = """ - true, false, true, true - false, false, false, false - true, true, true, true - """) + true, false, true, true + false, false, false, false + true, true, true, true + """) void createFungibleTokenAndGetIsTokenAndGetDefaultFreezeStatusAndGetDefaultKycStatus( final boolean withKeys, final boolean inheritKey, @@ -346,10 +346,10 @@ void createFungibleTokenAndGetIsTokenAndGetDefaultFreezeStatusAndGetDefaultKycSt @CsvSource( textBlock = """ - true, false, true, true - false, false, false, false - true, true, true, true - """) + true, false, true, true + false, false, false, false + true, true, true, true + """) void createNFTAndGetIsTokenAndGetDefaultFreezeStatusAndGetDefaultKycStatus( final boolean withKeys, final boolean inheritKey, @@ -387,11 +387,12 @@ void nestedGetTokenInfoAndHardcodedResult() throws Exception { final var contract = testWeb3jService.deploy(NestedCalls::deploy); // When - final var result = contract.call_nestedGetTokenInfoAndHardcodedResult(Address.ZERO.toHexString()) - .send(); + final var function = contract.call_nestedGetTokenInfoAndHardcodedResult(Address.ZERO.toHexString()); + final var result = function.send(); // Then assertThat(result).isEqualTo(EXPECTED_RESULT_NEGATIVE_TESTS); + verifyOpcodeTracerCall(function.encodeFunctionCall(), contract); } @Test @@ -400,12 +401,13 @@ void nestedHtsGetApprovedAndHardcodedResult() throws Exception { final var contract = testWeb3jService.deploy(NestedCalls::deploy); // When - final var result = contract.call_nestedHtsGetApprovedAndHardcodedResult( - Address.ZERO.toHexString(), BigInteger.ONE) - .send(); + final var function = + contract.call_nestedHtsGetApprovedAndHardcodedResult(Address.ZERO.toHexString(), BigInteger.ONE); + final var result = function.send(); // Then assertThat(result).isEqualTo(EXPECTED_RESULT_NEGATIVE_TESTS); + verifyOpcodeTracerCall(function.encodeFunctionCall(), contract); } @Test @@ -414,14 +416,15 @@ void nestedMintTokenAndHardcodedResult() throws Exception { final var contract = testWeb3jService.deploy(NestedCalls::deploy); // When - final var result = contract.call_nestedMintTokenAndHardcodedResult( - Address.ZERO.toHexString(), - BigInteger.ZERO, - List.of(ByteString.copyFromUtf8("firstMeta").toByteArray())) - .send(); + final var function = contract.call_nestedMintTokenAndHardcodedResult( + Address.ZERO.toHexString(), + BigInteger.ZERO, + List.of(ByteString.copyFromUtf8("firstMeta").toByteArray())); + final var result = function.send(); // Then assertThat(result).isEqualTo(EXPECTED_RESULT_NEGATIVE_TESTS); + verifyOpcodeTracerCall(function.encodeFunctionCall(), contract); } private KeyValue getKeyValueForType(final KeyValueType keyValueType, String contractAddress) { diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileTest.java index a9495fc5e64..b0d7034c650 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileTest.java @@ -31,12 +31,8 @@ import static com.hedera.mirror.web3.utils.ContractCallTestUtil.ZERO_VALUE; import static com.hedera.mirror.web3.utils.ContractCallTestUtil.isWithinExpectedGasRange; import static com.hedera.mirror.web3.utils.ContractCallTestUtil.longValueOf; -import static com.hedera.mirror.web3.utils.OpcodeTracerUtil.OPTIONS; -import static com.hedera.mirror.web3.utils.OpcodeTracerUtil.gasComparator; -import static com.hedera.mirror.web3.utils.OpcodeTracerUtil.toHumanReadableMessage; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static org.mockito.Mockito.doAnswer; import com.google.protobuf.InvalidProtocolBufferException; import com.hedera.mirror.common.domain.entity.Entity; @@ -52,13 +48,9 @@ import com.hedera.mirror.common.domain.token.TokenPauseStatusEnum; import com.hedera.mirror.common.domain.token.TokenSupplyTypeEnum; import com.hedera.mirror.common.domain.token.TokenTypeEnum; -import com.hedera.mirror.web3.common.ContractCallContext; -import com.hedera.mirror.web3.convert.BytesDecoder; -import com.hedera.mirror.web3.evm.contracts.execution.OpcodesProcessingResult; import com.hedera.mirror.web3.evm.exception.PrecompileNotSupportedException; import com.hedera.mirror.web3.exception.MirrorEvmTransactionException; import com.hedera.mirror.web3.service.model.CallServiceParameters; -import com.hedera.mirror.web3.service.model.ContractDebugParameters; import com.hedera.mirror.web3.service.model.ContractExecutionParameters; import com.hedera.mirror.web3.utils.ContractFunctionProviderRecord; import com.hedera.mirror.web3.viewmodel.BlockType; @@ -76,7 +68,6 @@ import com.hedera.mirror.web3.web3j.generated.PrecompileTestContract.KeyValue; import com.hedera.mirror.web3.web3j.generated.PrecompileTestContract.NonFungibleTokenInfo; import com.hedera.mirror.web3.web3j.generated.PrecompileTestContract.TokenInfo; -import com.hedera.node.app.service.evm.contracts.execution.HederaEvmTransactionProcessingResult; import com.hedera.node.app.service.evm.store.models.HederaEvmAccount; import com.hedera.services.store.contracts.precompile.codec.KeyValueWrapper.KeyValueType; import com.hedera.services.store.models.Id; @@ -88,49 +79,18 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; import org.apache.commons.lang3.tuple.Pair; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.web3j.protocol.core.RemoteFunctionCall; import org.web3j.tx.Contract; -@RequiredArgsConstructor -class ContractCallServicePrecompileTest extends AbstractContractCallServiceTest { - - private final ContractDebugService contractDebugService; - - @Captor - private ArgumentCaptor paramsCaptor; - - @Captor - private ArgumentCaptor gasCaptor; - - private HederaEvmTransactionProcessingResult resultCaptor; - private ContractCallContext contextCaptor; - - @BeforeEach - void setUpArgumentCaptors() { - doAnswer(invocation -> { - final var transactionProcessingResult = - (HederaEvmTransactionProcessingResult) invocation.callRealMethod(); - resultCaptor = transactionProcessingResult; - contextCaptor = ContractCallContext.get(); - return transactionProcessingResult; - }) - .when(processor) - .execute(paramsCaptor.capture(), gasCaptor.capture()); - } +class ContractCallServicePrecompileTest extends AbstractContractCallServiceOpcodeTracerTest { @Test void unsupportedPrecompileFails() { @@ -2677,70 +2637,4 @@ private PrecompileTestContract.RoyaltyFee getRoyaltyFee( false, getAddressFromEvmAddress(feeCollector.getEvmAddress())); } - - private void verifyOpcodeTracerCall(final String callData, final ContractFunctionProviderRecord functionProvider) { - final var callDataBytes = Bytes.fromHexString(callData); - final var debugParameters = getDebugParameters(functionProvider, callDataBytes); - - if (functionProvider.expectedErrorMessage() != null) { - verifyThrowingOpcodeTracerCall(debugParameters, functionProvider); - } else { - verifySuccessfulOpcodeTracerCall(debugParameters); - } - assertThat(paramsCaptor.getValue()).isEqualTo(debugParameters); - assertThat(gasCaptor.getValue()).isEqualTo(debugParameters.getGas()); - } - - private void verifyOpcodeTracerCall(final String callData, final Contract contract) { - ContractFunctionProviderRecord functionProvider = ContractFunctionProviderRecord.builder() - .contractAddress(Address.fromHexString(contract.getContractAddress())) - .build(); - - final var callDataBytes = Bytes.fromHexString(callData); - final var debugParameters = getDebugParameters(functionProvider, callDataBytes); - - if (functionProvider.expectedErrorMessage() != null) { - verifyThrowingOpcodeTracerCall(debugParameters, functionProvider); - } else { - verifySuccessfulOpcodeTracerCall(debugParameters); - } - assertThat(paramsCaptor.getValue()).isEqualTo(debugParameters); - assertThat(gasCaptor.getValue()).isEqualTo(debugParameters.getGas()); - } - - @SneakyThrows - private void verifyThrowingOpcodeTracerCall( - final ContractDebugParameters params, final ContractFunctionProviderRecord function) { - final var actual = contractDebugService.processOpcodeCall(params, OPTIONS); - assertThat(actual.transactionProcessingResult().isSuccessful()).isFalse(); - assertThat(actual.transactionProcessingResult().getOutput()).isEqualTo(Bytes.EMPTY); - assertThat(actual.transactionProcessingResult()) - .satisfiesAnyOf( - result -> assertThat(result.getRevertReason()) - .isPresent() - .map(BytesDecoder::maybeDecodeSolidityErrorStringToReadableMessage) - .hasValue(function.expectedErrorMessage()), - result -> assertThat(result.getHaltReason()) - .isPresent() - .map(ExceptionalHaltReason::getDescription) - .hasValue(function.expectedErrorMessage())); - assertThat(actual.opcodes().size()).isNotZero(); - assertThat(toHumanReadableMessage(actual.opcodes().getLast().reason())) - .isEqualTo(function.expectedErrorMessage()); - } - - private void verifySuccessfulOpcodeTracerCall(final ContractDebugParameters params) { - final var actual = contractDebugService.processOpcodeCall(params, OPTIONS); - final var expected = new OpcodesProcessingResult(resultCaptor, contextCaptor.getOpcodes()); - // Compare transaction processing result - assertThat(actual.transactionProcessingResult()) - .usingRecursiveComparison() - .ignoringFields("logs") - .isEqualTo(expected.transactionProcessingResult()); - // Compare opcodes with gas tolerance - assertThat(actual.opcodes()) - .usingRecursiveComparison() - .withComparatorForFields(gasComparator(), "gas") - .isEqualTo(expected.opcodes()); - } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractDebugServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractDebugServiceTest.java deleted file mode 100644 index d6ca891a7b9..00000000000 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractDebugServiceTest.java +++ /dev/null @@ -1,491 +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.web3.service; - -import static com.hedera.mirror.web3.evm.utils.EvmTokenUtils.toAddress; -import static com.hedera.mirror.web3.utils.OpcodeTracerUtil.OPTIONS; -import static com.hedera.mirror.web3.utils.OpcodeTracerUtil.gasComparator; -import static com.hedera.mirror.web3.utils.OpcodeTracerUtil.toHumanReadableMessage; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.Mockito.doAnswer; - -import com.google.protobuf.ByteString; -import com.hedera.mirror.common.domain.entity.EntityId; -import com.hedera.mirror.common.domain.token.TokenFreezeStatusEnum; -import com.hedera.mirror.common.domain.token.TokenPauseStatusEnum; -import com.hedera.mirror.web3.common.ContractCallContext; -import com.hedera.mirror.web3.convert.BytesDecoder; -import com.hedera.mirror.web3.evm.contracts.execution.OpcodesProcessingResult; -import com.hedera.mirror.web3.service.model.ContractDebugParameters; -import com.hedera.mirror.web3.utils.ContractFunctionProviderEnum; -import com.hedera.mirror.web3.viewmodel.BlockType; -import com.hedera.node.app.service.evm.contracts.execution.HederaEvmTransactionProcessingResult; -import java.math.BigInteger; -import java.util.List; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import org.apache.tuweni.bytes.Bytes; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; - -@RequiredArgsConstructor -class ContractDebugServiceTest extends ContractCallTestSetup { - - private static final Long DEFAULT_CALL_VALUE = 0L; - private final ContractDebugService contractDebugService; - - @Captor - private ArgumentCaptor paramsCaptor; - - @Captor - private ArgumentCaptor gasCaptor; - - private HederaEvmTransactionProcessingResult resultCaptor; - private ContractCallContext contextCaptor; - private EntityId ownerEntityId; - private EntityId senderEntityId; - private EntityId treasuryEntityId; - private EntityId spenderEntityId; - - @BeforeEach - void setUpEntities() { - // Obligatory data - genesisBlockPersist(); - historicalBlocksPersist(); - historicalDataPersist(); - precompileContractPersist(); - // Account entities - receiverPersist(); - notAssociatedSpenderEntityPersist(); - ownerEntityId = ownerEntityPersist(); - senderEntityId = senderEntityPersist(); - treasuryEntityId = treasureEntityPersist(); - spenderEntityId = spenderEntityPersist(); - } - - @BeforeEach - void setUpArgumentCaptors() { - doAnswer(invocation -> { - final var transactionProcessingResult = - (HederaEvmTransactionProcessingResult) invocation.callRealMethod(); - resultCaptor = transactionProcessingResult; - contextCaptor = ContractCallContext.get(); - return transactionProcessingResult; - }) - .when(processor) - .execute(paramsCaptor.capture(), gasCaptor.capture()); - } - - @ParameterizedTest - @EnumSource(NestedEthCallContractFunctionsNegativeCases.class) - void failedNestedCallWithHardcodedResult(final ContractFunctionProviderEnum function) { - nestedEthCallsContractPersist(); - final var params = serviceParametersForDebug( - function, - NESTED_CALLS_ABI_PATH, - NESTED_ETH_CALLS_CONTRACT_ADDRESS, - DEFAULT_CALL_VALUE, - domainBuilder.timestamp()); - verifyOpcodeTracerCall(params, function); - } - - @ParameterizedTest - @EnumSource(DynamicCallsContractFunctions.class) - void evmDynamicCallsTokenFunctions(final ContractFunctionProviderEnum function) { - setUpDynamicCallsContractEntities(); - final var params = serviceParametersForDebug( - function, - DYNAMIC_ETH_CALLS_ABI_PATH, - DYNAMIC_ETH_CALLS_CONTRACT_ALIAS, - DEFAULT_CALL_VALUE, - domainBuilder.timestamp()); - verifyOpcodeTracerCall(params, function); - } - - private void verifyOpcodeTracerCall( - final ContractDebugParameters params, final ContractFunctionProviderEnum function) { - if (function.getExpectedErrorMessage() != null) { - verifyThrowingOpcodeTracerCall(params, function); - } else { - verifySuccessfulOpcodeTracerCall(params, function); - } - assertThat(paramsCaptor.getValue()).isEqualTo(params); - assertThat(gasCaptor.getValue()).isEqualTo(params.getGas()); - } - - @SneakyThrows - private void verifyThrowingOpcodeTracerCall( - final ContractDebugParameters params, final ContractFunctionProviderEnum function) { - final var actual = contractDebugService.processOpcodeCall(params, OPTIONS); - assertThat(actual.transactionProcessingResult().isSuccessful()).isFalse(); - assertThat(actual.transactionProcessingResult().getOutput()).isEqualTo(Bytes.EMPTY); - assertThat(actual.transactionProcessingResult()) - .satisfiesAnyOf( - result -> assertThat(result.getRevertReason()) - .isPresent() - .map(BytesDecoder::maybeDecodeSolidityErrorStringToReadableMessage) - .hasValue(function.getExpectedErrorMessage()), - result -> assertThat(result.getHaltReason()) - .isPresent() - .map(ExceptionalHaltReason::getDescription) - .hasValue(function.getExpectedErrorMessage())); - assertThat(actual.opcodes().size()).isNotZero(); - assertThat(toHumanReadableMessage(actual.opcodes().getLast().reason())) - .isEqualTo(function.getExpectedErrorMessage()); - } - - private void verifySuccessfulOpcodeTracerCall( - final ContractDebugParameters params, final ContractFunctionProviderEnum function) { - final var actual = contractDebugService.processOpcodeCall(params, OPTIONS); - final var expected = new OpcodesProcessingResult(resultCaptor, contextCaptor.getOpcodes()); - - if (function.getExpectedResultFields() != null) { - assertThat(actual.transactionProcessingResult().getOutput().toHexString()) - .isEqualTo(functionEncodeDecoder.encodedResultFor( - function.getName(), NESTED_CALLS_ABI_PATH, function.getExpectedResultFields())); - } - - // Compare transaction processing result - assertThat(actual.transactionProcessingResult()) - .usingRecursiveComparison() - .ignoringFields("logs") - .isEqualTo(expected.transactionProcessingResult()); - // Compare opcodes with gas tolerance - assertThat(actual.opcodes()) - .usingRecursiveComparison() - .withComparatorForFields(gasComparator(), "gas") - .isEqualTo(expected.opcodes()); - } - - private void setUpDynamicCallsContractEntities() { - final var dynamicCallsContractId = dynamicEthCallContractPresist(); - commonTokensPersist(dynamicCallsContractId, DYNAMIC_ETH_CALLS_CONTRACT_ADDRESS); - final var nftWithoutKycKeyEntityId = nftPersistWithoutKycKey( - NFT_TRANSFER_ADDRESS_WITHOUT_KYC_KEY, - AUTO_RENEW_ACCOUNT_ADDRESS, - dynamicCallsContractId, - spenderEntityId, - dynamicCallsContractId, - KEY_PROTO, - TokenPauseStatusEnum.UNPAUSED, - false); - tokenAccountsPersist(dynamicCallsContractId, List.of(nftWithoutKycKeyEntityId)); - } - - private void commonTokensPersist(EntityId contractId, Address contractAddress) { - final var tokenId = fungibleTokenPersist( - ownerEntityId, - KEY_PROTO, - FUNGIBLE_TOKEN_ADDRESS, - AUTO_RENEW_ACCOUNT_ADDRESS, - 9999999999999L, - TokenPauseStatusEnum.PAUSED, - true); - final var treasuryTokenId = fungibleTokenPersist( - treasuryEntityId, - new byte[0], - TREASURY_TOKEN_ADDRESS, - AUTO_RENEW_ACCOUNT_ADDRESS, - 9999999999999L, - TokenPauseStatusEnum.UNPAUSED, - false); - final var transferFromTokenId = fungibleTokenPersist( - treasuryEntityId, - new byte[0], - TRANSFRER_FROM_TOKEN_ADDRESS, - AUTO_RENEW_ACCOUNT_ADDRESS, - 9999999999999L, - TokenPauseStatusEnum.UNPAUSED, - false); - fungibleTokenPersist( - treasuryEntityId, - KEY_PROTO, - NOT_FROZEN_FUNGIBLE_TOKEN_ADDRESS, - AUTO_RENEW_ACCOUNT_ADDRESS, - 0L, - TokenPauseStatusEnum.PAUSED, - false); - final var nftEntityId = nftPersist( - NFT_ADDRESS, - AUTO_RENEW_ACCOUNT_ADDRESS, - ownerEntityId, - spenderEntityId, - ownerEntityId, - KEY_PROTO, - TokenPauseStatusEnum.PAUSED, - true); - final var nftTransferEntityId = nftPersist( - NFT_TRANSFER_ADDRESS, - AUTO_RENEW_ACCOUNT_ADDRESS, - ownerEntityId, - spenderEntityId, - ownerEntityId, - KEY_PROTO, - TokenPauseStatusEnum.UNPAUSED, - false); - nftPersist( - NFT_ADDRESS_WITH_DIFFERENT_OWNER_AND_TREASURY, - AUTO_RENEW_ACCOUNT_ADDRESS, - senderEntityId, - spenderEntityId, - ownerEntityId, - KEY_PROTO, - TokenPauseStatusEnum.UNPAUSED, - false); - allowancesPersist(ownerEntityId, contractId, tokenId, nftEntityId); - allowancesPersist(senderEntityId, contractId, transferFromTokenId, nftEntityId); - contractAllowancesPersist(senderEntityId, contractAddress, treasuryTokenId, nftTransferEntityId); - tokenAccountsPersist( - contractId, List.of(tokenId, treasuryTokenId, transferFromTokenId, nftEntityId, nftTransferEntityId)); - } - - private void tokenAccountsPersist(EntityId contractId, List tokenIds) { - for (EntityId tokenId : tokenIds) { - tokenAccountPersist(contractId, tokenId, TokenFreezeStatusEnum.UNFROZEN); - } - } - - @Getter - @RequiredArgsConstructor - private enum DynamicCallsContractFunctions implements ContractFunctionProviderEnum { - MINT_FUNGIBLE_TOKEN( - "mintTokenGetTotalSupplyAndBalanceOfTreasury", - new Object[] {NOT_FROZEN_FUNGIBLE_TOKEN_ADDRESS, 100L, new byte[0][0], TREASURY_ADDRESS}, - null), - MINT_NFT( - "mintTokenGetTotalSupplyAndBalanceOfTreasury", - new Object[] { - NFT_ADDRESS, - 0L, - new byte[][] {ByteString.copyFromUtf8("firstMeta").toByteArray()}, - OWNER_ADDRESS - }, - null), - BURN_FUNGIBLE_TOKEN( - "burnTokenGetTotalSupplyAndBalanceOfTreasury", - new Object[] {NOT_FROZEN_FUNGIBLE_TOKEN_ADDRESS, 12L, new long[0], TREASURY_ADDRESS}, - null), - BURN_NFT( - "burnTokenGetTotalSupplyAndBalanceOfTreasury", - new Object[] {NFT_ADDRESS, 0L, new long[] {1L}, OWNER_ADDRESS}, - null), - WIPE_FUNGIBLE_TOKEN( - "wipeTokenGetTotalSupplyAndBalanceOfTreasury", - new Object[] {NOT_FROZEN_FUNGIBLE_TOKEN_ADDRESS, 10L, new long[0], SENDER_ALIAS}, - null), - WIPE_NFT( - "wipeTokenGetTotalSupplyAndBalanceOfTreasury", - new Object[] {NFT_ADDRESS_WITH_DIFFERENT_OWNER_AND_TREASURY, 0L, new long[] {1L}, SENDER_ALIAS}, - null), - PAUSE_UNPAUSE_FUNGIBLE_TOKEN( - "pauseTokenGetPauseStatusUnpauseGetPauseStatus", new Object[] {FUNGIBLE_TOKEN_ADDRESS}, null), - FREEZE_UNFREEZE_FUNGIBLE_TOKEN( - "freezeTokenGetPauseStatusUnpauseGetPauseStatus", - new Object[] {NOT_FROZEN_FUNGIBLE_TOKEN_ADDRESS, SPENDER_ALIAS}, - null), - PAUSE_UNPAUSE_NFT("pauseTokenGetPauseStatusUnpauseGetPauseStatus", new Object[] {NFT_ADDRESS}, null), - FREEZE_UNFREEZE_NFT( - "freezeTokenGetPauseStatusUnpauseGetPauseStatus", new Object[] {NFT_ADDRESS, SPENDER_ALIAS}, null), - ASSOCIATE_TRANSFER_NFT( - "associateTokenTransfer", - new Object[] { - NFT_TRANSFER_ADDRESS_WITHOUT_KYC_KEY, - DYNAMIC_ETH_CALLS_CONTRACT_ALIAS, - NOT_ASSOCIATED_SPENDER_ALIAS, - BigInteger.ZERO, - BigInteger.ONE - }, - null), - ASSOCIATE_TRANSFER_FUNGIBLE_TOKEN( - "associateTokenTransfer", - new Object[] { - TREASURY_TOKEN_ADDRESS, - DYNAMIC_ETH_CALLS_CONTRACT_ALIAS, - NOT_ASSOCIATED_SPENDER_ALIAS, - BigInteger.ONE, - BigInteger.ZERO - }, - null), - ASSOCIATE_DISSOCIATE_TRANSFER_FUNGIBLE_TOKEN_FAIL( - "associateTokenDissociateFailTransfer", - new Object[] { - TREASURY_TOKEN_ADDRESS, - NOT_ASSOCIATED_SPENDER_ALIAS, - DYNAMIC_ETH_CALLS_CONTRACT_ALIAS, - BigInteger.ONE, - BigInteger.ZERO - }, - "IERC20: failed to transfer"), - ASSOCIATE_DISSOCIATE_TRANSFER_NFT_FAIL( - "associateTokenDissociateFailTransfer", - new Object[] {NFT_TRANSFER_ADDRESS, SENDER_ALIAS, RECEIVER_ADDRESS, BigInteger.ZERO, BigInteger.ONE}, - "IERC721: failed to transfer"), - ASSOCIATE_TRANSFER_NFT_EXCEPTION( - "associateTokenTransfer", - new Object[] { - toAddress(1), // Not persisted address - DYNAMIC_ETH_CALLS_CONTRACT_ALIAS, - NOT_ASSOCIATED_SPENDER_ALIAS, - BigInteger.ZERO, - BigInteger.ONE - }, - "Failed to associate tokens"), - APPROVE_FUNGIBLE_TOKEN_GET_ALLOWANCE( - "approveTokenGetAllowance", - new Object[] {FUNGIBLE_TOKEN_ADDRESS, OWNER_ADDRESS, BigInteger.ONE, BigInteger.ZERO}, - null), - APPROVE_NFT_GET_ALLOWANCE( - "approveTokenGetAllowance", - new Object[] {NFT_ADDRESS, SPENDER_ALIAS, BigInteger.ZERO, BigInteger.ONE}, - null), - APPROVE_FUNGIBLE_TOKEN_TRANSFER_FROM_GET_ALLOWANCE( - "approveTokenTransferFromGetAllowanceGetBalance", - new Object[] {TREASURY_TOKEN_ADDRESS, SPENDER_ALIAS, BigInteger.ONE, BigInteger.ZERO}, - null), - APPROVE_FUNGIBLE_TOKEN_TRANSFER_FROM_GET_ALLOWANCE_2( - "approveTokenTransferFromGetAllowanceGetBalance", - new Object[] {TREASURY_TOKEN_ADDRESS, SENDER_ALIAS, BigInteger.ONE, BigInteger.ZERO}, - null), - APPROVE_NFT_TOKEN_TRANSFER_FROM_GET_ALLOWANCE( - "approveTokenTransferFromGetAllowanceGetBalance", - new Object[] {NFT_TRANSFER_ADDRESS_WITHOUT_KYC_KEY, SPENDER_ALIAS, BigInteger.ZERO, BigInteger.ONE}, - null), - APPROVE_NFT_TOKEN_TRANSFER_FROM_GET_ALLOWANCE_2( - "approveTokenTransferFromGetAllowanceGetBalance", - new Object[] {NFT_TRANSFER_ADDRESS_WITHOUT_KYC_KEY, SENDER_ALIAS, BigInteger.ZERO, BigInteger.ONE}, - null), - APPROVE_FUNGIBLE_TOKEN_TRANSFER_GET_ALLOWANCE( - "approveTokenTransferGetAllowanceGetBalance", - new Object[] {TREASURY_TOKEN_ADDRESS, SPENDER_ALIAS, BigInteger.ONE, BigInteger.ZERO}, - null), - APPROVE_NFT_TRANSFER_GET_ALLOWANCE( - "approveTokenTransferGetAllowanceGetBalance", - new Object[] {NFT_TRANSFER_ADDRESS, SPENDER_ALIAS, BigInteger.ZERO, BigInteger.ONE}, - null), - APPROVE_CRYPTO_TRANSFER_FUNGIBLE_GET_ALLOWANCE( - "approveTokenCryptoTransferGetAllowanceGetBalance", - new Object[] { - new Object[] {}, - new Object[] {TREASURY_TOKEN_ADDRESS, DYNAMIC_ETH_CALLS_CONTRACT_ALIAS, SPENDER_ALIAS, 1L, false} - }, - null), - APPROVE_CRYPTO_TRANSFER_NFT_GET_ALLOWANCE( - "approveTokenCryptoTransferGetAllowanceGetBalance", - new Object[] { - new Object[] {}, - new Object[] {NFT_TRANSFER_ADDRESS, DYNAMIC_ETH_CALLS_CONTRACT_ALIAS, SPENDER_ALIAS, 1L, true} - }, - null), - APPROVE_FOR_ALL_TRANSFER_FROM_NFT_GET_ALLOWANCE( - "approveForAllTokenTransferFromGetAllowance", - new Object[] {NFT_TRANSFER_ADDRESS, SPENDER_ALIAS, 1L}, - null), - APPROVE_FOR_ALL_TRANSFER_NFT_GET_ALLOWANCE( - "approveForAllTokenTransferGetAllowance", new Object[] {NFT_TRANSFER_ADDRESS, SPENDER_ALIAS, 1L}, null), - APPROVE_FOR_ALL_CRYPTO_TRANSFER_NFT_GET_ALLOWANCE( - "approveForAllCryptoTransferGetAllowance", - new Object[] { - new Object[] {}, - new Object[] {NFT_TRANSFER_ADDRESS, DYNAMIC_ETH_CALLS_CONTRACT_ALIAS, SPENDER_ALIAS, 1L, true} - }, - null), - TRANSFER_NFT_GET_ALLOWANCE_OWNER_OF( - "transferFromNFTGetAllowance", new Object[] {NFT_TRANSFER_ADDRESS, 1L}, null), - TRANSFER_FUNGIBLE_TOKEN_GET_BALANCE( - "transferFromGetAllowanceGetBalance", - new Object[] {TREASURY_TOKEN_ADDRESS, SPENDER_ALIAS, BigInteger.ONE, BigInteger.ZERO}, - null), - TRANSFER_NFT_GET_OWNER( - "transferFromGetAllowanceGetBalance", - new Object[] {NFT_TRANSFER_ADDRESS, SPENDER_ALIAS, BigInteger.ZERO, BigInteger.ONE}, - null), - CRYPTO_TRANSFER_FUNFIBLE_TOKEN_GET_OWNER( - "cryptoTransferFromGetAllowanceGetBalance", - new Object[] { - new Object[] {}, - new Object[] {TREASURY_TOKEN_ADDRESS, DYNAMIC_ETH_CALLS_CONTRACT_ALIAS, SPENDER_ALIAS, 1L, false} - }, - null), - CRYPTO_TRANSFER_NFT_GET_OWNER( - "cryptoTransferFromGetAllowanceGetBalance", - new Object[] { - new Object[] {}, - new Object[] {NFT_TRANSFER_ADDRESS, DYNAMIC_ETH_CALLS_CONTRACT_ALIAS, SPENDER_ALIAS, 1L, true} - }, - null), - GRANT_KYC_REVOKE_KYC_FUNGIBLE("grantKycRevokeKyc", new Object[] {FUNGIBLE_TOKEN_ADDRESS, SENDER_ALIAS}, null), - GRANT_KYC_REVOKE_KYC_NFT("grantKycRevokeKyc", new Object[] {NFT_ADDRESS, SENDER_ALIAS}, null), - ADDRESS_THIS("getAddressThis", null, null); - private final String name; - private final Object[] functionParameters; - private final String expectedErrorMessage; - } - - @Getter - @RequiredArgsConstructor - private enum NestedEthCallContractFunctionsNegativeCases implements ContractFunctionProviderEnum { - GET_TOKEN_INFO_HISTORICAL( - "nestedGetTokenInfoAndHardcodedResult", - new Object[] {NFT_ADDRESS_HISTORICAL}, - new Object[] {"hardcodedResult"}, - BlockType.of(String.valueOf(EVM_V_34_BLOCK - 1))), - GET_TOKEN_INFO( - "nestedGetTokenInfoAndHardcodedResult", - new Object[] {Address.ZERO}, - new Object[] {"hardcodedResult"}, - BlockType.LATEST), - HTS_GET_APPROVED_HISTORICAL( - "nestedHtsGetApprovedAndHardcodedResult", - new Object[] {NFT_ADDRESS_HISTORICAL, 1L}, - new Object[] {"hardcodedResult"}, - BlockType.of(String.valueOf(EVM_V_34_BLOCK - 1))), - HTS_GET_APPROVED( - "nestedHtsGetApprovedAndHardcodedResult", - new Object[] {Address.ZERO, 1L}, - new Object[] {"hardcodedResult"}, - BlockType.LATEST), - MINT_TOKEN_HISTORICAL( - "nestedMintTokenAndHardcodedResult", - new Object[] { - NFT_ADDRESS_HISTORICAL, - 0L, - new byte[][] {ByteString.copyFromUtf8("firstMeta").toByteArray()} - }, - new Object[] {"hardcodedResult"}, - BlockType.of(String.valueOf(EVM_V_34_BLOCK - 1))), - MINT_TOKEN( - "nestedMintTokenAndHardcodedResult", - new Object[] { - Address.ZERO, - 0L, - new byte[][] {ByteString.copyFromUtf8("firstMeta").toByteArray()} - }, - new Object[] {"hardcodedResult"}, - BlockType.LATEST); - - private final String name; - private final Object[] functionParameters; - private final Object[] expectedResultFields; - private final BlockType block; - } -}