diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/utils/BytecodeUtils.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/utils/BytecodeUtils.java
new file mode 100644
index 00000000000..701472f62c4
--- /dev/null
+++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/utils/BytecodeUtils.java
@@ -0,0 +1,119 @@
+/*
+ * 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.utils;
+
+import static com.hedera.mirror.web3.validation.HexValidator.HEX_PREFIX;
+
+import jakarta.annotation.Nonnull;
+import java.util.regex.Pattern;
+import lombok.experimental.UtilityClass;
+
+/**
+ * A utility class for extracting runtime bytecode from init bytecode of a smart contract.
+ *
+ * Smart contracts have init bytecode (constructor bytecode) and runtime bytecode (the code executed when the contract
+ * is called). This class helps in extracting the runtime bytecode from the given init bytecode by searching for
+ * specific patterns.
+ *
+ */
+@UtilityClass
+public class BytecodeUtils {
+
+ public static final String SKIP_INIT_CODE_CHECK = "HEDERA_MIRROR_WEB3_EVM_SKIPINITCODECHECK";
+ private static final String CODECOPY = "39";
+ private static final String RETURN = "f3";
+ private static final long MINIMUM_INIT_CODE_SIZE = 14L;
+ private static final String FREE_MEMORY_POINTER = "60806040";
+ private static final String FREE_MEMORY_POINTER_2 = "60606040";
+ private static final String RUNTIME_CODE_PREFIX =
+ "6080"; // The pattern to find the start of the runtime code in the init bytecode
+
+ /**
+ * Compiled regex pattern to match the init bytecode sequence. The pattern checks for a sequence of a free memory
+ * pointer setup, a CODECOPY operation, and a RETURN operation, in that order. The sequence is matched
+ * case-insensitively to account for hexadecimal representations.
+ *
+ * Pattern explanation: - (%s|%s) matches either FREE_MEMORY_POINTER or FREE_MEMORY_POINTER_2, which represent setup
+ * instructions for the free memory pointer, required for initialization bytecode. - [0-9a-z]+ matches one or more
+ * valid hexadecimal characters (0-9, a-f) after the free memory pointer setup. - %s matches the CODECOPY opcode,
+ * which copies code to memory and typically follows the memory pointer setup in init bytecode. - [0-9a-z]+ matches
+ * one or more valid hexadecimal characters (0-9, a-f) between CODECOPY and RETURN. - %s matches the RETURN opcode,
+ * signaling the end of the initialization bytecode.
+ *
+ *
+ * Example pattern: (60806040|60606040)[0-9a-z]+39[0-9a-z]+f3 This example would match any sequence where either
+ * "60806040" or "60606040" appears, followed by "39" (CODECOPY) and then "f3" (RETURN), with valid hexadecimal
+ * characters in between.
+ */
+ private static final Pattern INIT_BYTECODE_PATTERN = Pattern.compile(
+ String.format(
+ "(%s|%s)[0-9a-z]+%s[0-9a-z]+%s", FREE_MEMORY_POINTER, FREE_MEMORY_POINTER_2, CODECOPY, RETURN),
+ Pattern.CASE_INSENSITIVE);
+
+ public static String extractRuntimeBytecode(String initBytecode) {
+ // Check if the bytecode starts with "0x" and remove it if necessary
+ if (initBytecode.startsWith(HEX_PREFIX)) {
+ initBytecode = initBytecode.substring(2);
+ }
+
+ String runtimeBytecode = getRuntimeBytecode(initBytecode);
+
+ return HEX_PREFIX + runtimeBytecode; // Append "0x" prefix and return
+ }
+
+ @Nonnull
+ private static String getRuntimeBytecode(final String initBytecode) {
+ // Find the first occurrence of "CODECOPY" (39)
+ int codeCopyIndex = initBytecode.indexOf(CODECOPY);
+
+ if (codeCopyIndex == -1) {
+ throw new IllegalArgumentException("CODECOPY instruction (39) not found in init bytecode.");
+ }
+
+ // Find the first occurrence of "6080" after the "CODECOPY"
+ int runtimeCodePrefixIndex = initBytecode.indexOf(RUNTIME_CODE_PREFIX, codeCopyIndex);
+
+ if (runtimeCodePrefixIndex == -1) {
+ throw new IllegalArgumentException("Runtime code prefix (6080) not found after CODECOPY.");
+ }
+
+ // Extract the runtime bytecode starting from the runtimeCodePrefixIndex
+ return initBytecode.substring(runtimeCodePrefixIndex);
+ }
+
+ /**
+ * Checks if a given data string is likely init bytecode.
+ *
+ * @param data the data string to check.
+ * @return true if it is init bytecode, false otherwise.
+ */
+ public static boolean isInitBytecode(final String data) {
+ if (data == null || data.length() < MINIMUM_INIT_CODE_SIZE) {
+ return false;
+ }
+
+ return INIT_BYTECODE_PATTERN.matcher(data).find();
+ }
+
+ public static boolean isValidInitBytecode(final String data) {
+ return shouldSkipBytecodeCheck() || BytecodeUtils.isInitBytecode(data);
+ }
+
+ private static boolean shouldSkipBytecodeCheck() {
+ return Boolean.parseBoolean(System.getenv(SKIP_INIT_CODE_CHECK));
+ }
+}
diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/viewmodel/ContractCallRequest.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/viewmodel/ContractCallRequest.java
index b82562a0efe..2dfe035a8b3 100644
--- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/viewmodel/ContractCallRequest.java
+++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/viewmodel/ContractCallRequest.java
@@ -20,6 +20,7 @@
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.hedera.mirror.web3.convert.BlockTypeDeserializer;
import com.hedera.mirror.web3.convert.BlockTypeSerializer;
+import com.hedera.mirror.web3.utils.BytecodeUtils;
import com.hedera.mirror.web3.validation.Hex;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Min;
@@ -63,6 +64,7 @@ private boolean hasFrom() {
@AssertTrue(message = "must not be empty")
private boolean hasTo() {
- return value <= 0 || from == null || StringUtils.isNotEmpty(to);
+ boolean isValidToField = value <= 0 || from == null || StringUtils.isNotEmpty(to);
+ return BytecodeUtils.isValidInitBytecode(data) || isValidToField;
}
}
diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/controller/ContractControllerTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/controller/ContractControllerTest.java
index 44d0ac7d301..0863b29981c 100644
--- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/controller/ContractControllerTest.java
+++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/controller/ContractControllerTest.java
@@ -45,6 +45,15 @@
import com.hedera.mirror.web3.viewmodel.BlockType;
import com.hedera.mirror.web3.viewmodel.ContractCallRequest;
import com.hedera.mirror.web3.viewmodel.GenericErrorResponse;
+import com.hedera.mirror.web3.web3j.generated.DynamicEthCalls;
+import com.hedera.mirror.web3.web3j.generated.ERCTestContractHistorical;
+import com.hedera.mirror.web3.web3j.generated.EthCall;
+import com.hedera.mirror.web3.web3j.generated.EvmCodes;
+import com.hedera.mirror.web3.web3j.generated.EvmCodesHistorical;
+import com.hedera.mirror.web3.web3j.generated.ExchangeRatePrecompileHistorical;
+import com.hedera.mirror.web3.web3j.generated.NestedCallsHistorical;
+import com.hedera.mirror.web3.web3j.generated.PrecompileTestContractHistorical;
+import com.hedera.mirror.web3.web3j.generated.TestAddressThis;
import io.github.bucket4j.Bucket;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
@@ -79,6 +88,7 @@ class ContractControllerTest {
private static final String CALL_URI = "/api/v1/contracts/call";
private static final String ONE_BYTE_HEX = "80";
private static final long THROTTLE_GAS_LIMIT = 10_000_000L;
+ private static final String INIT_CODE = "0x6080604052348015600f57600080fd5b5060a38061001c6000396000f3";
@Resource
private MockMvc mockMvc;
@@ -117,9 +127,9 @@ private ResultActions contractCall(ContractCallRequest request) {
.content(convert(request)));
}
- @NullAndEmptySource
- @ValueSource(strings = {"0x00000000000000000000000000000000000007e7"})
@ParameterizedTest
+ @NullAndEmptySource
+ @ValueSource(strings = {"0x00000000000000000000000000000000000007e7", "0x00000000000000000000000000000000000004e2"})
void estimateGas(String to) throws Exception {
final var request = request();
request.setEstimate(true);
@@ -128,6 +138,28 @@ void estimateGas(String to) throws Exception {
contractCall(request).andExpect(status().isOk());
}
+ @ParameterizedTest
+ @ValueSource(
+ strings = {
+ DynamicEthCalls.BINARY,
+ ERCTestContractHistorical.BINARY,
+ EthCall.BINARY,
+ EvmCodes.BINARY,
+ EvmCodesHistorical.BINARY,
+ ExchangeRatePrecompileHistorical.BINARY,
+ NestedCallsHistorical.BINARY,
+ PrecompileTestContractHistorical.BINARY,
+ TestAddressThis.BINARY
+ })
+ void estimateGasContractDeploy(final String data) throws Exception {
+ final var request = request();
+ request.setEstimate(true);
+ request.setValue(0);
+ request.setTo(null);
+ request.setData(data);
+ contractCall(request).andExpect(status().isOk());
+ }
+
@ValueSource(longs = {2000, -2000, 16_000_000L, 0})
@ParameterizedTest
void estimateGasWithInvalidGasParameter(long gas) throws Exception {
@@ -446,7 +478,7 @@ void callSuccessWithNullAndEmptyData(String data) throws Exception {
void callSuccessOnContractCreateWithMissingFrom() throws Exception {
final var request = request();
request.setFrom(null);
- request.setTo(null);
+ request.setData(INIT_CODE);
request.setValue(0);
request.setEstimate(false);
diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallAddressThisTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallAddressThisTest.java
index 431763e869a..09bc96ecb8e 100644
--- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallAddressThisTest.java
+++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallAddressThisTest.java
@@ -25,30 +25,59 @@
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.hedera.mirror.web3.utils.BytecodeUtils;
+import com.hedera.mirror.web3.viewmodel.BlockType;
+import com.hedera.mirror.web3.viewmodel.ContractCallRequest;
import com.hedera.mirror.web3.web3j.generated.TestAddressThis;
import com.hedera.mirror.web3.web3j.generated.TestNestedAddressThis;
import com.hedera.node.app.service.evm.contracts.execution.HederaEvmTransactionProcessingResult;
import jakarta.annotation.Resource;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
+import lombok.SneakyThrows;
import org.apache.tuweni.bytes.Bytes;
import org.hyperledger.besu.datatypes.Address;
import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;
+@AutoConfigureMockMvc
class ContractCallAddressThisTest extends AbstractContractCallServiceTest {
+ private static final String CALL_URI = "/api/v1/contracts/call";
+
@Resource
protected ContractExecutionService contractCallService;
+ @Resource
+ private MockMvc mockMvc;
+
+ @Resource
+ private ObjectMapper objectMapper;
+
@SpyBean
private ContractExecutionService contractExecutionService;
+ @SneakyThrows
+ private ResultActions contractCall(ContractCallRequest request) {
+ return mockMvc.perform(post(CALL_URI)
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(convert(request)));
+ }
+
@Test
void deployAddressThisContract() {
- final var contract = testWeb3jService.deploy(TestAddressThis::deploy);
+ final var contract = testWeb3jService.deployWithValue(TestAddressThis::deploy, BigInteger.valueOf(1000));
final var serviceParameters = testWeb3jService.serviceParametersForTopLevelContractCreate(
contract.getContractBinary(), ETH_ESTIMATE_GAS, Address.ZERO);
final long actualGas = 57764L;
@@ -59,7 +88,7 @@ void deployAddressThisContract() {
@Test
void addressThisFromFunction() {
- final var contract = testWeb3jService.deploy(TestAddressThis::deploy);
+ final var contract = testWeb3jService.deployWithValue(TestAddressThis::deploy, BigInteger.valueOf(1000));
final var functionCall = contract.send_testAddressThisFunction();
verifyEthCallAndEstimateGas(functionCall, contract);
}
@@ -67,7 +96,8 @@ void addressThisFromFunction() {
@Test
void addressThisEthCallWithoutEvmAlias() throws Exception {
// Given
- final var contract = testWeb3jService.deployWithoutPersist(TestAddressThis::deploy);
+ final var contract =
+ testWeb3jService.deployWithoutPersistWithValue(TestAddressThis::deploy, BigInteger.valueOf(1000));
addressThisContractPersist(
testWeb3jService.getContractRuntime(), Address.fromHexString(contract.getContractAddress()));
final List capturedOutputs = new ArrayList<>();
@@ -88,6 +118,43 @@ void addressThisEthCallWithoutEvmAlias() throws Exception {
assertThat(successfulResponse).isEqualTo(capturedOutputs.getFirst().toHexString());
}
+ @Test
+ void contractDeployWithoutValue() throws Exception {
+ // Given
+ final var contract = testWeb3jService.deployWithValue(TestAddressThis::deploy, BigInteger.valueOf(1000));
+ final var request = new ContractCallRequest();
+ request.setBlock(BlockType.LATEST);
+ request.setData(contract.getContractBinary());
+ request.setFrom(Address.ZERO.toHexString());
+ // When
+ contractCall(request)
+ // Then
+ .andExpect(status().isOk())
+ .andExpect(result -> {
+ final var response = result.getResponse().getContentAsString();
+ assertThat(response).contains(BytecodeUtils.extractRuntimeBytecode(contract.getContractBinary()));
+ });
+ }
+
+ @Test
+ void contractDeployWithValue() throws Exception {
+ // Given
+ final var contract = testWeb3jService.deployWithValue(TestAddressThis::deploy, BigInteger.valueOf(1000));
+ final var request = new ContractCallRequest();
+ request.setBlock(BlockType.LATEST);
+ request.setData(contract.getContractBinary());
+ request.setFrom(Address.ZERO.toHexString());
+ request.setValue(1000);
+ // When
+ contractCall(request)
+ // Then
+ .andExpect(status().isOk())
+ .andExpect(result -> {
+ final var response = result.getResponse().getContentAsString();
+ assertThat(response).contains(BytecodeUtils.extractRuntimeBytecode(contract.getContractBinary()));
+ });
+ }
+
@Test
void deployNestedAddressThisContract() {
final var contract = testWeb3jService.deploy(TestNestedAddressThis::deploy);
@@ -116,4 +183,9 @@ private void addressThisContractPersist(byte[] runtimeBytecode, Address contract
.persist();
domainBuilder.recordFile().customize(f -> f.bytes(runtimeBytecode)).persist();
}
+
+ @SneakyThrows
+ private String convert(Object object) {
+ return objectMapper.writeValueAsString(object);
+ }
}
diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceERCTokenModificationFunctionsTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceERCTokenModificationFunctionsTest.java
index 24078c2c242..8e2534df9a6 100644
--- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceERCTokenModificationFunctionsTest.java
+++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceERCTokenModificationFunctionsTest.java
@@ -24,20 +24,49 @@
import static com.hedera.mirror.web3.utils.ContractCallTestUtil.SPENDER_ALIAS;
import static com.hedera.mirror.web3.utils.ContractCallTestUtil.SPENDER_PUBLIC_KEY;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.ByteString;
import com.hedera.mirror.common.domain.entity.EntityId;
import com.hedera.mirror.common.domain.token.Token;
import com.hedera.mirror.common.domain.token.TokenKycStatusEnum;
import com.hedera.mirror.common.domain.token.TokenTypeEnum;
+import com.hedera.mirror.web3.utils.BytecodeUtils;
+import com.hedera.mirror.web3.viewmodel.BlockType;
+import com.hedera.mirror.web3.viewmodel.ContractCallRequest;
import com.hedera.mirror.web3.web3j.generated.ERCTestContract;
import com.hedera.mirror.web3.web3j.generated.RedirectTestContract;
+import jakarta.annotation.Resource;
import java.math.BigInteger;
+import lombok.SneakyThrows;
import org.hyperledger.besu.datatypes.Address;
import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+@AutoConfigureMockMvc
class ContractCallServiceERCTokenModificationFunctionsTest extends AbstractContractCallServiceTest {
+ private static final String CALL_URI = "/api/v1/contracts/call";
+
+ @Resource
+ private MockMvc mockMvc;
+
+ @Resource
+ private ObjectMapper objectMapper;
+
+ @SneakyThrows
+ private ResultActions contractCall(ContractCallRequest request) {
+ return mockMvc.perform(post(CALL_URI)
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(convert(request)));
+ }
+
@Test
void approveFungibleToken() {
// Given
@@ -92,6 +121,39 @@ void deleteAllowanceNFT() {
verifyEthCallAndEstimateGas(functionCall, contract);
}
+ @Test
+ void contractDeployNonPayableWithoutValue() throws Exception {
+ // Given
+ final var contract = testWeb3jService.deploy(ERCTestContract::deploy);
+ final var request = new ContractCallRequest();
+ request.setBlock(BlockType.LATEST);
+ request.setData(contract.getContractBinary());
+ request.setFrom(Address.ZERO.toHexString());
+ // When
+ contractCall(request)
+ // Then
+ .andExpect(status().isOk())
+ .andExpect(result -> {
+ final var response = result.getResponse().getContentAsString();
+ assertThat(response).contains(BytecodeUtils.extractRuntimeBytecode(contract.getContractBinary()));
+ });
+ }
+
+ @Test
+ void contractDeployNonPayableWithValue() throws Exception {
+ // Given
+ final var contract = testWeb3jService.deploy(ERCTestContract::deploy);
+ final var request = new ContractCallRequest();
+ request.setBlock(BlockType.LATEST);
+ request.setData(contract.getContractBinary());
+ request.setFrom(Address.ZERO.toHexString());
+ request.setValue(10);
+ // When
+ contractCall(request)
+ // Then
+ .andExpect(status().isBadRequest());
+ }
+
@Test
void approveFungibleTokenWithAlias() {
// Given
@@ -739,4 +801,9 @@ protected void fungibleTokenAllowancePersist(
.owner(owner.getId()))
.persist();
}
+
+ @SneakyThrows
+ private String convert(Object object) {
+ return objectMapper.writeValueAsString(object);
+ }
}
diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/utils/RuntimeBytecodeExtractorTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/utils/BytecodeUtilsTest.java
similarity index 56%
rename from hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/utils/RuntimeBytecodeExtractorTest.java
rename to hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/utils/BytecodeUtilsTest.java
index b175c144e51..d9233ade4c9 100644
--- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/utils/RuntimeBytecodeExtractorTest.java
+++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/utils/BytecodeUtilsTest.java
@@ -24,18 +24,26 @@
import com.hedera.mirror.web3.service.ContractExecutionService;
import com.hedera.mirror.web3.web3j.TestWeb3jService;
import com.hedera.mirror.web3.web3j.TestWeb3jService.Web3jTestConfiguration;
+import com.hedera.mirror.web3.web3j.generated.DynamicEthCalls;
+import com.hedera.mirror.web3.web3j.generated.ERCTestContractHistorical;
import com.hedera.mirror.web3.web3j.generated.EthCall;
import com.hedera.mirror.web3.web3j.generated.EvmCodes;
+import com.hedera.mirror.web3.web3j.generated.EvmCodesHistorical;
import com.hedera.mirror.web3.web3j.generated.ExchangeRatePrecompileHistorical;
+import com.hedera.mirror.web3.web3j.generated.NestedCallsHistorical;
+import com.hedera.mirror.web3.web3j.generated.PrecompileTestContractHistorical;
+import com.hedera.mirror.web3.web3j.generated.TestAddressThis;
import lombok.RequiredArgsConstructor;
import org.hyperledger.besu.datatypes.Address;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.context.annotation.Import;
@Import(Web3jTestConfiguration.class)
@RequiredArgsConstructor
-class RuntimeBytecodeExtractorTest extends Web3IntegrationTest {
+class BytecodeUtilsTest extends Web3IntegrationTest {
private final ContractExecutionService contractExecutionService;
private final TestWeb3jService testWeb3jService;
@@ -49,7 +57,7 @@ void setUp() {
void testExtractRuntimeBytecodeEvmCodes() {
final var serviceParameters =
testWeb3jService.serviceParametersForTopLevelContractCreate(EvmCodes.BINARY, ETH_CALL, Address.ZERO);
- assertThat(RuntimeBytecodeExtractor.extractRuntimeBytecode(EvmCodes.BINARY))
+ assertThat(BytecodeUtils.extractRuntimeBytecode(EvmCodes.BINARY))
.isEqualTo(contractExecutionService.processCall(serviceParameters));
}
@@ -57,7 +65,7 @@ void testExtractRuntimeBytecodeEvmCodes() {
void testExtractRuntimeBytecodeEthCall() {
final var serviceParameters =
testWeb3jService.serviceParametersForTopLevelContractCreate(EthCall.BINARY, ETH_CALL, Address.ZERO);
- assertThat(RuntimeBytecodeExtractor.extractRuntimeBytecode(EthCall.BINARY))
+ assertThat(BytecodeUtils.extractRuntimeBytecode(EthCall.BINARY))
.isEqualTo(contractExecutionService.processCall(serviceParameters));
}
@@ -65,7 +73,7 @@ void testExtractRuntimeBytecodeEthCall() {
void testExtractRuntimeBytecodeExchangeRateHistorical() {
final var serviceParameters = testWeb3jService.serviceParametersForTopLevelContractCreate(
ExchangeRatePrecompileHistorical.BINARY, ETH_CALL, Address.ZERO);
- assertThat(RuntimeBytecodeExtractor.extractRuntimeBytecode(ExchangeRatePrecompileHistorical.BINARY))
+ assertThat(BytecodeUtils.extractRuntimeBytecode(ExchangeRatePrecompileHistorical.BINARY))
.isEqualTo(contractExecutionService.processCall(serviceParameters));
}
@@ -73,7 +81,7 @@ void testExtractRuntimeBytecodeExchangeRateHistorical() {
void testExtractRuntimeBytecodeMissingCODECOPY() {
String initBytecode = "6080abcdef"; // No CODECOPY present
final var exception = assertThrows(RuntimeException.class, () -> {
- RuntimeBytecodeExtractor.extractRuntimeBytecode(initBytecode);
+ BytecodeUtils.extractRuntimeBytecode(initBytecode);
});
assertThat(exception.getMessage()).isEqualTo("CODECOPY instruction (39) not found in init bytecode.");
}
@@ -82,8 +90,46 @@ void testExtractRuntimeBytecodeMissingCODECOPY() {
void testExtractRuntimeBytecodeMissingRuntimePrefix() {
String initBytecode = "395ff3fe"; // CODECOPY present but no runtime code prefix
RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
- RuntimeBytecodeExtractor.extractRuntimeBytecode(initBytecode);
+ BytecodeUtils.extractRuntimeBytecode(initBytecode);
});
assertThat(thrown.getMessage()).isEqualTo("Runtime code prefix (6080) not found after CODECOPY.");
}
+
+ @ParameterizedTest
+ @ValueSource(
+ strings = {
+ DynamicEthCalls.BINARY,
+ ERCTestContractHistorical.BINARY,
+ EthCall.BINARY,
+ EvmCodes.BINARY,
+ EvmCodesHistorical.BINARY,
+ ExchangeRatePrecompileHistorical.BINARY,
+ NestedCallsHistorical.BINARY,
+ PrecompileTestContractHistorical.BINARY,
+ TestAddressThis.BINARY
+ })
+ void testIsInitBytecode(final String data) {
+ assertThat(BytecodeUtils.isInitBytecode(data)).isTrue();
+ }
+
+ @ParameterizedTest
+ @ValueSource(
+ strings = {
+ "",
+ " ",
+ "0x",
+ "0x39", // Only CODECOPY, missing everything else
+ "0xf30039", // Contains RETURN and CODECOPY, but in the wrong order
+ "608060", // Starts with a partial free memory pointer setup
+ "608060403900000", // Free memory pointer setup + CODECOPY but missing RETURN
+ "396080604000000", // CODECOPY before free memory pointer setup
+ "606060f3", // Free memory pointer setup + RETURN but missing CODECOPY
+ "60404039f2", // Free memory pointer setup + CODECOPY, but invalid opcode instead of RETURN
+ "0x0039608040f3", // CODECOPY at start, free memory pointer setup and RETURN out of order
+ "60806040f360", // Free memory pointer setup followed by RETURN and CODECOPY out of order
+ "0x608060f34039", // Free memory pointer setup, RETURN before CODECOPY
+ })
+ void testIsInitBytecodeFalse(final String data) {
+ assertThat(BytecodeUtils.isInitBytecode(data)).isFalse();
+ }
}
diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/utils/RuntimeBytecodeExtractor.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/utils/RuntimeBytecodeExtractor.java
deleted file mode 100644
index aadf0c7508e..00000000000
--- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/utils/RuntimeBytecodeExtractor.java
+++ /dev/null
@@ -1,69 +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.utils;
-
-import static com.hedera.mirror.web3.validation.HexValidator.HEX_PREFIX;
-
-import jakarta.annotation.Nonnull;
-import lombok.experimental.UtilityClass;
-
-/**
- * A utility class for extracting runtime bytecode from init bytecode of a smart contract.
- *
- * Smart contracts have init bytecode (constructor bytecode) and runtime bytecode (the code
- * executed when the contract is called). This class helps in extracting the runtime bytecode from the
- * given init bytecode by searching for specific patterns.
- *
- */
-@UtilityClass
-public class RuntimeBytecodeExtractor {
-
- private static final String CODECOPY = "39";
- private static final String RUNTIME_CODE_PREFIX =
- "6080"; // The pattern to find the start of the runtime code in the init bytecode
-
- public static String extractRuntimeBytecode(String initBytecode) {
- // Check if the bytecode starts with "0x" and remove it if necessary
- if (initBytecode.startsWith(HEX_PREFIX)) {
- initBytecode = initBytecode.substring(2);
- }
-
- String runtimeBytecode = getRuntimeBytecode(initBytecode);
-
- return HEX_PREFIX + runtimeBytecode; // Append "0x" prefix and return
- }
-
- @Nonnull
- private static String getRuntimeBytecode(final String initBytecode) {
- // Find the first occurrence of "CODECOPY" (39)
- int codeCopyIndex = initBytecode.indexOf(CODECOPY);
-
- if (codeCopyIndex == -1) {
- throw new RuntimeException("CODECOPY instruction (39) not found in init bytecode.");
- }
-
- // Find the first occurrence of "6080" after the "CODECOPY"
- int runtimeCodePrefixIndex = initBytecode.indexOf(RUNTIME_CODE_PREFIX, codeCopyIndex);
-
- if (runtimeCodePrefixIndex == -1) {
- throw new RuntimeException("Runtime code prefix (6080) not found after CODECOPY.");
- }
-
- // Extract the runtime bytecode starting from the runtimeCodePrefixIndex
- return initBytecode.substring(runtimeCodePrefixIndex);
- }
-}
diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/web3j/TestWeb3jService.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/web3j/TestWeb3jService.java
index 9c800c79077..ca144549f75 100644
--- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/web3j/TestWeb3jService.java
+++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/web3j/TestWeb3jService.java
@@ -30,7 +30,7 @@
import com.hedera.mirror.web3.service.ContractExecutionService;
import com.hedera.mirror.web3.service.model.CallServiceParameters;
import com.hedera.mirror.web3.service.model.ContractExecutionParameters;
-import com.hedera.mirror.web3.utils.RuntimeBytecodeExtractor;
+import com.hedera.mirror.web3.utils.BytecodeUtils;
import com.hedera.mirror.web3.viewmodel.BlockType;
import com.hedera.node.app.service.evm.store.models.HederaEvmAccount;
import com.hederahashgraph.api.proto.java.Key.KeyCase;
@@ -132,6 +132,12 @@ public T deployWithoutPersist(Deployer deployer) {
return deployer.deploy(web3j, credentials, contractGasProvider).send();
}
+ @SneakyThrows(Exception.class)
+ public T deployWithoutPersistWithValue(DeployerWithValue deployer, BigInteger value) {
+ persistContract = false;
+ return deployer.deploy(web3j, credentials, contractGasProvider, value).send();
+ }
+
@SneakyThrows(Exception.class)
public T deployWithValue(DeployerWithValue deployer, BigInteger value) {
return deployer.deploy(web3j, credentials, contractGasProvider, value).send();
@@ -170,7 +176,7 @@ private EthSendTransaction sendTopLevelContractCreate(
serviceParametersForTopLevelContractCreate(rawTransaction.getData(), ETH_CALL, sender);
runtimeCode = contractExecutionService.processCall(serviceParameters);
} else {
- runtimeCode = RuntimeBytecodeExtractor.extractRuntimeBytecode(rawTransaction.getData());
+ runtimeCode = BytecodeUtils.extractRuntimeBytecode(rawTransaction.getData());
}
try {
final var contractInstance = deployInternal(runtimeCode, persistContract);
diff --git a/hedera-mirror-web3/src/test/solidity/TestAddressThis.sol b/hedera-mirror-web3/src/test/solidity/TestAddressThis.sol
index 9e78f0447bf..f4a04559cde 100644
--- a/hedera-mirror-web3/src/test/solidity/TestAddressThis.sol
+++ b/hedera-mirror-web3/src/test/solidity/TestAddressThis.sol
@@ -3,7 +3,7 @@ pragma solidity ^0.8.18;
contract TestAddressThis {
- constructor() {
+ constructor() payable {
address test = address(this);
if (test == address(0)) {
revert("Zero address.");