From 7a1c83902cfc409c30b14f700ffeadb5a87bdda8 Mon Sep 17 00:00:00 2001 From: sdimitrov9 Date: Wed, 19 Feb 2025 17:03:14 +0200 Subject: [PATCH 1/3] Fix tests in ContractcallSeviceTest class Signed-off-by: sdimitrov9 --- .../web3/service/ContractCallServiceTest.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java index 5c051fed67..3626738a11 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java @@ -567,9 +567,12 @@ void ethCallWithValueAndNotExistingSenderAlias() { // Given final var receiverEntity = accountPersist(); final var receiverAddress = getAliasAddressFromEntity(receiverEntity); - final var notExistingSenderAlias = Address.fromHexString("0x6b175474e89094c44da98b954eedeac495271d0f"); - final var serviceParameters = - getContractExecutionParametersWithValue(Bytes.EMPTY, notExistingSenderAlias, receiverAddress, 10L); + final var payer = accountEntityPersist(); + + assertThat(payer.getAlias()).isEqualTo(null); + + final var serviceParameters = getContractExecutionParametersWithValue( + Bytes.EMPTY, toAddress(payer.toEntityId()), receiverAddress, 10L); // When final var result = contractExecutionService.processCall(serviceParameters); @@ -1118,7 +1121,8 @@ protected Address getAliasAddressFromEntity(final Entity entity) { @Nested class EVM46Validation { - private static final Address NON_EXISTING_ADDRESS = toAddress(123456789); + private static final Address NON_EXISTING_ADDRESS = + Address.fromHexString("0xA7D9ddBE1f17865597fbd27EC712455208B6B76D"); @Test void callToNonExistingContract() { @@ -1136,8 +1140,12 @@ void callToNonExistingContract() { @Test void transferToNonExistingContract() { // Given - final var serviceParameters = - getContractExecutionParametersWithValue(Bytes.EMPTY, NON_EXISTING_ADDRESS, 1L); + final var payer = accountEntityWithEvmAddressPersist(); + + // The NON_EXISTING_ADDRESS should be a valid EVM alias key(Ethereum-style address derived from an ECDSA + // public key), otherwise INVALID_ALIAS_KEY could be thrown + final var serviceParameters = getContractExecutionParametersWithValue( + Bytes.EMPTY, getAliasAddressFromEntity(payer), NON_EXISTING_ADDRESS, 1L); // When final var result = contractExecutionService.processCall(serviceParameters); From eb6a04a767f660e6cf46d6ba09261a551ac828d8 Mon Sep 17 00:00:00 2001 From: sdimitrov9 Date: Thu, 20 Feb 2025 18:07:14 +0200 Subject: [PATCH 2/3] Fix estimateGasWithoutReceiver test Signed-off-by: sdimitrov9 --- .../AbstractContractCallServiceTest.java | 16 +++++++++++++ .../web3/service/ContractCallServiceTest.java | 23 +++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java index 564e6780ef..b3ab9eb67a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java @@ -16,6 +16,7 @@ package com.hedera.mirror.web3.service; +import static com.hedera.mirror.web3.service.model.CallServiceParameters.CallType.ETH_ESTIMATE_GAS; import static com.hedera.mirror.web3.utils.ContractCallTestUtil.ESTIMATE_GAS_ERROR_MESSAGE; import static com.hedera.mirror.web3.utils.ContractCallTestUtil.TRANSACTION_GAS_LIMIT; import static com.hedera.mirror.web3.utils.ContractCallTestUtil.isWithinExpectedGasRange; @@ -225,6 +226,21 @@ protected ContractExecutionParameters getContractExecutionParameters( .build(); } + protected ContractExecutionParameters getContractExecutionParams( + final Bytes data, final Address senderAddress, final Address receiverAddress, final CallType callType) { + return ContractExecutionParameters.builder() + .block(BlockType.LATEST) + .callData(data) + .callType(callType) + .gas(TRANSACTION_GAS_LIMIT) + .isEstimate(callType == ETH_ESTIMATE_GAS) + .isStatic(false) + .receiver(receiverAddress) + .sender(new HederaEvmAccount(senderAddress)) + .value(0L) + .build(); + } + protected ContractExecutionParameters getContractExecutionParameters( final RemoteFunctionCall functionCall, final Contract contract, diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java index 3626738a11..beacae7b40 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java @@ -339,14 +339,29 @@ void estimateGasForPureCall() { verifyEthCallAndEstimateGas(functionCall, contract); } + /** + * If the callContract method is invoked with the zero address (0x0) as the recipient, + * Hedera treats this as a contract creation request rather than a function call. + * The contract is then initialized using the contractCallData, which should contain the compiled bytecode of the + * contract.If contractCallData is empty (0x0), the contract will be deployed without any functions(no fallback + * function as well, which is called when the contract is called without specifying a function or when non-existent + * function is specified.) in its bytecode. Respectively, any call to the contract will fail with + * CONTRACT_BYTECODE_EMPTY, indicating that the contract exists, but does not have any executable logic. + */ @Test void estimateGasWithoutReceiver() { - // Given + final var payer = accountEntityPersist(); + + final Address receiverAddress = Address.ZERO; + // The compiled EVM bytecode of a smart contract with a fallback function() + final Bytes contractCallData = Bytes.fromHexString( + "608060405234801561001057600080fd5b50610104806100206000396000f3fe6080604052600436106100295760003560e01c80632d353dd21461002e578063b69ef8a81461004e575b600080fd5b610048600480360381019061004391906100d5565b61006a565b005b610056610078565b60405161006391906100f8565b60405180910390f35b60008054905090565b60008135905061008a8161011a565b92915050565b6000602082840312156100a657600080fd5b60006100b484828501610080565b91505092915050565b6100c7816100de565b82525050565b60006020820190506100e260008301846100be565b92915050565b600080fd5b6100f5816100de565b811461010057600080fd5b5056fea2646970667358221220e429de601e30eb0481c0e6bc72a9f9f8e5b2e720a097394d319c2b8a55ffb2d164736f6c63430008140033"); final var serviceParametersEthCall = - getContractExecutionParameters(Bytes.fromHexString(HEX_PREFIX), Address.ZERO, ETH_CALL); + getContractExecutionParams(contractCallData, receiverAddress, toAddress(payer.toEntityId()), ETH_CALL); + final var actualGasUsed = gasUsedAfterExecution(serviceParametersEthCall); - final var serviceParametersEstimateGas = - getContractExecutionParameters(Bytes.fromHexString(HEX_PREFIX), Address.ZERO, ETH_ESTIMATE_GAS); + final var serviceParametersEstimateGas = getContractExecutionParams( + contractCallData, receiverAddress, toAddress(payer.toEntityId()), ETH_ESTIMATE_GAS); // When final var result = contractExecutionService.processCall(serviceParametersEstimateGas); From 4cdf90eb4c29ab2b101a666f4aac79404b25d5de Mon Sep 17 00:00:00 2001 From: sdimitrov9 Date: Fri, 21 Feb 2025 14:14:12 +0200 Subject: [PATCH 3/3] Add new ContractCallService test Signed-off-by: sdimitrov9 --- .../AbstractContractCallServiceTest.java | 23 +++----- .../web3/service/ContractCallServiceTest.java | 58 +++++++++++-------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java index bec1fe58af..c840cee80c 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java @@ -16,6 +16,7 @@ package com.hedera.mirror.web3.service; +import static com.hedera.mirror.web3.service.model.CallServiceParameters.CallType.ETH_CALL; import static com.hedera.mirror.web3.service.model.CallServiceParameters.CallType.ETH_ESTIMATE_GAS; import static com.hedera.mirror.web3.utils.ContractCallTestUtil.ESTIMATE_GAS_ERROR_MESSAGE; import static com.hedera.mirror.web3.utils.ContractCallTestUtil.TRANSACTION_GAS_LIMIT; @@ -212,21 +213,15 @@ protected ContractExecutionParameters getContractExecutionParameters( protected ContractExecutionParameters getContractExecutionParameters( final Bytes data, final Address receiver, final Address payerAddress, final long value) { - return ContractExecutionParameters.builder() - .block(BlockType.LATEST) - .callData(data) - .callType(CallType.ETH_CALL) - .gas(TRANSACTION_GAS_LIMIT) - .isEstimate(false) - .isStatic(false) - .receiver(receiver) - .sender(new HederaEvmAccount(payerAddress)) - .value(value) - .build(); + return getContractExecutionParameters(data, receiver, payerAddress, value, ETH_CALL); } - protected ContractExecutionParameters getContractExecutionParams( - final Bytes data, final Address senderAddress, final Address receiverAddress, final CallType callType) { + protected ContractExecutionParameters getContractExecutionParameters( + final Bytes data, + final Address receiverAddress, + final Address senderAddress, + final long value, + final CallType callType) { return ContractExecutionParameters.builder() .block(BlockType.LATEST) .callData(data) @@ -236,7 +231,7 @@ protected ContractExecutionParameters getContractExecutionParams( .isStatic(false) .receiver(receiverAddress) .sender(new HederaEvmAccount(senderAddress)) - .value(0L) + .value(value) .build(); } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java index 068c128719..3bba7e9e4a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java @@ -16,6 +16,7 @@ package com.hedera.mirror.web3.service; +import static com.hedera.hapi.node.base.ResponseCodeEnum.PAYER_ACCOUNT_NOT_FOUND; import static com.hedera.mirror.common.util.DomainUtils.toEvmAddress; import static com.hedera.mirror.web3.evm.utils.EvmTokenUtils.toAddress; import static com.hedera.mirror.web3.exception.BlockNumberNotFoundException.UNKNOWN_BLOCK_NUMBER; @@ -338,10 +339,10 @@ void estimateGasForPureCall() { } /** - * If the callContract method is invoked with the zero address (0x0) as the recipient, + * If we make a contract call with the zero address (0x0) set as the recipient in the contractExecutionParameters, * Hedera treats this as a contract creation request rather than a function call. * The contract is then initialized using the contractCallData, which should contain the compiled bytecode of the - * contract.If contractCallData is empty (0x0), the contract will be deployed without any functions(no fallback + * contract. If contractCallData is empty (0x0), the contract will be deployed without any functions(no fallback * function as well, which is called when the contract is called without specifying a function or when non-existent * function is specified.) in its bytecode. Respectively, any call to the contract will fail with * CONTRACT_BYTECODE_EMPTY, indicating that the contract exists, but does not have any executable logic. @@ -349,17 +350,17 @@ void estimateGasForPureCall() { @Test void estimateGasWithoutReceiver() { final var payer = accountEntityPersist(); + final var receiverAddress = Address.ZERO; - final Address receiverAddress = Address.ZERO; - // The compiled EVM bytecode of a smart contract with a fallback function() - final Bytes contractCallData = Bytes.fromHexString( - "608060405234801561001057600080fd5b50610104806100206000396000f3fe6080604052600436106100295760003560e01c80632d353dd21461002e578063b69ef8a81461004e575b600080fd5b610048600480360381019061004391906100d5565b61006a565b005b610056610078565b60405161006391906100f8565b60405180910390f35b60008054905090565b60008135905061008a8161011a565b92915050565b6000602082840312156100a657600080fd5b60006100b484828501610080565b91505092915050565b6100c7816100de565b82525050565b60006020820190506100e260008301846100be565b92915050565b600080fd5b6100f5816100de565b811461010057600080fd5b5056fea2646970667358221220e429de601e30eb0481c0e6bc72a9f9f8e5b2e720a097394d319c2b8a55ffb2d164736f6c63430008140033"); - final var serviceParametersEthCall = - getContractExecutionParams(contractCallData, receiverAddress, toAddress(payer.toEntityId()), ETH_CALL); + final var contract = testWeb3jService.deployWithoutPersist(ERCTestContract::deploy); + final var contractCallData = Bytes.fromHexString(contract.getContractBinary()); + + final var serviceParametersEthCall = getContractExecutionParameters( + contractCallData, toAddress(payer.toEntityId()), receiverAddress, 0L, ETH_CALL); final var actualGasUsed = gasUsedAfterExecution(serviceParametersEthCall); - final var serviceParametersEstimateGas = getContractExecutionParams( - contractCallData, receiverAddress, toAddress(payer.toEntityId()), ETH_ESTIMATE_GAS); + final var serviceParametersEstimateGas = getContractExecutionParameters( + contractCallData, toAddress(payer.toEntityId()), receiverAddress, 0L, ETH_ESTIMATE_GAS); // When final var result = contractExecutionService.processCall(serviceParametersEstimateGas); @@ -576,13 +577,11 @@ void nonExistingFunctionCallWithFallback() { } @Test - void ethCallWithValueAndNotExistingSenderAlias() { + void ethCallWithValueAndSenderWithoutAlias() { // Given - final var receiverEntity = accountPersist(); + final var receiverEntity = accountEntityWithEvmAddressPersist(); final var receiverAddress = getAliasAddressFromEntity(receiverEntity); - final var payer = accountEntityPersist(); - - assertThat(payer.getAlias()).isEqualTo(null); + final var payer = accountEntityPersist(); // Account without alias final var serviceParameters = getContractExecutionParametersWithValue( Bytes.EMPTY, toAddress(payer.toEntityId()), receiverAddress, 10L); @@ -595,6 +594,27 @@ void ethCallWithValueAndNotExistingSenderAlias() { assertGasLimit(serviceParameters); } + @Test + void ethCallWithValueAndNotExistingSenderAddress() { + final var receiverEntity = accountEntityWithEvmAddressPersist(); + final var receiverAddress = getAliasAddressFromEntity(receiverEntity); + final var notExistingAccountAddress = toAddress(EntityId.of(4325)); + + final var serviceParameters = + getContractExecutionParametersWithValue(Bytes.EMPTY, notExistingAccountAddress, receiverAddress, 10L); + + if (mirrorNodeEvmProperties.isModularizedServices()) { + assertThatThrownBy(() -> contractExecutionService.processCall(serviceParameters)) + .isInstanceOf(MirrorEvmTransactionException.class) + .hasMessage(PAYER_ACCOUNT_NOT_FOUND.name()); + } else { + final var result = contractExecutionService.processCall(serviceParameters); + assertThat(result).isEqualTo(HEX_PREFIX); + } + + assertGasLimit(serviceParameters); + } + @Test void invalidFunctionSig() { // Given @@ -1103,10 +1123,6 @@ private ContractExecutionParameters getContractExecutionParametersWithValue( .build(); } - private Entity accountPersist() { - return domainBuilder.entity().persist(); - } - private Entity systemAccountEntityWithEvmAddressPersist() { final var systemAccountEntityId = EntityId.of(700); @@ -1119,10 +1135,6 @@ private Entity systemAccountEntityWithEvmAddressPersist() { .persist(); } - protected Address getAliasAddressFromEntity(final Entity entity) { - return Address.wrap(Bytes.wrap(entity.getEvmAddress())); - } - @Nested class EVM46Validation {