From e3aaaa5cd85132126413277495f12e242bec3c53 Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Wed, 4 Sep 2024 16:48:34 +0300 Subject: [PATCH 01/13] feat: add hedera.app dependency in hedera-mirror-web3 Signed-off-by: IvanKavaldzhiev --- build.gradle.kts | 1 + .../main/kotlin/java-conventions.gradle.kts | 1 + hedera-mirror-web3/build.gradle.kts | 4 + .../convert/SemanticVersionConvertor.java | 5 +- .../web3/evm/config/EvmConfiguration.java | 10 +- .../execution/MirrorEvmTxProcessorImpl.java | 2 +- .../properties/MirrorNodeEvmProperties.java | 6 +- .../execution/HederaEvmTxProcessor.java | 8 +- .../MirrorNodeEvmPropertiesTest.java | 318 +++++++++--------- 9 files changed, 183 insertions(+), 172 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2065aa1e14e..c59486abf3f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { api("com.graphql-java-generator:graphql-java-client-runtime:2.8") api("com.graphql-java:graphql-java-extended-scalars:22.0") api("com.graphql-java:graphql-java-extended-validation:22.0") + api("com.hedera.hashgraph:app:0.54.0") api("com.hedera.evm:hedera-evm:0.48.0") api("com.hedera.hashgraph:hedera-protobuf-java-api:0.53.0") api("com.hedera.hashgraph:sdk:2.38.0") diff --git a/buildSrc/src/main/kotlin/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/java-conventions.gradle.kts index 32b777f2987..d22f0695cca 100644 --- a/buildSrc/src/main/kotlin/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/java-conventions.gradle.kts @@ -32,6 +32,7 @@ configurations.all { } repositories { + mavenLocal() maven { url = uri("https://hyperledger.jfrog.io/artifactory/besu-maven/") } maven { url = uri("https://artifacts.consensys.net/public/maven/maven/") } } diff --git a/hedera-mirror-web3/build.gradle.kts b/hedera-mirror-web3/build.gradle.kts index fb36c7cddb0..d58ab19920f 100644 --- a/hedera-mirror-web3/build.gradle.kts +++ b/hedera-mirror-web3/build.gradle.kts @@ -31,6 +31,10 @@ dependencies { implementation(project(":common")) implementation("com.bucket4j:bucket4j-core") implementation("com.esaulpaugh:headlong") + implementation("com.hedera.hashgraph:app") { + exclude(group = "com.hedera.evm") + exclude(group = "io.netty") + } implementation("com.hedera.evm:hedera-evm") implementation("io.github.mweirauch:micrometer-jvm-extras") implementation("io.micrometer:micrometer-registry-prometheus") diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java index e0bc34890a0..f4fb29c1bfa 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java @@ -16,7 +16,7 @@ package com.hedera.mirror.web3.convert; -import com.swirlds.common.utility.SemanticVersion; +import com.hedera.hapi.node.base.SemanticVersion; import jakarta.inject.Named; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.core.convert.converter.Converter; @@ -26,6 +26,7 @@ public class SemanticVersionConvertor implements Converter { @Override public SemanticVersion convert(String source) { - return SemanticVersion.parse(source); + // return SemanticVersion.parse(source); + return SemanticVersion.DEFAULT; } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/config/EvmConfiguration.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/config/EvmConfiguration.java index 98683f6823c..71a35c251c3 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/config/EvmConfiguration.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/config/EvmConfiguration.java @@ -19,6 +19,7 @@ import static org.hyperledger.besu.evm.internal.EvmConfiguration.WorldUpdaterMode.JOURNALED; import com.github.benmanes.caffeine.cache.Caffeine; +import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.mirror.web3.evm.contracts.execution.MirrorEvmMessageCallProcessor; import com.hedera.mirror.web3.evm.contracts.execution.MirrorEvmMessageCallProcessorV30; import com.hedera.mirror.web3.evm.contracts.execution.traceability.MirrorOperationTracer; @@ -48,7 +49,6 @@ import com.hedera.services.evm.contracts.operations.HederaSelfDestructOperationV046; import com.hedera.services.txns.crypto.AbstractAutoCreationLogic; import com.hedera.services.txns.util.PrngLogic; -import com.swirlds.common.utility.SemanticVersion; import java.util.EnumMap; import java.util.HashMap; import java.util.List; @@ -107,10 +107,10 @@ public class EvmConfiguration { public static final String CACHE_NAME_TOKEN_ACCOUNT = "tokenAccount"; public static final String CACHE_NAME_TOKEN_ACCOUNT_COUNT = "tokenAccountCount"; public static final String CACHE_NAME_TOKEN_ALLOWANCE = "tokenAllowance"; - public static final SemanticVersion EVM_VERSION_0_30 = SemanticVersion.parse("0.30.0"); - public static final SemanticVersion EVM_VERSION_0_34 = SemanticVersion.parse("0.34.0"); - public static final SemanticVersion EVM_VERSION_0_38 = SemanticVersion.parse("0.38.0"); - public static final SemanticVersion EVM_VERSION_0_46 = SemanticVersion.parse("0.46.0"); + public static final SemanticVersion EVM_VERSION_0_30 = new SemanticVersion(0, 30, 0, "", ""); + public static final SemanticVersion EVM_VERSION_0_34 = new SemanticVersion(0, 34, 0, "", ""); + public static final SemanticVersion EVM_VERSION_0_38 = new SemanticVersion(0, 38, 0, "", ""); + public static final SemanticVersion EVM_VERSION_0_46 = new SemanticVersion(0, 46, 0, "", ""); public static final SemanticVersion EVM_VERSION = EVM_VERSION_0_46; private final CacheProperties cacheProperties; private final MirrorNodeEvmProperties mirrorNodeEvmProperties; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorImpl.java index 90d95262c23..d50d4d042cd 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorImpl.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorImpl.java @@ -35,7 +35,7 @@ import com.hedera.services.store.models.Account; import com.hedera.services.utils.EntityIdUtils; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import com.swirlds.common.utility.SemanticVersion; +import com.hederahashgraph.api.proto.java.SemanticVersion; import jakarta.inject.Named; import java.time.Instant; import java.util.Map; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java index 40ea33a8a65..1deb37b018b 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java @@ -23,10 +23,10 @@ import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_46; import static com.swirlds.common.utility.CommonUtils.unhex; +import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.mirror.common.domain.entity.EntityType; import com.hedera.mirror.web3.common.ContractCallContext; import com.hedera.node.app.service.evm.contracts.execution.EvmProperties; -import com.swirlds.common.utility.SemanticVersion; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; @@ -199,7 +199,7 @@ public boolean isCreate2Enabled() { @Override public boolean allowCallsToNonContractAccounts() { - return getSemanticEvmVersion().compareTo(EVM_VERSION_0_46) >= 0; + return getSemanticEvmVersion().minor() > 46; } @Override @@ -209,7 +209,7 @@ public Set
grandfatherContracts() { @Override public boolean callsToNonExistingEntitiesEnabled(Address target) { - return !(getSemanticEvmVersion().compareTo(EVM_VERSION_0_46) < 0 + return !(getSemanticEvmVersion().minor() < 46 || !allowCallsToNonContractAccounts() || grandfatherContracts().contains(target)); } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java b/hedera-mirror-web3/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java index 2f0185bb809..4ef8a28f5ad 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java @@ -26,7 +26,7 @@ import com.hedera.node.app.service.evm.store.contracts.HederaEvmMutableWorldState; import com.hedera.node.app.service.evm.store.models.HederaEvmAccount; import com.hederahashgraph.api.proto.java.HederaFunctionality; -import com.swirlds.common.utility.SemanticVersion; +import com.hederahashgraph.api.proto.java.SemanticVersion; import java.time.Instant; import java.util.Map; import javax.inject.Provider; @@ -204,13 +204,15 @@ protected MessageFrame buildInitialFrame( } protected void process( - final MessageFrame frame, final OperationTracer operationTracer, final SemanticVersion evmVersion) { + final MessageFrame frame, + final OperationTracer operationTracer, + final com.hedera.hapi.node.base.SemanticVersion evmVersion) { final AbstractMessageProcessor executor = getMessageProcessor(frame.getType(), evmVersion); executor.process(frame, operationTracer); } private AbstractMessageProcessor getMessageProcessor( - final MessageFrame.Type type, final SemanticVersion evmVersion) { + final MessageFrame.Type type, final com.hedera.hapi.node.base.SemanticVersion evmVersion) { return switch (type) { case MESSAGE_CALL -> mcps.get(evmVersion).get(); case CONTRACT_CREATION -> ccps.get(evmVersion).get(); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmPropertiesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmPropertiesTest.java index 740cf055b05..fc1c1ecf740 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmPropertiesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmPropertiesTest.java @@ -16,163 +16,165 @@ package com.hedera.mirror.web3.evm.properties; -import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION; -import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_30; -import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_34; -import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_38; -import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_46; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mockStatic; - -import com.hedera.mirror.common.domain.transaction.RecordFile; -import com.hedera.mirror.web3.common.ContractCallContext; -import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties.HederaNetwork; -import com.swirlds.common.utility.SemanticVersion; -import java.util.Collections; -import java.util.Map; -import java.util.NavigableMap; -import java.util.TreeMap; -import java.util.stream.Stream; -import org.apache.tuweni.bytes.Bytes32; -import org.hyperledger.besu.datatypes.Address; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) +// import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION; +// import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_30; +// import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_34; +// import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_38; +// import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_46; +// import static org.assertj.core.api.Assertions.assertThat; +// import static org.mockito.BDDMockito.given; +// import static org.mockito.Mockito.mockStatic; +// +// import com.hedera.mirror.common.domain.transaction.RecordFile; +// import com.hedera.mirror.web3.common.ContractCallContext; +// import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties.HederaNetwork; +// import com.swirlds.common.utility.SemanticVersion; +// import java.util.Collections; +// import java.util.Map; +// import java.util.NavigableMap; +// import java.util.TreeMap; +// import java.util.stream.Stream; +// import org.apache.tuweni.bytes.Bytes32; +// import org.hyperledger.besu.datatypes.Address; +// import org.junit.jupiter.api.AfterEach; +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.Test; +// import org.junit.jupiter.api.extension.ExtendWith; +// import org.junit.jupiter.params.ParameterizedTest; +// import org.junit.jupiter.params.provider.Arguments; +// import org.junit.jupiter.params.provider.MethodSource; +// import org.mockito.Mock; +// import org.mockito.MockedStatic; +// import org.mockito.junit.jupiter.MockitoExtension; +// +// @ExtendWith(MockitoExtension.class) class MirrorNodeEvmPropertiesTest { - private static final int MAX_REFUND_PERCENT = 100; - private static final int MAX_CUSTOM_FEES_ALLOWED = 10; - private static final Address FUNDING_ADDRESS = Address.fromHexString("0x0000000000000000000000000000000000000062"); - private static final Bytes32 CHAIN_ID = Bytes32.fromHexString("0x0128"); - - private final MirrorNodeEvmProperties properties = new MirrorNodeEvmProperties(); - private MockedStatic staticMock; - - @Mock - private ContractCallContext contractCallContext; - - private static NavigableMap createEvmVersionsMapCustom() { - NavigableMap evmVersions = new TreeMap<>(); - evmVersions.put(0L, EVM_VERSION_0_30); - evmVersions.put(50L, EVM_VERSION_0_34); - evmVersions.put(100L, EVM_VERSION_0_38); - evmVersions.put(150L, EVM_VERSION_0_46); - return Collections.unmodifiableNavigableMap(evmVersions); - } - - private static NavigableMap createEvmVersionsMapMainnet() { - NavigableMap evmVersions = new TreeMap<>(); - evmVersions.put(0L, EVM_VERSION_0_30); - evmVersions.put(44029066L, EVM_VERSION_0_34); - evmVersions.put(49117794L, EVM_VERSION_0_38); - evmVersions.put(60258042L, EVM_VERSION_0_46); - return Collections.unmodifiableNavigableMap(evmVersions); - } - - private static Stream blockNumberToEvmVersionProviderCustom() { - return blockNumberToEvmVersionProvider(createEvmVersionsMapCustom()); - } - - private static Stream blockNumberToEvmVersionProviderMainnet() { - return blockNumberToEvmVersionProvider(createEvmVersionsMapMainnet()); - } - - private static Stream blockNumberToEvmVersionProvider(NavigableMap evmVersions) { - Stream.Builder argumentsBuilder = Stream.builder(); - - Long firstKey = evmVersions.firstKey(); - // return default EVM version for key - 1 since none will be found - argumentsBuilder.add(Arguments.of(firstKey - 1, EVM_VERSION)); - - for (Map.Entry entry : evmVersions.entrySet()) { - Long key = entry.getKey(); - var currentValue = entry.getValue(); - // Test the block number just before the key (key - 1) if it's not the first key - if (!key.equals(firstKey)) { - var lowerValue = evmVersions.lowerEntry(key).getValue(); - argumentsBuilder.add(Arguments.of(key - 1, lowerValue)); - } - - // test the exact key - argumentsBuilder.add(Arguments.of(key, currentValue)); - - // Test the next block number after the key (key + 1) - argumentsBuilder.add(Arguments.of(key + 1, currentValue)); - } - return argumentsBuilder.build(); - } - - @BeforeEach - void setup() { - properties.setEvmVersions(new TreeMap<>()); - } - - @AfterEach - void cleanup() { - if (staticMock != null) { - staticMock.close(); - } - } - - @Test - void correctPropertiesEvaluation() { - staticMock = mockStatic(ContractCallContext.class); - staticMock.when(ContractCallContext::get).thenReturn(contractCallContext); - given(contractCallContext.useHistorical()).willReturn(false); - assertThat(properties.evmVersion()).isEqualTo(EVM_VERSION.toString()); - assertThat(properties.dynamicEvmVersion()).isTrue(); - assertThat(properties.maxGasRefundPercentage()).isEqualTo(MAX_REFUND_PERCENT); - assertThat(properties.fundingAccountAddress()).isEqualTo(FUNDING_ADDRESS); - assertThat(properties.isRedirectTokenCallsEnabled()).isTrue(); - assertThat(properties.isLazyCreationEnabled()).isTrue(); - assertThat(properties.isCreate2Enabled()).isTrue(); - assertThat(properties.chainIdBytes32()).isEqualTo(CHAIN_ID); - assertThat(properties.isLimitTokenAssociations()).isFalse(); - assertThat(properties.shouldAutoRenewAccounts()).isFalse(); - assertThat(properties.shouldAutoRenewContracts()).isFalse(); - assertThat(properties.shouldAutoRenewSomeEntityType()).isFalse(); - assertThat(properties.maxCustomFeesAllowed()).isEqualTo(MAX_CUSTOM_FEES_ALLOWED); - } - - @ParameterizedTest - @MethodSource("blockNumberToEvmVersionProviderCustom") - void correctHistoricalEvmVersion(Long blockNumber, SemanticVersion expectedEvmVersion) { - staticMock = mockStatic(ContractCallContext.class); - staticMock.when(ContractCallContext::get).thenReturn(contractCallContext); - given(contractCallContext.useHistorical()).willReturn(true); - var recordFile = new RecordFile(); - recordFile.setIndex(blockNumber); - given(contractCallContext.getRecordFile()).willReturn(recordFile); - properties.setEvmVersions(createEvmVersionsMapCustom()); - assertThat(properties.evmVersion()).isEqualTo(expectedEvmVersion.toString()); - } - - @ParameterizedTest - @MethodSource("blockNumberToEvmVersionProviderMainnet") - void getEvmVersionForBlockFromHederaNetwork(Long blockNumber, SemanticVersion expectedEvmVersion) { - // given - properties.setNetwork(HederaNetwork.MAINNET); - - var result = properties.getEvmVersionForBlock(blockNumber); - assertThat(result).isEqualByComparingTo(expectedEvmVersion); - } - - @ParameterizedTest - @MethodSource("blockNumberToEvmVersionProviderCustom") - void getEvmVersionForBlockFromConfig(Long blockNumber, SemanticVersion expectedEvmVersion) { - // given - properties.setEvmVersions(createEvmVersionsMapCustom()); - - var result = properties.getEvmVersionForBlock(blockNumber); - assertThat(result).isEqualByComparingTo(expectedEvmVersion); - } + // private static final int MAX_REFUND_PERCENT = 100; + // private static final int MAX_CUSTOM_FEES_ALLOWED = 10; + // private static final Address FUNDING_ADDRESS = + // Address.fromHexString("0x0000000000000000000000000000000000000062"); + // private static final Bytes32 CHAIN_ID = Bytes32.fromHexString("0x0128"); + // + // private final MirrorNodeEvmProperties properties = new MirrorNodeEvmProperties(); + // private MockedStatic staticMock; + // + // @Mock + // private ContractCallContext contractCallContext; + // + // private static NavigableMap createEvmVersionsMapCustom() { + // NavigableMap evmVersions = new TreeMap<>(); + // evmVersions.put(0L, EVM_VERSION_0_30); + // evmVersions.put(50L, EVM_VERSION_0_34); + // evmVersions.put(100L, EVM_VERSION_0_38); + // evmVersions.put(150L, EVM_VERSION_0_46); + // return Collections.unmodifiableNavigableMap(evmVersions); + // } + // + // private static NavigableMap createEvmVersionsMapMainnet() { + // NavigableMap evmVersions = new TreeMap<>(); + // evmVersions.put(0L, EVM_VERSION_0_30); + // evmVersions.put(44029066L, EVM_VERSION_0_34); + // evmVersions.put(49117794L, EVM_VERSION_0_38); + // evmVersions.put(60258042L, EVM_VERSION_0_46); + // return Collections.unmodifiableNavigableMap(evmVersions); + // } + // + // private static Stream blockNumberToEvmVersionProviderCustom() { + // return blockNumberToEvmVersionProvider(createEvmVersionsMapCustom()); + // } + // + // private static Stream blockNumberToEvmVersionProviderMainnet() { + // return blockNumberToEvmVersionProvider(createEvmVersionsMapMainnet()); + // } + // + // private static Stream blockNumberToEvmVersionProvider(NavigableMap + // evmVersions) { + // Stream.Builder argumentsBuilder = Stream.builder(); + // + // Long firstKey = evmVersions.firstKey(); + // // return default EVM version for key - 1 since none will be found + // argumentsBuilder.add(Arguments.of(firstKey - 1, EVM_VERSION)); + // + // for (Map.Entry entry : evmVersions.entrySet()) { + // Long key = entry.getKey(); + // var currentValue = entry.getValue(); + // // Test the block number just before the key (key - 1) if it's not the first key + // if (!key.equals(firstKey)) { + // var lowerValue = evmVersions.lowerEntry(key).getValue(); + // argumentsBuilder.add(Arguments.of(key - 1, lowerValue)); + // } + // + // // test the exact key + // argumentsBuilder.add(Arguments.of(key, currentValue)); + // + // // Test the next block number after the key (key + 1) + // argumentsBuilder.add(Arguments.of(key + 1, currentValue)); + // } + // return argumentsBuilder.build(); + // } + // + // @BeforeEach + // void setup() { + // properties.setEvmVersions(new TreeMap<>()); + // } + // + // @AfterEach + // void cleanup() { + // if (staticMock != null) { + // staticMock.close(); + // } + // } + // + // @Test + // void correctPropertiesEvaluation() { + // staticMock = mockStatic(ContractCallContext.class); + // staticMock.when(ContractCallContext::get).thenReturn(contractCallContext); + // given(contractCallContext.useHistorical()).willReturn(false); + // assertThat(properties.evmVersion()).isEqualTo(EVM_VERSION.toString()); + // assertThat(properties.dynamicEvmVersion()).isTrue(); + // assertThat(properties.maxGasRefundPercentage()).isEqualTo(MAX_REFUND_PERCENT); + // assertThat(properties.fundingAccountAddress()).isEqualTo(FUNDING_ADDRESS); + // assertThat(properties.isRedirectTokenCallsEnabled()).isTrue(); + // assertThat(properties.isLazyCreationEnabled()).isTrue(); + // assertThat(properties.isCreate2Enabled()).isTrue(); + // assertThat(properties.chainIdBytes32()).isEqualTo(CHAIN_ID); + // assertThat(properties.isLimitTokenAssociations()).isFalse(); + // assertThat(properties.shouldAutoRenewAccounts()).isFalse(); + // assertThat(properties.shouldAutoRenewContracts()).isFalse(); + // assertThat(properties.shouldAutoRenewSomeEntityType()).isFalse(); + // assertThat(properties.maxCustomFeesAllowed()).isEqualTo(MAX_CUSTOM_FEES_ALLOWED); + // } + // + // @ParameterizedTest + // @MethodSource("blockNumberToEvmVersionProviderCustom") + // void correctHistoricalEvmVersion(Long blockNumber, SemanticVersion expectedEvmVersion) { + // staticMock = mockStatic(ContractCallContext.class); + // staticMock.when(ContractCallContext::get).thenReturn(contractCallContext); + // given(contractCallContext.useHistorical()).willReturn(true); + // var recordFile = new RecordFile(); + // recordFile.setIndex(blockNumber); + // given(contractCallContext.getRecordFile()).willReturn(recordFile); + // properties.setEvmVersions(createEvmVersionsMapCustom()); + // assertThat(properties.evmVersion()).isEqualTo(expectedEvmVersion.toString()); + // } + // + // @ParameterizedTest + // @MethodSource("blockNumberToEvmVersionProviderMainnet") + // void getEvmVersionForBlockFromHederaNetwork(Long blockNumber, SemanticVersion expectedEvmVersion) { + // // given + // properties.setNetwork(HederaNetwork.MAINNET); + // + // var result = properties.getEvmVersionForBlock(blockNumber); + // assertThat(result).isEqualByComparingTo(expectedEvmVersion); + // } + // + // @ParameterizedTest + // @MethodSource("blockNumberToEvmVersionProviderCustom") + // void getEvmVersionForBlockFromConfig(Long blockNumber, SemanticVersion expectedEvmVersion) { + // // given + // properties.setEvmVersions(createEvmVersionsMapCustom()); + // + // var result = properties.getEvmVersionForBlock(blockNumber); + // assertThat(result).isEqualByComparingTo(expectedEvmVersion); + // } } From 722033a11c244a7705c699b337e5a482bed25789 Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Wed, 18 Sep 2024 13:04:41 +0300 Subject: [PATCH 02/13] feat: integrate com.hedera.hashgraph:app:0.53.5 version Signed-off-by: IvanKavaldzhiev --- build.gradle.kts | 2 +- .../main/kotlin/java-conventions.gradle.kts | 1 - .../convert/SemanticVersionConvertor.java | 4 ++-- .../execution/MirrorEvmTxProcessorImpl.java | 2 +- .../execution/HederaEvmTxProcessor.java | 8 +++---- ...MirrorEvmMessageCallProcessorBaseTest.java | 16 ++++++------- .../execution/MirrorEvmTxProcessorTest.java | 2 +- .../PrngSystemPrecompiledContractTest.java | 23 ++++++++++--------- 8 files changed, 28 insertions(+), 30 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e84182ca8d2..c5c8e565c6f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -66,7 +66,7 @@ dependencies { api("com.graphql-java-generator:graphql-java-client-runtime:2.8") api("com.graphql-java:graphql-java-extended-scalars:22.0") api("com.graphql-java:graphql-java-extended-validation:22.0") - api("com.hedera.hashgraph:app:0.54.0") + api("com.hedera.hashgraph:app:0.53.5") api("com.hedera.evm:hedera-evm:0.48.0") api("com.hedera.hashgraph:hedera-protobuf-java-api:0.54.0") api("com.hedera.hashgraph:sdk:2.39.0") diff --git a/buildSrc/src/main/kotlin/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/java-conventions.gradle.kts index d22f0695cca..32b777f2987 100644 --- a/buildSrc/src/main/kotlin/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/java-conventions.gradle.kts @@ -32,7 +32,6 @@ configurations.all { } repositories { - mavenLocal() maven { url = uri("https://hyperledger.jfrog.io/artifactory/besu-maven/") } maven { url = uri("https://artifacts.consensys.net/public/maven/maven/") } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java index f4fb29c1bfa..0db70d28cf2 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java @@ -26,7 +26,7 @@ public class SemanticVersionConvertor implements Converter { @Override public SemanticVersion convert(String source) { - // return SemanticVersion.parse(source); - return SemanticVersion.DEFAULT; + final var versions = source.split("\\."); + return new SemanticVersion(0, Integer.parseInt(versions[1]), 0, "", ""); } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorImpl.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorImpl.java index ade527b6d0b..2bb13ae8b37 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorImpl.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorImpl.java @@ -16,6 +16,7 @@ package com.hedera.mirror.web3.evm.contracts.execution; +import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.mirror.web3.evm.account.MirrorEvmContractAliases; import com.hedera.mirror.web3.evm.contracts.execution.traceability.TracerType; import com.hedera.mirror.web3.evm.store.Store; @@ -35,7 +36,6 @@ import com.hedera.services.store.models.Account; import com.hedera.services.utils.EntityIdUtils; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import com.hederahashgraph.api.proto.java.SemanticVersion; import jakarta.inject.Named; import java.time.Instant; import java.util.Map; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java b/hedera-mirror-web3/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java index 4ef8a28f5ad..0cef4e3af3d 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/node/app/service/evm/contracts/execution/HederaEvmTxProcessor.java @@ -18,6 +18,7 @@ import static com.hedera.mirror.web3.common.PrecompileContext.PRECOMPILE_CONTEXT; +import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.mirror.web3.common.ContractCallContext; import com.hedera.mirror.web3.common.PrecompileContext; import com.hedera.mirror.web3.evm.contracts.execution.traceability.TracerType; @@ -26,7 +27,6 @@ import com.hedera.node.app.service.evm.store.contracts.HederaEvmMutableWorldState; import com.hedera.node.app.service.evm.store.models.HederaEvmAccount; import com.hederahashgraph.api.proto.java.HederaFunctionality; -import com.hederahashgraph.api.proto.java.SemanticVersion; import java.time.Instant; import java.util.Map; import javax.inject.Provider; @@ -204,15 +204,13 @@ protected MessageFrame buildInitialFrame( } protected void process( - final MessageFrame frame, - final OperationTracer operationTracer, - final com.hedera.hapi.node.base.SemanticVersion evmVersion) { + final MessageFrame frame, final OperationTracer operationTracer, final SemanticVersion evmVersion) { final AbstractMessageProcessor executor = getMessageProcessor(frame.getType(), evmVersion); executor.process(frame, operationTracer); } private AbstractMessageProcessor getMessageProcessor( - final MessageFrame.Type type, final com.hedera.hapi.node.base.SemanticVersion evmVersion) { + final MessageFrame.Type type, final SemanticVersion evmVersion) { return switch (type) { case MESSAGE_CALL -> mcps.get(evmVersion).get(); case CONTRACT_CREATION -> ccps.get(evmVersion).get(); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmMessageCallProcessorBaseTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmMessageCallProcessorBaseTest.java index 9f820eebaa8..2945605af85 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmMessageCallProcessorBaseTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmMessageCallProcessorBaseTest.java @@ -61,14 +61,13 @@ @ExtendWith(MockitoExtension.class) public abstract class MirrorEvmMessageCallProcessorBaseTest { + private static final byte[] WELL_KNOWN_HASH_BYTE_ARRAY = CommonUtils.unhex( + "65386630386164632d356537632d343964342d623437372d62636134346538386338373133633038316162372d6163"); private static final Hash WELL_KNOWN_HASH = new Hash(CommonUtils.unhex( "65386630386164632d356537632d343964342d623437372d62636134346538386338373133633038316162372d616300")); private static MockedStatic contextMockedStatic; - @Spy - private ContractCallContext contractCallContext; - @Mock AbstractAutoCreationLogic autoCreationLogic; @@ -105,32 +104,33 @@ public abstract class MirrorEvmMessageCallProcessorBaseTest { @Mock EvmInfrastructureFactory evmInfrastructureFactory; + final EvmHTSPrecompiledContract htsPrecompiledContract = new EvmHTSPrecompiledContract(evmInfrastructureFactory); + @Mock PrecompilePricingUtils precompilePricingUtils; @Mock RatesAndFeesLoader ratesAndFeesLoader; - final EvmHTSPrecompiledContract htsPrecompiledContract = new EvmHTSPrecompiledContract(evmInfrastructureFactory); - final ExchangeRatePrecompiledContract exchangeRatePrecompiledContract = new ExchangeRatePrecompiledContract( gasCalculatorHederaV22, new BasicHbarCentExchange(ratesAndFeesLoader), new MirrorNodeEvmProperties(), Instant.now()); - final PrngSystemPrecompiledContract prngSystemPrecompiledContract = new PrngSystemPrecompiledContract( gasCalculatorHederaV22, - new PrngLogic(WELL_KNOWN_HASH::getValue), + new PrngLogic(() -> WELL_KNOWN_HASH_BYTE_ARRAY), new LivePricesSource( new BasicHbarCentExchange(ratesAndFeesLoader), new BasicFcfsUsagePrices(ratesAndFeesLoader)), precompilePricingUtils); - final Map hederaPrecompileList = Map.of( EVM_HTS_PRECOMPILED_CONTRACT_ADDRESS, htsPrecompiledContract, EXCHANGE_RATE_SYSTEM_CONTRACT_ADDRESS, exchangeRatePrecompiledContract, PRNG_PRECOMPILE_ADDRESS, prngSystemPrecompiledContract); + @Spy + private ContractCallContext contractCallContext; + @BeforeAll static void initStaticMocks() { contextMockedStatic = mockStatic(ContractCallContext.class); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorTest.java index 493e525bf6b..ffd0d4d4597 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmTxProcessorTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import com.google.protobuf.ByteString; +import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.mirror.web3.ContextExtension; import com.hedera.mirror.web3.common.ContractCallContext; import com.hedera.mirror.web3.evm.account.MirrorEvmContractAliases; @@ -51,7 +52,6 @@ import com.hedera.node.app.service.evm.store.models.HederaEvmAccount; import com.hedera.node.app.service.evm.store.tokens.TokenAccessor; import com.hedera.services.store.models.Account; -import com.swirlds.common.utility.SemanticVersion; import java.math.BigInteger; import java.util.List; import java.util.Map; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/store/contract/precompile/PrngSystemPrecompiledContractTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/store/contract/precompile/PrngSystemPrecompiledContractTest.java index c142a41ed49..8def9e3b05b 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/store/contract/precompile/PrngSystemPrecompiledContractTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/store/contract/precompile/PrngSystemPrecompiledContractTest.java @@ -50,8 +50,11 @@ @ExtendWith(MockitoExtension.class) class PrngSystemPrecompiledContractTest { + private static final byte[] WELL_KNOWN_HASH_BYTE_ARRAY = CommonUtils.unhex( + "65386630386164632d356537632d343964342d623437372d62636134346538386338373133633038316162372d6163"); private static final Hash WELL_KNOWN_HASH = new Hash(CommonUtils.unhex( "65386630386164632d356537632d343964342d623437372d62636134346538386338373133633038316162372d616300")); + private final Instant consensusNow = Instant.ofEpochSecond(123456789L); @Mock private MessageFrame frame; @@ -62,16 +65,22 @@ class PrngSystemPrecompiledContractTest { @Mock private PrecompilePricingUtils pricingUtils; - private final Instant consensusNow = Instant.ofEpochSecond(123456789L); - @Mock private LivePricesSource livePricesSource; private PrngSystemPrecompiledContract subject; + private static Bytes random256BitGeneratorInput() { + return input(PSEUDORANDOM_SEED_GENERATOR_SELECTOR); + } + + private static Bytes input(final int selector) { + return Bytes.concatenate(Bytes.ofUnsignedInt(selector & 0xffffffffL), Bytes.EMPTY); + } + @BeforeEach void setUp() { - Supplier mockSupplier = WELL_KNOWN_HASH::getValue; + Supplier mockSupplier = () -> WELL_KNOWN_HASH_BYTE_ARRAY; final var logic = new PrngLogic(mockSupplier); subject = new PrngSystemPrecompiledContract(gasCalculator, logic, livePricesSource, pricingUtils); @@ -169,14 +178,6 @@ void zeroedHashReturnsSentinelOutputs() { assertNull(result); } - private static Bytes random256BitGeneratorInput() { - return input(PSEUDORANDOM_SEED_GENERATOR_SELECTOR); - } - - private static Bytes input(final int selector) { - return Bytes.concatenate(Bytes.ofUnsignedInt(selector & 0xffffffffL), Bytes.EMPTY); - } - private void initialSetUp() { given(pricingUtils.getCanonicalPriceInTinyCents(PRNG)).willReturn(100000000L); given(livePricesSource.currentGasPriceInTinycents(consensusNow, HederaFunctionality.ContractCall)) From e39e649d6e46c4d4ced8fc2b13d8a82bf4c54e1e Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Wed, 18 Sep 2024 15:53:49 +0300 Subject: [PATCH 03/13] fix: stabilize tests and build Signed-off-by: IvanKavaldzhiev --- hedera-mirror-web3/build.gradle.kts | 5 +---- ...ctCallServiceERCTokenModificationFunctionsTest.java | 10 ++++------ .../ContractCallServicePrecompileModificationTest.java | 4 ++-- .../com/hedera/mirror/web3/web3j/TestWeb3jService.java | 2 ++ 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/hedera-mirror-web3/build.gradle.kts b/hedera-mirror-web3/build.gradle.kts index ec4d8e964fa..ab821f11315 100644 --- a/hedera-mirror-web3/build.gradle.kts +++ b/hedera-mirror-web3/build.gradle.kts @@ -33,10 +33,7 @@ dependencies { implementation(project(":common")) implementation("com.bucket4j:bucket4j-core") implementation("com.esaulpaugh:headlong") - implementation("com.hedera.hashgraph:app") { - exclude(group = "com.hedera.evm") - exclude(group = "io.netty") - } + implementation("com.hedera.hashgraph:app") { exclude(group = "io.netty") } implementation("com.hedera.evm:hedera-evm") implementation("io.github.mweirauch:micrometer-jvm-extras") implementation("io.micrometer:micrometer-registry-prometheus") 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 66953349e1a..24078c2c242 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 @@ -206,8 +206,7 @@ void transferFromToHollowAccount() { final var hollowAccount = domainBuilder .entity() .customize(e -> e.key(null).maxAutomaticTokenAssociations(10)) - .persist() - .toEntityId(); + .persist(); final var contract = testWeb3jService.deploy(ERCTestContract::deploy); final var contractAddress = Address.fromHexString(contract.getContractAddress()); @@ -220,7 +219,7 @@ void transferFromToHollowAccount() { final var functionCall = contract.send_transferFrom( toAddress(token.getId()).toHexString(), toAddress(owner).toHexString(), - toAddress(hollowAccount.getId()).toHexString(), + getAliasFromEntity(hollowAccount), BigInteger.valueOf(amount)); // Then verifyEthCallAndEstimateGas(functionCall, contract); @@ -501,8 +500,7 @@ void transferFromToHollowAccountRedirect() { final var hollowAccount = domainBuilder .entity() .customize(e -> e.key(null).maxAutomaticTokenAssociations(10)) - .persist() - .toEntityId(); + .persist(); final var contract = testWeb3jService.deploy(RedirectTestContract::deploy); final var contractAddress = Address.fromHexString(contract.getContractAddress()); @@ -515,7 +513,7 @@ void transferFromToHollowAccountRedirect() { final var functionCall = contract.send_transferFromRedirect( toAddress(tokenEntity.getId()).toHexString(), toAddress(owner).toHexString(), - toAddress(hollowAccount.getId()).toHexString(), + getAliasFromEntity(hollowAccount), BigInteger.valueOf(amount)); // Then verifyEthCallAndEstimateGas(functionCall, contract); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileModificationTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileModificationTest.java index 87f34a5de50..8c133a1a12a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileModificationTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileModificationTest.java @@ -285,9 +285,9 @@ void dissociateToken(final Boolean single) throws Exception { // When final var functionCall = single ? contract.call_dissociateTokenExternal( - getAddressFromEntity(associatedAccount), getAddressFromEntity(tokenEntity)) + getAliasFromEntity(associatedAccount), getAddressFromEntity(tokenEntity)) : contract.call_dissociateTokensExternal( - getAddressFromEntity(associatedAccount), List.of(getAddressFromEntity(tokenEntity))); + getAliasFromEntity(associatedAccount), List.of(getAddressFromEntity(tokenEntity))); // Then verifyEthCallAndEstimateGas(functionCall, contract, ZERO_VALUE); 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 f411f0a1d28..9c800c79077 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 @@ -337,6 +337,8 @@ private void contractPersist(String binary, long entityId) { .customize(e -> e.type(CONTRACT) .id(entityId) .num(entityId) + .alias(null) + .evmAddress(null) .key(domainBuilder.key(KeyCase.ED25519)) .balance(3000L)) .persist(); From 7acfa7a8c822806351e8f98613105828f053f495 Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Wed, 18 Sep 2024 15:57:44 +0300 Subject: [PATCH 04/13] nit: enable MirrorNodeEvmPropertiesTest Signed-off-by: IvanKavaldzhiev --- .../MirrorNodeEvmPropertiesTest.java | 318 +++++++++--------- 1 file changed, 158 insertions(+), 160 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmPropertiesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmPropertiesTest.java index fc1c1ecf740..410118844cd 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmPropertiesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmPropertiesTest.java @@ -16,165 +16,163 @@ package com.hedera.mirror.web3.evm.properties; -// import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION; -// import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_30; -// import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_34; -// import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_38; -// import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_46; -// import static org.assertj.core.api.Assertions.assertThat; -// import static org.mockito.BDDMockito.given; -// import static org.mockito.Mockito.mockStatic; -// -// import com.hedera.mirror.common.domain.transaction.RecordFile; -// import com.hedera.mirror.web3.common.ContractCallContext; -// import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties.HederaNetwork; -// import com.swirlds.common.utility.SemanticVersion; -// import java.util.Collections; -// import java.util.Map; -// import java.util.NavigableMap; -// import java.util.TreeMap; -// import java.util.stream.Stream; -// import org.apache.tuweni.bytes.Bytes32; -// import org.hyperledger.besu.datatypes.Address; -// import org.junit.jupiter.api.AfterEach; -// import org.junit.jupiter.api.BeforeEach; -// import org.junit.jupiter.api.Test; -// import org.junit.jupiter.api.extension.ExtendWith; -// import org.junit.jupiter.params.ParameterizedTest; -// import org.junit.jupiter.params.provider.Arguments; -// import org.junit.jupiter.params.provider.MethodSource; -// import org.mockito.Mock; -// import org.mockito.MockedStatic; -// import org.mockito.junit.jupiter.MockitoExtension; -// -// @ExtendWith(MockitoExtension.class) +import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION; +import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_30; +import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_34; +import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_38; +import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_46; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mockStatic; + +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.mirror.common.domain.transaction.RecordFile; +import com.hedera.mirror.web3.common.ContractCallContext; +import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties.HederaNetwork; +import java.util.Collections; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.stream.Stream; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.datatypes.Address; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) class MirrorNodeEvmPropertiesTest { - // private static final int MAX_REFUND_PERCENT = 100; - // private static final int MAX_CUSTOM_FEES_ALLOWED = 10; - // private static final Address FUNDING_ADDRESS = - // Address.fromHexString("0x0000000000000000000000000000000000000062"); - // private static final Bytes32 CHAIN_ID = Bytes32.fromHexString("0x0128"); - // - // private final MirrorNodeEvmProperties properties = new MirrorNodeEvmProperties(); - // private MockedStatic staticMock; - // - // @Mock - // private ContractCallContext contractCallContext; - // - // private static NavigableMap createEvmVersionsMapCustom() { - // NavigableMap evmVersions = new TreeMap<>(); - // evmVersions.put(0L, EVM_VERSION_0_30); - // evmVersions.put(50L, EVM_VERSION_0_34); - // evmVersions.put(100L, EVM_VERSION_0_38); - // evmVersions.put(150L, EVM_VERSION_0_46); - // return Collections.unmodifiableNavigableMap(evmVersions); - // } - // - // private static NavigableMap createEvmVersionsMapMainnet() { - // NavigableMap evmVersions = new TreeMap<>(); - // evmVersions.put(0L, EVM_VERSION_0_30); - // evmVersions.put(44029066L, EVM_VERSION_0_34); - // evmVersions.put(49117794L, EVM_VERSION_0_38); - // evmVersions.put(60258042L, EVM_VERSION_0_46); - // return Collections.unmodifiableNavigableMap(evmVersions); - // } - // - // private static Stream blockNumberToEvmVersionProviderCustom() { - // return blockNumberToEvmVersionProvider(createEvmVersionsMapCustom()); - // } - // - // private static Stream blockNumberToEvmVersionProviderMainnet() { - // return blockNumberToEvmVersionProvider(createEvmVersionsMapMainnet()); - // } - // - // private static Stream blockNumberToEvmVersionProvider(NavigableMap - // evmVersions) { - // Stream.Builder argumentsBuilder = Stream.builder(); - // - // Long firstKey = evmVersions.firstKey(); - // // return default EVM version for key - 1 since none will be found - // argumentsBuilder.add(Arguments.of(firstKey - 1, EVM_VERSION)); - // - // for (Map.Entry entry : evmVersions.entrySet()) { - // Long key = entry.getKey(); - // var currentValue = entry.getValue(); - // // Test the block number just before the key (key - 1) if it's not the first key - // if (!key.equals(firstKey)) { - // var lowerValue = evmVersions.lowerEntry(key).getValue(); - // argumentsBuilder.add(Arguments.of(key - 1, lowerValue)); - // } - // - // // test the exact key - // argumentsBuilder.add(Arguments.of(key, currentValue)); - // - // // Test the next block number after the key (key + 1) - // argumentsBuilder.add(Arguments.of(key + 1, currentValue)); - // } - // return argumentsBuilder.build(); - // } - // - // @BeforeEach - // void setup() { - // properties.setEvmVersions(new TreeMap<>()); - // } - // - // @AfterEach - // void cleanup() { - // if (staticMock != null) { - // staticMock.close(); - // } - // } - // - // @Test - // void correctPropertiesEvaluation() { - // staticMock = mockStatic(ContractCallContext.class); - // staticMock.when(ContractCallContext::get).thenReturn(contractCallContext); - // given(contractCallContext.useHistorical()).willReturn(false); - // assertThat(properties.evmVersion()).isEqualTo(EVM_VERSION.toString()); - // assertThat(properties.dynamicEvmVersion()).isTrue(); - // assertThat(properties.maxGasRefundPercentage()).isEqualTo(MAX_REFUND_PERCENT); - // assertThat(properties.fundingAccountAddress()).isEqualTo(FUNDING_ADDRESS); - // assertThat(properties.isRedirectTokenCallsEnabled()).isTrue(); - // assertThat(properties.isLazyCreationEnabled()).isTrue(); - // assertThat(properties.isCreate2Enabled()).isTrue(); - // assertThat(properties.chainIdBytes32()).isEqualTo(CHAIN_ID); - // assertThat(properties.isLimitTokenAssociations()).isFalse(); - // assertThat(properties.shouldAutoRenewAccounts()).isFalse(); - // assertThat(properties.shouldAutoRenewContracts()).isFalse(); - // assertThat(properties.shouldAutoRenewSomeEntityType()).isFalse(); - // assertThat(properties.maxCustomFeesAllowed()).isEqualTo(MAX_CUSTOM_FEES_ALLOWED); - // } - // - // @ParameterizedTest - // @MethodSource("blockNumberToEvmVersionProviderCustom") - // void correctHistoricalEvmVersion(Long blockNumber, SemanticVersion expectedEvmVersion) { - // staticMock = mockStatic(ContractCallContext.class); - // staticMock.when(ContractCallContext::get).thenReturn(contractCallContext); - // given(contractCallContext.useHistorical()).willReturn(true); - // var recordFile = new RecordFile(); - // recordFile.setIndex(blockNumber); - // given(contractCallContext.getRecordFile()).willReturn(recordFile); - // properties.setEvmVersions(createEvmVersionsMapCustom()); - // assertThat(properties.evmVersion()).isEqualTo(expectedEvmVersion.toString()); - // } - // - // @ParameterizedTest - // @MethodSource("blockNumberToEvmVersionProviderMainnet") - // void getEvmVersionForBlockFromHederaNetwork(Long blockNumber, SemanticVersion expectedEvmVersion) { - // // given - // properties.setNetwork(HederaNetwork.MAINNET); - // - // var result = properties.getEvmVersionForBlock(blockNumber); - // assertThat(result).isEqualByComparingTo(expectedEvmVersion); - // } - // - // @ParameterizedTest - // @MethodSource("blockNumberToEvmVersionProviderCustom") - // void getEvmVersionForBlockFromConfig(Long blockNumber, SemanticVersion expectedEvmVersion) { - // // given - // properties.setEvmVersions(createEvmVersionsMapCustom()); - // - // var result = properties.getEvmVersionForBlock(blockNumber); - // assertThat(result).isEqualByComparingTo(expectedEvmVersion); - // } + private static final int MAX_REFUND_PERCENT = 100; + private static final int MAX_CUSTOM_FEES_ALLOWED = 10; + private static final Address FUNDING_ADDRESS = Address.fromHexString("0x0000000000000000000000000000000000000062"); + private static final Bytes32 CHAIN_ID = Bytes32.fromHexString("0x0128"); + + private final MirrorNodeEvmProperties properties = new MirrorNodeEvmProperties(); + private MockedStatic staticMock; + + @Mock + private ContractCallContext contractCallContext; + + private static NavigableMap createEvmVersionsMapCustom() { + NavigableMap evmVersions = new TreeMap<>(); + evmVersions.put(0L, EVM_VERSION_0_30); + evmVersions.put(50L, EVM_VERSION_0_34); + evmVersions.put(100L, EVM_VERSION_0_38); + evmVersions.put(150L, EVM_VERSION_0_46); + return Collections.unmodifiableNavigableMap(evmVersions); + } + + private static NavigableMap createEvmVersionsMapMainnet() { + NavigableMap evmVersions = new TreeMap<>(); + evmVersions.put(0L, EVM_VERSION_0_30); + evmVersions.put(44029066L, EVM_VERSION_0_34); + evmVersions.put(49117794L, EVM_VERSION_0_38); + evmVersions.put(60258042L, EVM_VERSION_0_46); + return Collections.unmodifiableNavigableMap(evmVersions); + } + + private static Stream blockNumberToEvmVersionProviderCustom() { + return blockNumberToEvmVersionProvider(createEvmVersionsMapCustom()); + } + + private static Stream blockNumberToEvmVersionProviderMainnet() { + return blockNumberToEvmVersionProvider(createEvmVersionsMapMainnet()); + } + + private static Stream blockNumberToEvmVersionProvider(NavigableMap evmVersions) { + Stream.Builder argumentsBuilder = Stream.builder(); + + Long firstKey = evmVersions.firstKey(); + // return default EVM version for key - 1 since none will be found + argumentsBuilder.add(Arguments.of(firstKey - 1, EVM_VERSION)); + + for (Map.Entry entry : evmVersions.entrySet()) { + Long key = entry.getKey(); + var currentValue = entry.getValue(); + // Test the block number just before the key (key - 1) if it's not the first key + if (!key.equals(firstKey)) { + var lowerValue = evmVersions.lowerEntry(key).getValue(); + argumentsBuilder.add(Arguments.of(key - 1, lowerValue)); + } + + // test the exact key + argumentsBuilder.add(Arguments.of(key, currentValue)); + + // Test the next block number after the key (key + 1) + argumentsBuilder.add(Arguments.of(key + 1, currentValue)); + } + return argumentsBuilder.build(); + } + + @BeforeEach + void setup() { + properties.setEvmVersions(new TreeMap<>()); + } + + @AfterEach + void cleanup() { + if (staticMock != null) { + staticMock.close(); + } + } + + @Test + void correctPropertiesEvaluation() { + staticMock = mockStatic(ContractCallContext.class); + staticMock.when(ContractCallContext::get).thenReturn(contractCallContext); + given(contractCallContext.useHistorical()).willReturn(false); + assertThat(properties.evmVersion()).isEqualTo(EVM_VERSION.toString()); + assertThat(properties.dynamicEvmVersion()).isTrue(); + assertThat(properties.maxGasRefundPercentage()).isEqualTo(MAX_REFUND_PERCENT); + assertThat(properties.fundingAccountAddress()).isEqualTo(FUNDING_ADDRESS); + assertThat(properties.isRedirectTokenCallsEnabled()).isTrue(); + assertThat(properties.isLazyCreationEnabled()).isTrue(); + assertThat(properties.isCreate2Enabled()).isTrue(); + assertThat(properties.chainIdBytes32()).isEqualTo(CHAIN_ID); + assertThat(properties.isLimitTokenAssociations()).isFalse(); + assertThat(properties.shouldAutoRenewAccounts()).isFalse(); + assertThat(properties.shouldAutoRenewContracts()).isFalse(); + assertThat(properties.shouldAutoRenewSomeEntityType()).isFalse(); + assertThat(properties.maxCustomFeesAllowed()).isEqualTo(MAX_CUSTOM_FEES_ALLOWED); + } + + @ParameterizedTest + @MethodSource("blockNumberToEvmVersionProviderCustom") + void correctHistoricalEvmVersion(Long blockNumber, SemanticVersion expectedEvmVersion) { + staticMock = mockStatic(ContractCallContext.class); + staticMock.when(ContractCallContext::get).thenReturn(contractCallContext); + given(contractCallContext.useHistorical()).willReturn(true); + var recordFile = new RecordFile(); + recordFile.setIndex(blockNumber); + given(contractCallContext.getRecordFile()).willReturn(recordFile); + properties.setEvmVersions(createEvmVersionsMapCustom()); + assertThat(properties.evmVersion()).isEqualTo(expectedEvmVersion.toString()); + } + + @ParameterizedTest + @MethodSource("blockNumberToEvmVersionProviderMainnet") + void getEvmVersionForBlockFromHederaNetwork(Long blockNumber, SemanticVersion expectedEvmVersion) { + // given + properties.setNetwork(HederaNetwork.MAINNET); + + var result = properties.getEvmVersionForBlock(blockNumber); + assertThat(result).isEqualTo(expectedEvmVersion); + } + + @ParameterizedTest + @MethodSource("blockNumberToEvmVersionProviderCustom") + void getEvmVersionForBlockFromConfig(Long blockNumber, SemanticVersion expectedEvmVersion) { + // given + properties.setEvmVersions(createEvmVersionsMapCustom()); + + var result = properties.getEvmVersionForBlock(blockNumber); + assertThat(result).isEqualTo(expectedEvmVersion); + } } From 080a2fe60588b07ce2c2bb35a1ddb3ac10e7a56a Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Wed, 18 Sep 2024 16:01:24 +0300 Subject: [PATCH 05/13] nit: delete unused code Signed-off-by: IvanKavaldzhiev --- .../execution/MirrorEvmMessageCallProcessorBaseTest.java | 3 --- .../contract/precompile/PrngSystemPrecompiledContractTest.java | 3 --- 2 files changed, 6 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmMessageCallProcessorBaseTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmMessageCallProcessorBaseTest.java index 2945605af85..097813f03a5 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmMessageCallProcessorBaseTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/contracts/execution/MirrorEvmMessageCallProcessorBaseTest.java @@ -40,7 +40,6 @@ import com.hedera.services.store.contracts.precompile.utils.PrecompilePricingUtils; import com.hedera.services.txns.crypto.AbstractAutoCreationLogic; import com.hedera.services.txns.util.PrngLogic; -import com.swirlds.common.crypto.Hash; import com.swirlds.common.utility.CommonUtils; import java.time.Instant; import java.util.Map; @@ -63,8 +62,6 @@ public abstract class MirrorEvmMessageCallProcessorBaseTest { private static final byte[] WELL_KNOWN_HASH_BYTE_ARRAY = CommonUtils.unhex( "65386630386164632d356537632d343964342d623437372d62636134346538386338373133633038316162372d6163"); - private static final Hash WELL_KNOWN_HASH = new Hash(CommonUtils.unhex( - "65386630386164632d356537632d343964342d623437372d62636134346538386338373133633038316162372d616300")); private static MockedStatic contextMockedStatic; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/store/contract/precompile/PrngSystemPrecompiledContractTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/store/contract/precompile/PrngSystemPrecompiledContractTest.java index 8def9e3b05b..b20e6780a6e 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/store/contract/precompile/PrngSystemPrecompiledContractTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/evm/store/contract/precompile/PrngSystemPrecompiledContractTest.java @@ -31,7 +31,6 @@ import com.hedera.services.txns.util.PrngLogic; import com.hederahashgraph.api.proto.java.HederaFunctionality; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; -import com.swirlds.common.crypto.Hash; import com.swirlds.common.utility.CommonUtils; import java.time.Instant; import java.util.Optional; @@ -52,8 +51,6 @@ class PrngSystemPrecompiledContractTest { private static final byte[] WELL_KNOWN_HASH_BYTE_ARRAY = CommonUtils.unhex( "65386630386164632d356537632d343964342d623437372d62636134346538386338373133633038316162372d6163"); - private static final Hash WELL_KNOWN_HASH = new Hash(CommonUtils.unhex( - "65386630386164632d356537632d343964342d623437372d62636134346538386338373133633038316162372d616300")); private final Instant consensusNow = Instant.ofEpochSecond(123456789L); @Mock From 372f3d0ac293dea1b8135c5299f660458819c7ea Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Thu, 19 Sep 2024 13:57:27 +0300 Subject: [PATCH 06/13] nit: resolve PR comments Signed-off-by: IvanKavaldzhiev --- .../convert/SemanticVersionConvertor.java | 36 +++++++++++++++++-- .../properties/MirrorNodeEvmProperties.java | 5 +-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java index 0db70d28cf2..fa51b08e30d 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java @@ -18,15 +18,45 @@ import com.hedera.hapi.node.base.SemanticVersion; import jakarta.inject.Named; +import java.util.regex.Pattern; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.core.convert.converter.Converter; @Named @ConfigurationPropertiesBinding public class SemanticVersionConvertor implements Converter { + /** Arbitrary limit to prevent stack overflow when parsing unrealistically long versions. */ + private static final int MAX_VERSION_LENGTH = 100; + /** From */ + // suppress the warning that the regular expression is too complicated + @SuppressWarnings({"java:S5843", "java:S5998"}) + private static final Pattern SEMVER_SPEC_REGEX = Pattern.compile( + "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)" + + "(?:\\." + + "(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)" + + "*))?$"); + @Override - public SemanticVersion convert(String source) { - final var versions = source.split("\\."); - return new SemanticVersion(0, Integer.parseInt(versions[1]), 0, "", ""); + public SemanticVersion convert(String value) { + if (value.length() > MAX_VERSION_LENGTH) { + throw new IllegalArgumentException("Semantic version '" + value + "' is too long"); + } + + final var matcher = SEMVER_SPEC_REGEX.matcher(value); + if (matcher.matches()) { + final var builder = SemanticVersion.newBuilder() + .major(Integer.parseInt(matcher.group(1))) + .minor(Integer.parseInt(matcher.group(2))) + .patch(Integer.parseInt(matcher.group(3))); + if (matcher.group(4) != null) { + builder.pre(matcher.group(4)); + } + if (matcher.group(5) != null) { + builder.build(matcher.group(5)); + } + return builder.build(); + } else { + throw new IllegalArgumentException("'" + value + "' is not a valid semantic version"); + } } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java index 1deb37b018b..799410f78f3 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java @@ -22,6 +22,7 @@ import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_38; import static com.hedera.mirror.web3.evm.config.EvmConfiguration.EVM_VERSION_0_46; import static com.swirlds.common.utility.CommonUtils.unhex; +import static com.swirlds.state.spi.HapiUtils.SEMANTIC_VERSION_COMPARATOR; import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.mirror.common.domain.entity.EntityType; @@ -199,7 +200,7 @@ public boolean isCreate2Enabled() { @Override public boolean allowCallsToNonContractAccounts() { - return getSemanticEvmVersion().minor() > 46; + return SEMANTIC_VERSION_COMPARATOR.compare(getSemanticEvmVersion(), EVM_VERSION_0_46) >= 0; } @Override @@ -209,7 +210,7 @@ public Set
grandfatherContracts() { @Override public boolean callsToNonExistingEntitiesEnabled(Address target) { - return !(getSemanticEvmVersion().minor() < 46 + return !(SEMANTIC_VERSION_COMPARATOR.compare(getSemanticEvmVersion(), EVM_VERSION_0_46) < 0 || !allowCallsToNonContractAccounts() || grandfatherContracts().contains(target)); } From e1b63b65aee943481b0ef69726e3744d318631ec Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Thu, 19 Sep 2024 18:22:19 +0300 Subject: [PATCH 07/13] feat: add implementation for AccountReadableKVState Signed-off-by: IvanKavaldzhiev --- .../state/AccountReadableKVState.java | 278 ++++++++++ .../state/CommonEntityAccessor.java | 57 ++ .../state/AccountReadableKVStateTest.java | 502 ++++++++++++++++++ .../state/CommonEntityAccessorTest.java | 96 ++++ 4 files changed, 933 insertions(+) create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java create mode 100644 hedera-mirror-web3/src/main/java/com/hedera/modularized/state/CommonEntityAccessor.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/modularized/state/AccountReadableKVStateTest.java create mode 100644 hedera-mirror-web3/src/test/java/com/hedera/modularized/state/CommonEntityAccessorTest.java diff --git a/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java new file mode 100644 index 00000000000..a3a16ddecf7 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java @@ -0,0 +1,278 @@ +/* + * 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.modularized.state; + +import static com.hedera.hapi.node.state.token.codec.AccountProtoCodec.STAKED_ID_UNSET; +import static com.hedera.mirror.common.domain.entity.EntityType.CONTRACT; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.AccountID.AccountOneOfType; +import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.NftID; +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.AccountApprovalForAllAllowance; +import com.hedera.hapi.node.state.token.AccountCryptoAllowance; +import com.hedera.hapi.node.state.token.AccountFungibleTokenAllowance; +import com.hedera.mirror.common.domain.entity.CryptoAllowance; +import com.hedera.mirror.common.domain.entity.Entity; +import com.hedera.mirror.common.domain.entity.NftAllowance; +import com.hedera.mirror.common.domain.entity.TokenAllowance; +import com.hedera.mirror.web3.common.ContractCallContext; +import com.hedera.mirror.web3.repository.AccountBalanceRepository; +import com.hedera.mirror.web3.repository.CryptoAllowanceRepository; +import com.hedera.mirror.web3.repository.NftAllowanceRepository; +import com.hedera.mirror.web3.repository.NftRepository; +import com.hedera.mirror.web3.repository.TokenAccountRepository; +import com.hedera.mirror.web3.repository.TokenAllowanceRepository; +import com.hedera.mirror.web3.repository.projections.TokenAccountAssociationsCount; +import com.hedera.mirror.web3.utils.Suppliers; +import com.hedera.pbj.runtime.OneOf; +import com.hedera.pbj.runtime.ParseException; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.spi.ReadableKVStateBase; +import jakarta.inject.Named; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import lombok.extern.java.Log; +import org.jetbrains.annotations.NotNull; + +@Named +@Log +public class AccountReadableKVState extends ReadableKVStateBase { + private static final long DEFAULT_AUTO_RENEW_PERIOD = 7776000L; + private static final String KEY = "ACCOUNTS"; + private static final Long ZERO_BALANCE = 0L; + + private final CommonEntityAccessor commonEntityAccessor; + private final NftAllowanceRepository nftAllowanceRepository; + private final NftRepository nftRepository; + private final TokenAllowanceRepository tokenAllowanceRepository; + private final CryptoAllowanceRepository cryptoAllowanceRepository; + private final TokenAccountRepository tokenAccountRepository; + private final AccountBalanceRepository accountBalanceRepository; + + /** + * Create a new AccountReadableKVState. + */ + public AccountReadableKVState( + CommonEntityAccessor commonEntityAccessor, + NftAllowanceRepository nftAllowanceRepository, + NftRepository nftRepository, + TokenAllowanceRepository tokenAllowanceRepository, + CryptoAllowanceRepository cryptoAllowanceRepository, + TokenAccountRepository tokenAccountRepository, + AccountBalanceRepository accountBalanceRepository) { + super(KEY); + this.commonEntityAccessor = commonEntityAccessor; + this.nftAllowanceRepository = nftAllowanceRepository; + this.nftRepository = nftRepository; + this.tokenAllowanceRepository = tokenAllowanceRepository; + this.cryptoAllowanceRepository = cryptoAllowanceRepository; + this.tokenAccountRepository = tokenAccountRepository; + this.accountBalanceRepository = accountBalanceRepository; + } + + @Override + protected Account readFromDataSource(@NotNull AccountID key) { + final var timestampValue = ContractCallContext.get().getTimestamp(); + final Optional timestamp = timestampValue != 0 ? Optional.of(timestampValue) : Optional.empty(); + return commonEntityAccessor + .get(key, timestamp) + .map(entity -> accountFromEntity(entity, timestamp)) + .orElse(null); + } + + @Override + protected @NotNull Iterator iterateFromDataSource() { + return Collections.emptyIterator(); + } + + @Override + public long size() { + return 0; + } + + private com.hedera.hapi.node.state.token.Account accountFromEntity(Entity entity, final Optional timestamp) { + var tokenAccountBalances = getNumberOfAllAndPositiveBalanceTokenAssociations(entity.getId(), timestamp); + + return new com.hedera.hapi.node.state.token.Account( + new com.hedera.hapi.node.base.AccountID( + entity.getShard(), + entity.getRealm(), + new OneOf<>(AccountOneOfType.ACCOUNT_NUM, entity.getNum())), + entity.getEvmAddress() != null && entity.getEvmAddress().length > 0 + ? Bytes.wrap(entity.getEvmAddress()) + : Bytes.EMPTY, + parseKey(entity), + TimeUnit.SECONDS.convert(entity.getEffectiveExpiration(), TimeUnit.NANOSECONDS), + getAccountBalance(entity, timestamp), + entity.getMemo(), + Optional.ofNullable(entity.getDeleted()).orElse(false), + 0L, + 0L, + STAKED_ID_UNSET, + true, + entity.getReceiverSigRequired() != null ? entity.getReceiverSigRequired() : false, + TokenID.DEFAULT, + NftID.DEFAULT, + 0L, + getOwnedNfts(entity.getId(), timestamp), + Optional.ofNullable(entity.getMaxAutomaticTokenAssociations()).orElse(0), + 0, + tokenAccountBalances.get().all(), + CONTRACT.equals(entity.getType()), + tokenAccountBalances.get().positive(), + entity.getEthereumNonce() != null ? entity.getEthereumNonce() : 0L, + 0L, + new com.hedera.hapi.node.base.AccountID( + entity.getShard(), + entity.getRealm(), + new OneOf<>(AccountOneOfType.ACCOUNT_NUM, entity.getAutoRenewAccountId())), + entity.getAutoRenewPeriod() != null ? entity.getAutoRenewPeriod() : DEFAULT_AUTO_RENEW_PERIOD, + 0, + getCryptoAllowances(entity.getId(), timestamp), + getApproveForAllNfts(entity.getId(), timestamp), + getFungibleTokenAllowances(entity.getId(), timestamp), + 0, + false, + Bytes.EMPTY, + PendingAirdropId.DEFAULT); + } + + private Long getOwnedNfts(Long accountId, final Optional timestamp) { + return timestamp + .map(aLong -> nftRepository.countByAccountIdAndTimestampNotDeleted(accountId, aLong)) + .orElseGet(() -> nftRepository.countByAccountIdNotDeleted(accountId)); + } + + /** + * Determines account balance based on block context. + * + * Non-historical Call: + * Get the balance from entity.getBalance() + * Historical Call: + * If the entity creation is after the passed timestamp - return 0L (the entity was not created) + * Else get the balance from the historical query `findHistoricalAccountBalanceUpToTimestamp` + */ + private Long getAccountBalance(Entity entity, final Optional timestamp) { + if (timestamp.isPresent()) { + Long createdTimestamp = entity.getCreatedTimestamp(); + if (createdTimestamp == null || timestamp.get() >= createdTimestamp) { + return accountBalanceRepository + .findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get()) + .orElse(ZERO_BALANCE); + } else { + return ZERO_BALANCE; + } + } + + return entity.getBalance() != null ? entity.getBalance() : ZERO_BALANCE; + } + + private List getCryptoAllowances(Long ownerId, final Optional timestamp) { + final var cryptoAllowances = timestamp.isPresent() + ? cryptoAllowanceRepository.findByOwnerAndTimestamp(ownerId, timestamp.get()) + : cryptoAllowanceRepository.findByOwner(ownerId); + + return cryptoAllowances.stream().map(this::convertCryptoAllowance).collect(Collectors.toList()); + } + + private AccountCryptoAllowance convertCryptoAllowance(final CryptoAllowance cryptoAllowance) { + return new AccountCryptoAllowance( + new com.hedera.hapi.node.base.AccountID( + 0L, 0L, new OneOf<>(AccountOneOfType.ACCOUNT_NUM, cryptoAllowance.getSpender())), + cryptoAllowance.getAmount()); + } + + private List getFungibleTokenAllowances( + Long ownerId, final Optional timestamp) { + final var fungibleAllowances = timestamp.isPresent() + ? tokenAllowanceRepository.findByOwnerAndTimestamp(ownerId, timestamp.get()) + : tokenAllowanceRepository.findByOwner(ownerId); + + return fungibleAllowances.stream().map(this::convertFungibleAllowance).collect(Collectors.toList()); + } + + private AccountFungibleTokenAllowance convertFungibleAllowance(final TokenAllowance tokenAllowance) { + return new AccountFungibleTokenAllowance( + new TokenID(0L, 0L, tokenAllowance.getTokenId()), + new com.hedera.hapi.node.base.AccountID( + 0L, 0L, new OneOf<>(AccountOneOfType.ACCOUNT_NUM, tokenAllowance.getSpender())), + tokenAllowance.getAmount()); + } + + private List getApproveForAllNfts(Long ownerId, final Optional timestamp) { + final var nftAllowances = timestamp.isPresent() + ? nftAllowanceRepository.findByOwnerAndTimestampAndApprovedForAllIsTrue(ownerId, timestamp.get()) + : nftAllowanceRepository.findByOwnerAndApprovedForAllIsTrue(ownerId); + + return nftAllowances.stream().map(this::convertNftAllowance).collect(Collectors.toList()); + } + + private AccountApprovalForAllAllowance convertNftAllowance(final NftAllowance nftAllowance) { + return new AccountApprovalForAllAllowance( + new TokenID(0L, 0L, nftAllowance.getTokenId()), + new com.hedera.hapi.node.base.AccountID( + 0L, 0L, new OneOf<>(AccountOneOfType.ACCOUNT_NUM, nftAllowance.getSpender()))); + } + + private Supplier getNumberOfAllAndPositiveBalanceTokenAssociations( + long accountId, final Optional timestamp) { + var counts = timestamp + .map(t -> tokenAccountRepository.countByAccountIdAndTimestampAndAssociatedGroupedByBalanceIsPositive( + accountId, t)) + .orElseGet(() -> + tokenAccountRepository.countByAccountIdAndAssociatedGroupedByBalanceIsPositive(accountId)); + int all = 0; + int positive = 0; + + for (TokenAccountAssociationsCount count : counts) { + if (count.getIsPositiveBalance()) { + positive = count.getTokenCount(); + } + all += count.getTokenCount(); + } + + final var allAggregated = all; + final var positiveAggregated = positive; + + return Suppliers.memoize(() -> new TokenAccountBalances(allAggregated, positiveAggregated)); + } + + private com.hedera.hapi.node.base.Key parseKey(Entity entity) { + final byte[] keyBytes = entity.getKey(); + + try { + if (keyBytes != null && keyBytes.length > 0) { + return Key.PROTOBUF.parse(Bytes.wrap(keyBytes)); + } + } catch (final ParseException e) { + log.warning("Failed to parse key for account " + entity.getId()); + } + + return Key.DEFAULT; + } + + private record TokenAccountBalances(int all, int positive) {} +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/CommonEntityAccessor.java b/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/CommonEntityAccessor.java new file mode 100644 index 00000000000..8e5f93922f8 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/CommonEntityAccessor.java @@ -0,0 +1,57 @@ +/* + * 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.modularized.state; + +import static com.hedera.services.utils.EntityIdUtils.entityIdFromId; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.mirror.common.domain.entity.Entity; +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.web3.repository.EntityRepository; +import com.hedera.services.store.models.Id; +import jakarta.annotation.Nonnull; +import jakarta.inject.Named; +import java.util.Optional; +import lombok.RequiredArgsConstructor; + +@Named +@RequiredArgsConstructor +public class CommonEntityAccessor { + private final EntityRepository entityRepository; + + public @Nonnull Optional get(@Nonnull AccountID accountID, final Optional timestamp) { + if (accountID.hasAccountNum()) { + return getEntityByMirrorAddressAndTimestamp( + entityIdFromId(new Id(accountID.shardNum(), accountID.realmNum(), accountID.accountNum())), + timestamp); + } else { + return getEntityByEvmAddressAndTimestamp(accountID.alias().toByteArray(), timestamp); + } + } + + private Optional getEntityByMirrorAddressAndTimestamp(EntityId entityId, final Optional timestamp) { + return timestamp + .map(t -> entityRepository.findActiveByIdAndTimestamp(entityId.getId(), t)) + .orElseGet(() -> entityRepository.findByIdAndDeletedIsFalse(entityId.getId())); + } + + private Optional getEntityByEvmAddressAndTimestamp(byte[] addressBytes, final Optional timestamp) { + return timestamp + .map(t -> entityRepository.findActiveByEvmAddressAndTimestamp(addressBytes, t)) + .orElseGet(() -> entityRepository.findByEvmAddressAndDeletedIsFalse(addressBytes)); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/modularized/state/AccountReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/modularized/state/AccountReadableKVStateTest.java new file mode 100644 index 00000000000..121d9a4f7c3 --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/modularized/state/AccountReadableKVStateTest.java @@ -0,0 +1,502 @@ +/* + * 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.modularized.state; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.AccountID.AccountOneOfType; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.AccountApprovalForAllAllowance; +import com.hedera.hapi.node.state.token.AccountCryptoAllowance; +import com.hedera.hapi.node.state.token.AccountFungibleTokenAllowance; +import com.hedera.mirror.common.domain.entity.AbstractEntity; +import com.hedera.mirror.common.domain.entity.CryptoAllowance; +import com.hedera.mirror.common.domain.entity.Entity; +import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.common.domain.entity.EntityType; +import com.hedera.mirror.common.domain.entity.NftAllowance; +import com.hedera.mirror.common.domain.entity.TokenAllowance; +import com.hedera.mirror.web3.common.ContractCallContext; +import com.hedera.mirror.web3.evm.store.accessor.AccountDatabaseAccessor; +import com.hedera.mirror.web3.repository.AccountBalanceRepository; +import com.hedera.mirror.web3.repository.CryptoAllowanceRepository; +import com.hedera.mirror.web3.repository.NftAllowanceRepository; +import com.hedera.mirror.web3.repository.NftRepository; +import com.hedera.mirror.web3.repository.TokenAccountRepository; +import com.hedera.mirror.web3.repository.TokenAllowanceRepository; +import com.hedera.mirror.web3.repository.projections.TokenAccountAssociationsCount; +import com.hedera.pbj.runtime.OneOf; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class AccountReadableKVStateTest { + private static final long SHARD = 0L; + private static final long REALM = 1L; + private static final long NUM = 1252L; + private static final AccountID ACCOUNT_ID = + new AccountID(SHARD, REALM, new OneOf<>(AccountOneOfType.ACCOUNT_NUM, NUM)); + private static final long EXPIRATION_TIMESTAMP = 2_000_000_000L; + private static final long BALANCE = 3L; + private static final long AUTO_RENEW_PERIOD = 4_000_000_000L; + private static final EntityId PROXY_ACCOUNT_ID = EntityId.of(SHARD, REALM, 5L); + private static final int MAX_AUTOMATIC_TOKEN_ASSOCIATIONS = 6; + private static final Optional timestamp = Optional.of(1234L); + private static final int POSITIVE_BALANCES = 7; + private static final int NEGATIVE_BALANCES = 8; + private static final List associationsCount = Arrays.asList( + new TokenAccountAssociationsCount() { + @Override + public Integer getTokenCount() { + return POSITIVE_BALANCES; + } + + @Override + public boolean getIsPositiveBalance() { + return true; + } + }, + new TokenAccountAssociationsCount() { + @Override + public Integer getTokenCount() { + return NEGATIVE_BALANCES; + } + + @Override + public boolean getIsPositiveBalance() { + return false; + } + }); + private static MockedStatic contextMockedStatic; + private Entity entity; + + @InjectMocks + private AccountReadableKVState accountReadableKVState; + + @Mock + private CommonEntityAccessor commonEntityAccessor; + + @Mock + private NftAllowanceRepository nftAllowanceRepository; + + @Mock + private NftRepository nftRepository; + + @Mock + private TokenAllowanceRepository tokenAllowanceRepository; + + @Mock + private CryptoAllowanceRepository cryptoAllowanceRepository; + + @Mock + private AccountBalanceRepository accountBalanceRepository; + + @Mock + private TokenAccountRepository tokenAccountRepository; + + @Spy + private ContractCallContext contractCallContext; + + @BeforeAll + static void initStaticMocks() { + contextMockedStatic = mockStatic(ContractCallContext.class); + } + + @AfterAll + static void closeStaticMocks() { + contextMockedStatic.close(); + } + + @BeforeEach + void setup() { + entity = new Entity(); + entity.setId(NUM); + entity.setCreatedTimestamp(timestamp.get()); + entity.setShard(SHARD); + entity.setRealm(REALM); + entity.setNum(NUM); + entity.setExpirationTimestamp(EXPIRATION_TIMESTAMP); + entity.setBalance(BALANCE); + entity.setDeleted(false); + entity.setAutoRenewPeriod(AUTO_RENEW_PERIOD); + entity.setProxyAccountId(PROXY_ACCOUNT_ID); + entity.setMaxAutomaticTokenAssociations(MAX_AUTOMATIC_TOKEN_ASSOCIATIONS); + entity.setType(EntityType.ACCOUNT); + + contextMockedStatic.when(ContractCallContext::get).thenReturn(contractCallContext); + } + + @Test + void accountFieldsMatchEntityFields() { + when(contractCallContext.getTimestamp()).thenReturn(0L); + when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); + assertThat(accountReadableKVState.get(ACCOUNT_ID)).satisfies(account -> assertThat(account) + .returns( + new AccountID( + entity.getShard(), + entity.getRealm(), + new OneOf<>(AccountOneOfType.ACCOUNT_NUM, entity.getNum())), + com.hedera.hapi.node.state.token.Account::accountId) + .returns( + TimeUnit.SECONDS.convert(entity.getEffectiveExpiration(), TimeUnit.NANOSECONDS), + Account::expirationSecond) + .returns(entity.getBalance(), Account::tinybarBalance) + .returns(entity.getAutoRenewPeriod(), Account::autoRenewSeconds) + .returns(entity.getMaxAutomaticTokenAssociations(), Account::maxAutoAssociations)); + } + + @Test + void whenExpirationTimestampIsNullThenExpiryIsBasedOnCreatedAndRenewTimestamps() { + when(contractCallContext.getTimestamp()).thenReturn(0L); + when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); + entity.setExpirationTimestamp(null); + entity.setCreatedTimestamp(987_000_000L); + long expectedExpiry = TimeUnit.SECONDS.convert(entity.getCreatedTimestamp(), TimeUnit.NANOSECONDS) + + entity.getAutoRenewPeriod(); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(expectedExpiry, Account::expirationSecond)); + } + + @Test + void useDefaultValuesWhenFieldsAreNull() { + when(contractCallContext.getTimestamp()).thenReturn(0L); + when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); + entity.setExpirationTimestamp(null); + entity.setCreatedTimestamp(null); + entity.setAutoRenewPeriod(null); + entity.setMaxAutomaticTokenAssociations(null); + entity.setBalance(null); + entity.setDeleted(null); + entity.setProxyAccountId(null); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)).satisfies(account -> assertThat(account) + .returns( + TimeUnit.SECONDS.convert(AbstractEntity.DEFAULT_EXPIRY_TIMESTAMP, TimeUnit.NANOSECONDS), + Account::expirationSecond) + .returns(0L, Account::numberOwnedNfts) + .returns(false, Account::deleted) + .returns(AccountDatabaseAccessor.DEFAULT_AUTO_RENEW_PERIOD, Account::autoRenewSeconds) + .returns(0, Account::maxAutoAssociations) + .returns(0, Account::usedAutoAssociations)); + } + + @Test + void accountOwnedNftsMatchesValueFromRepository() { + when(contractCallContext.getTimestamp()).thenReturn(0L); + when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); + long ownedNfts = 20; + when(nftRepository.countByAccountIdNotDeleted(any())).thenReturn(ownedNfts); + + verify(nftRepository, never()).countByAccountIdNotDeleted(entity.getId()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(ownedNfts, Account::numberOwnedNfts)); + + verify(nftRepository).countByAccountIdNotDeleted(entity.getId()); + } + + @Test + void accountOwnedNftsMatchesValueFromRepositoryHistorical() { + when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); + long ownedNfts = 20; + when(nftRepository.countByAccountIdAndTimestampNotDeleted(entity.getId(), timestamp.get())) + .thenReturn(ownedNfts); + + verify(nftRepository, never()).countByAccountIdAndTimestampNotDeleted(entity.getId(), timestamp.get()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(ownedNfts, Account::numberOwnedNfts)); + + verify(nftRepository).countByAccountIdAndTimestampNotDeleted(entity.getId(), timestamp.get()); + } + + @Test + void accountBalanceMatchesValueFromRepositoryHistorical() { + when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); + long balance = 20; + when(accountBalanceRepository.findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get())) + .thenReturn(Optional.of(balance)); + + verify(accountBalanceRepository, never()) + .findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(balance, Account::tinybarBalance)); + + verify(accountBalanceRepository).findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get()); + } + + @Test + void accountBalanceBeforeAccountCreation() { + when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + entity.setCreatedTimestamp(timestamp.get() + 1); + when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); + long balance = 0; + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(balance, Account::tinybarBalance)); + } + + @Test + void accountBalanceIsZeroHistorical() { + when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + entity.setCreatedTimestamp(timestamp.get() - 1); + when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); + long balance = 0; + when(accountBalanceRepository.findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get())) + .thenReturn(Optional.of(balance)); + + verify(accountBalanceRepository, never()) + .findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(balance, Account::tinybarBalance)); + + verify(accountBalanceRepository).findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get()); + } + + @Test + void accountBalanceWhenCreatedTimestampIsNull() { + when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); + long balance = 20; + entity.setCreatedTimestamp(null); + when(accountBalanceRepository.findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get())) + .thenReturn(Optional.of(balance)); + + verify(accountBalanceRepository, never()) + .findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(balance, Account::tinybarBalance)); + + verify(accountBalanceRepository).findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get()); + } + + @Test + void cryptoAllowancesMatchValuesFromRepository() { + when(contractCallContext.getTimestamp()).thenReturn(0L); + when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); + CryptoAllowance firstAllowance = new CryptoAllowance(); + firstAllowance.setSpender(123L); + firstAllowance.setOwner(entity.getId()); + firstAllowance.setAmount(50L); + + CryptoAllowance secondAllowance = new CryptoAllowance(); + secondAllowance.setSpender(234L); + secondAllowance.setOwner(entity.getId()); + secondAllowance.setAmount(60L); + + when(cryptoAllowanceRepository.findByOwner(anyLong())) + .thenReturn(Arrays.asList(firstAllowance, secondAllowance)); + + List cryptoAllowances = new ArrayList<>(); + cryptoAllowances.add( + new AccountCryptoAllowance(getAccountId(firstAllowance.getSpender()), firstAllowance.getAmount())); + cryptoAllowances.add( + new AccountCryptoAllowance(getAccountId(secondAllowance.getSpender()), secondAllowance.getAmount())); + + verify(cryptoAllowanceRepository, never()).findByOwner(entity.getId()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(cryptoAllowances, Account::cryptoAllowances)); + + verify(cryptoAllowanceRepository).findByOwner(entity.getId()); + } + + @Test + void cryptoAllowancesMatchValuesFromRepositoryHistorical() { + when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); + CryptoAllowance firstAllowance = new CryptoAllowance(); + firstAllowance.setSpender(123L); + firstAllowance.setOwner(entity.getId()); + firstAllowance.setAmount(50L); + + CryptoAllowance secondAllowance = new CryptoAllowance(); + secondAllowance.setSpender(234L); + secondAllowance.setOwner(entity.getId()); + secondAllowance.setAmount(60L); + + when(cryptoAllowanceRepository.findByOwnerAndTimestamp(entity.getId(), timestamp.get())) + .thenReturn(Arrays.asList(firstAllowance, secondAllowance)); + + List cryptoAllowances = new ArrayList<>(); + cryptoAllowances.add( + new AccountCryptoAllowance(getAccountId(firstAllowance.getSpender()), firstAllowance.getAmount())); + cryptoAllowances.add( + new AccountCryptoAllowance(getAccountId(secondAllowance.getSpender()), secondAllowance.getAmount())); + + verify(cryptoAllowanceRepository, never()).findByOwnerAndTimestamp(entity.getId(), timestamp.get()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(cryptoAllowances, Account::cryptoAllowances)); + + verify(cryptoAllowanceRepository).findByOwnerAndTimestamp(entity.getId(), timestamp.get()); + } + + @Test + void fungibleTokenAllowancesMatchValuesFromRepository() { + when(contractCallContext.getTimestamp()).thenReturn(0L); + when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); + TokenAllowance firstAllowance = new TokenAllowance(); + firstAllowance.setOwner(entity.getId()); + firstAllowance.setTokenId(15L); + firstAllowance.setSpender(123L); + firstAllowance.setAmount(50L); + + TokenAllowance secondAllowance = new TokenAllowance(); + secondAllowance.setOwner(entity.getId()); + secondAllowance.setTokenId(16L); + secondAllowance.setSpender(234L); + secondAllowance.setAmount(60L); + + when(tokenAllowanceRepository.findByOwner(entity.getId())) + .thenReturn(Arrays.asList(firstAllowance, secondAllowance)); + + List tokenAllowances = new ArrayList<>(); + tokenAllowances.add(new AccountFungibleTokenAllowance( + new TokenID(0L, 0L, firstAllowance.getTokenId()), + getAccountId(firstAllowance.getSpender()), + firstAllowance.getAmount())); + tokenAllowances.add(new AccountFungibleTokenAllowance( + new TokenID(0L, 0L, secondAllowance.getTokenId()), + getAccountId(secondAllowance.getSpender()), + secondAllowance.getAmount())); + + verify(tokenAllowanceRepository, never()).findByOwner(entity.getId()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(tokenAllowances, Account::tokenAllowances)); + + verify(tokenAllowanceRepository).findByOwner(entity.getId()); + } + + @Test + void fungibleTokenAllowancesMatchValuesFromRepositoryHistorical() { + when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); + TokenAllowance firstAllowance = new TokenAllowance(); + firstAllowance.setOwner(entity.getId()); + firstAllowance.setTokenId(15L); + firstAllowance.setSpender(123L); + firstAllowance.setAmount(50L); + + TokenAllowance secondAllowance = new TokenAllowance(); + secondAllowance.setOwner(entity.getId()); + secondAllowance.setTokenId(16L); + secondAllowance.setSpender(234L); + secondAllowance.setAmount(60L); + + when(tokenAllowanceRepository.findByOwnerAndTimestamp(entity.getId(), timestamp.get())) + .thenReturn(Arrays.asList(firstAllowance, secondAllowance)); + + List tokenAllowances = new ArrayList<>(); + tokenAllowances.add(new AccountFungibleTokenAllowance( + new TokenID(0L, 0L, firstAllowance.getTokenId()), + getAccountId(firstAllowance.getSpender()), + firstAllowance.getAmount())); + tokenAllowances.add(new AccountFungibleTokenAllowance( + new TokenID(0L, 0L, secondAllowance.getTokenId()), + getAccountId(secondAllowance.getSpender()), + secondAllowance.getAmount())); + + verify(tokenAllowanceRepository, never()).findByOwnerAndTimestamp(entity.getId(), timestamp.get()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)) + .satisfies(account -> assertThat(account).returns(tokenAllowances, Account::tokenAllowances)); + + verify(tokenAllowanceRepository).findByOwnerAndTimestamp(entity.getId(), timestamp.get()); + } + + @Test + void approveForAllNftsMatchValuesFromRepository() { + when(contractCallContext.getTimestamp()).thenReturn(0L); + when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); + NftAllowance firstAllowance = new NftAllowance(); + firstAllowance.setOwner(entity.getId()); + firstAllowance.setTokenId(15L); + firstAllowance.setSpender(123L); + + NftAllowance secondAllowance = new NftAllowance(); + secondAllowance.setOwner(entity.getId()); + secondAllowance.setTokenId(16L); + secondAllowance.setSpender(234L); + + when(nftAllowanceRepository.findByOwnerAndApprovedForAllIsTrue(entity.getId())) + .thenReturn(Arrays.asList(firstAllowance, secondAllowance)); + + List approveForAllAllowances = new ArrayList<>(); + approveForAllAllowances.add(new AccountApprovalForAllAllowance( + new TokenID(0L, 0L, firstAllowance.getTokenId()), getAccountId(firstAllowance.getSpender()))); + approveForAllAllowances.add(new AccountApprovalForAllAllowance( + new TokenID(0L, 0L, secondAllowance.getTokenId()), getAccountId(secondAllowance.getSpender()))); + + verify(nftAllowanceRepository, never()).findByOwnerAndApprovedForAllIsTrue(entity.getId()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)).satisfies(account -> assertThat(account) + .returns(approveForAllAllowances, Account::approveForAllNftAllowances)); + + verify(nftAllowanceRepository).findByOwnerAndApprovedForAllIsTrue(entity.getId()); + } + + private AccountID getAccountId(final Long num) { + return new AccountID(0L, 0L, new OneOf<>(AccountOneOfType.ACCOUNT_NUM, num)); + } + + @Test + void numTokenAssociationsAndNumPositiveBalancesMatchValuesFromRepository() { + when(contractCallContext.getTimestamp()).thenReturn(0L); + when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); + when(tokenAccountRepository.countByAccountIdAndAssociatedGroupedByBalanceIsPositive(anyLong())) + .thenReturn(associationsCount); + + verify(tokenAccountRepository, never()).countByAccountIdAndAssociatedGroupedByBalanceIsPositive(entity.getId()); + + assertThat(accountReadableKVState.get(ACCOUNT_ID)).satisfies(account -> assertThat(account) + .returns(POSITIVE_BALANCES + NEGATIVE_BALANCES, Account::numberAssociations) + .returns(POSITIVE_BALANCES, Account::numberPositiveBalances)); + + verify(tokenAccountRepository, times(1)) + .countByAccountIdAndAssociatedGroupedByBalanceIsPositive(entity.getId()); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/modularized/state/CommonEntityAccessorTest.java b/hedera-mirror-web3/src/test/java/com/hedera/modularized/state/CommonEntityAccessorTest.java new file mode 100644 index 00000000000..0f57001348c --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/modularized/state/CommonEntityAccessorTest.java @@ -0,0 +1,96 @@ +/* + * 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.modularized.state; + +import static com.hedera.services.utils.EntityIdUtils.entityIdFromId; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.AccountID.AccountOneOfType; +import com.hedera.mirror.common.domain.entity.Entity; +import com.hedera.mirror.web3.repository.EntityRepository; +import com.hedera.pbj.runtime.OneOf; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.hedera.services.store.models.Id; +import java.util.Optional; +import org.hyperledger.besu.datatypes.Address; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CommonEntityAccessorTest { + private static final String HEX = "0x00000000000000000000000000000000000004e4"; + private static final String ALIAS_HEX = "0x67d8d32e9bf1a9968a5ff53b87d777aa8ebbee69"; + private static final Address ALIAS_ADDRESS = Address.fromHexString(ALIAS_HEX); + private static final AccountID ACCOUNT_ALIAS = + new AccountID(0L, 1L, new OneOf<>(AccountOneOfType.ALIAS, Bytes.wrap(ALIAS_ADDRESS.toArray()))); + private static final Long NUM = 1252L; + private static final AccountID ACCOUNT_ID = new AccountID(0L, 1L, new OneOf<>(AccountOneOfType.ACCOUNT_NUM, NUM)); + private static final Optional timestamp = Optional.of(1234L); + private static final Entity mockEntity = mock(Entity.class); + + @InjectMocks + private CommonEntityAccessor commonEntityAccessor; + + @Mock + private EntityRepository entityRepository; + + @Test + void getEntityByAddress() { + final var id = new Id(0L, 1L, NUM); + when(entityRepository.findByIdAndDeletedIsFalse(entityIdFromId(id).getId())) + .thenReturn(Optional.of(mockEntity)); + + assertThat(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())) + .hasValueSatisfying(entity -> assertThat(entity).isEqualTo(mockEntity)); + } + + @Test + void getEntityByAddressHistorical() { + final var id = new Id(0L, 1L, NUM); + when(entityRepository.findActiveByIdAndTimestamp(entityIdFromId(id).getId(), timestamp.get())) + .thenReturn(Optional.of(mockEntity)); + + assertThat(commonEntityAccessor.get(ACCOUNT_ID, timestamp)) + .hasValueSatisfying(entity -> assertThat(entity).isEqualTo(mockEntity)); + } + + @Test + void getEntityByAlias() { + when(entityRepository.findByEvmAddressAndDeletedIsFalse( + ACCOUNT_ALIAS.alias().toByteArray())) + .thenReturn(Optional.of(mockEntity)); + + assertThat(commonEntityAccessor.get(ACCOUNT_ALIAS, Optional.empty())) + .hasValueSatisfying(entity -> assertThat(entity).isEqualTo(mockEntity)); + } + + @Test + void getEntityByAliasHistorical() { + when(entityRepository.findActiveByEvmAddressAndTimestamp( + ACCOUNT_ALIAS.alias().toByteArray(), timestamp.get())) + .thenReturn(Optional.of(mockEntity)); + + assertThat(commonEntityAccessor.get(ACCOUNT_ALIAS, timestamp)) + .hasValueSatisfying(entity -> assertThat(entity).isEqualTo(mockEntity)); + } +} From 7ef1c124119853afce3314674533931b41bf4ff7 Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Thu, 19 Sep 2024 18:40:25 +0300 Subject: [PATCH 08/13] nit: add javadoc and enhance code style Signed-off-by: IvanKavaldzhiev --- .../state/AccountReadableKVState.java | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java index a3a16ddecf7..5b4d558dd81 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java @@ -40,8 +40,6 @@ import com.hedera.mirror.web3.repository.NftRepository; import com.hedera.mirror.web3.repository.TokenAccountRepository; import com.hedera.mirror.web3.repository.TokenAllowanceRepository; -import com.hedera.mirror.web3.repository.projections.TokenAccountAssociationsCount; -import com.hedera.mirror.web3.utils.Suppliers; import com.hedera.pbj.runtime.OneOf; import com.hedera.pbj.runtime.ParseException; import com.hedera.pbj.runtime.io.buffer.Bytes; @@ -52,11 +50,15 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; import java.util.stream.Collectors; import lombok.extern.java.Log; import org.jetbrains.annotations.NotNull; +/** + * This class serves as a repository layer between hedera app services read only state and the Postgres database in mirror-node + * + * The object, which is read from DB is converted to the PBJ generated format, so that it can properly be utilized by the hedera app components + * */ @Named @Log public class AccountReadableKVState extends ReadableKVStateBase { @@ -72,9 +74,6 @@ public class AccountReadableKVState extends ReadableKVStateBase timestamp) { + private Account accountFromEntity(Entity entity, final Optional timestamp) { var tokenAccountBalances = getNumberOfAllAndPositiveBalanceTokenAssociations(entity.getId(), timestamp); - return new com.hedera.hapi.node.state.token.Account( - new com.hedera.hapi.node.base.AccountID( + return new Account( + new AccountID( entity.getShard(), entity.getRealm(), new OneOf<>(AccountOneOfType.ACCOUNT_NUM, entity.getNum())), @@ -140,9 +139,9 @@ private com.hedera.hapi.node.state.token.Account accountFromEntity(Entity entity getOwnedNfts(entity.getId(), timestamp), Optional.ofNullable(entity.getMaxAutomaticTokenAssociations()).orElse(0), 0, - tokenAccountBalances.get().all(), + tokenAccountBalances.all(), CONTRACT.equals(entity.getType()), - tokenAccountBalances.get().positive(), + tokenAccountBalances.positive(), entity.getEthereumNonce() != null ? entity.getEthereumNonce() : 0L, 0L, new com.hedera.hapi.node.base.AccountID( @@ -195,7 +194,7 @@ private List getCryptoAllowances(Long ownerId, final Opt ? cryptoAllowanceRepository.findByOwnerAndTimestamp(ownerId, timestamp.get()) : cryptoAllowanceRepository.findByOwner(ownerId); - return cryptoAllowances.stream().map(this::convertCryptoAllowance).collect(Collectors.toList()); + return cryptoAllowances.stream().map(this::convertCryptoAllowance).toList(); } private AccountCryptoAllowance convertCryptoAllowance(final CryptoAllowance cryptoAllowance) { @@ -211,7 +210,7 @@ private List getFungibleTokenAllowances( ? tokenAllowanceRepository.findByOwnerAndTimestamp(ownerId, timestamp.get()) : tokenAllowanceRepository.findByOwner(ownerId); - return fungibleAllowances.stream().map(this::convertFungibleAllowance).collect(Collectors.toList()); + return fungibleAllowances.stream().map(this::convertFungibleAllowance).toList(); } private AccountFungibleTokenAllowance convertFungibleAllowance(final TokenAllowance tokenAllowance) { @@ -237,7 +236,7 @@ private AccountApprovalForAllAllowance convertNftAllowance(final NftAllowance nf 0L, 0L, new OneOf<>(AccountOneOfType.ACCOUNT_NUM, nftAllowance.getSpender()))); } - private Supplier getNumberOfAllAndPositiveBalanceTokenAssociations( + private TokenAccountBalances getNumberOfAllAndPositiveBalanceTokenAssociations( long accountId, final Optional timestamp) { var counts = timestamp .map(t -> tokenAccountRepository.countByAccountIdAndTimestampAndAssociatedGroupedByBalanceIsPositive( @@ -247,7 +246,7 @@ private Supplier getNumberOfAllAndPositiveBalanceTokenAsso int all = 0; int positive = 0; - for (TokenAccountAssociationsCount count : counts) { + for (final var count : counts) { if (count.getIsPositiveBalance()) { positive = count.getTokenCount(); } @@ -257,10 +256,10 @@ private Supplier getNumberOfAllAndPositiveBalanceTokenAsso final var allAggregated = all; final var positiveAggregated = positive; - return Suppliers.memoize(() -> new TokenAccountBalances(allAggregated, positiveAggregated)); + return new TokenAccountBalances(allAggregated, positiveAggregated); } - private com.hedera.hapi.node.base.Key parseKey(Entity entity) { + private Key parseKey(Entity entity) { final byte[] keyBytes = entity.getKey(); try { From a76472cc4afc98882ff2422412930d79a0c5ac8f Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Thu, 19 Sep 2024 22:18:58 +0300 Subject: [PATCH 09/13] nit: resolve PR comment Signed-off-by: IvanKavaldzhiev --- .../convert/SemanticVersionConvertor.java | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java index fa51b08e30d..0bd5dea3e0f 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/convert/SemanticVersionConvertor.java @@ -18,45 +18,18 @@ import com.hedera.hapi.node.base.SemanticVersion; import jakarta.inject.Named; -import java.util.regex.Pattern; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.core.convert.converter.Converter; @Named @ConfigurationPropertiesBinding public class SemanticVersionConvertor implements Converter { - /** Arbitrary limit to prevent stack overflow when parsing unrealistically long versions. */ - private static final int MAX_VERSION_LENGTH = 100; - /** From */ - // suppress the warning that the regular expression is too complicated - @SuppressWarnings({"java:S5843", "java:S5998"}) - private static final Pattern SEMVER_SPEC_REGEX = Pattern.compile( - "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)" - + "(?:\\." - + "(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)" - + "*))?$"); - @Override - public SemanticVersion convert(String value) { - if (value.length() > MAX_VERSION_LENGTH) { - throw new IllegalArgumentException("Semantic version '" + value + "' is too long"); - } + private final com.hedera.node.config.converter.SemanticVersionConverter delegate = + new com.hedera.node.config.converter.SemanticVersionConverter(); - final var matcher = SEMVER_SPEC_REGEX.matcher(value); - if (matcher.matches()) { - final var builder = SemanticVersion.newBuilder() - .major(Integer.parseInt(matcher.group(1))) - .minor(Integer.parseInt(matcher.group(2))) - .patch(Integer.parseInt(matcher.group(3))); - if (matcher.group(4) != null) { - builder.pre(matcher.group(4)); - } - if (matcher.group(5) != null) { - builder.build(matcher.group(5)); - } - return builder.build(); - } else { - throw new IllegalArgumentException("'" + value + "' is not a valid semantic version"); - } + @Override + public SemanticVersion convert(String source) { + return delegate.convert(source); } } From 5b15a49eba3a3c423ee22aa875da1bc7f5313fa5 Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Fri, 20 Sep 2024 11:19:27 +0300 Subject: [PATCH 10/13] nit: resolve PR comment Signed-off-by: IvanKavaldzhiev --- .../state/AccountReadableKVState.java | 70 ++++++++----------- 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java index 5b4d558dd81..8ef1de3cf60 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java @@ -16,14 +16,11 @@ package com.hedera.modularized.state; -import static com.hedera.hapi.node.state.token.codec.AccountProtoCodec.STAKED_ID_UNSET; import static com.hedera.mirror.common.domain.entity.EntityType.CONTRACT; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.AccountID.AccountOneOfType; import com.hedera.hapi.node.base.Key; -import com.hedera.hapi.node.base.NftID; -import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.state.token.AccountApprovalForAllAllowance; @@ -115,48 +112,39 @@ public long size() { private Account accountFromEntity(Entity entity, final Optional timestamp) { var tokenAccountBalances = getNumberOfAllAndPositiveBalanceTokenAssociations(entity.getId(), timestamp); - return new Account( - new AccountID( + return Account.newBuilder() + .accountId(new AccountID( entity.getShard(), entity.getRealm(), - new OneOf<>(AccountOneOfType.ACCOUNT_NUM, entity.getNum())), - entity.getEvmAddress() != null && entity.getEvmAddress().length > 0 - ? Bytes.wrap(entity.getEvmAddress()) - : Bytes.EMPTY, - parseKey(entity), - TimeUnit.SECONDS.convert(entity.getEffectiveExpiration(), TimeUnit.NANOSECONDS), - getAccountBalance(entity, timestamp), - entity.getMemo(), - Optional.ofNullable(entity.getDeleted()).orElse(false), - 0L, - 0L, - STAKED_ID_UNSET, - true, - entity.getReceiverSigRequired() != null ? entity.getReceiverSigRequired() : false, - TokenID.DEFAULT, - NftID.DEFAULT, - 0L, - getOwnedNfts(entity.getId(), timestamp), - Optional.ofNullable(entity.getMaxAutomaticTokenAssociations()).orElse(0), - 0, - tokenAccountBalances.all(), - CONTRACT.equals(entity.getType()), - tokenAccountBalances.positive(), - entity.getEthereumNonce() != null ? entity.getEthereumNonce() : 0L, - 0L, - new com.hedera.hapi.node.base.AccountID( + new OneOf<>(AccountOneOfType.ACCOUNT_NUM, entity.getNum()))) + .alias( + entity.getEvmAddress() != null && entity.getEvmAddress().length > 0 + ? Bytes.wrap(entity.getEvmAddress()) + : Bytes.EMPTY) + .key(parseKey(entity)) + .expirationSecond(TimeUnit.SECONDS.convert(entity.getEffectiveExpiration(), TimeUnit.NANOSECONDS)) + .tinybarBalance(getAccountBalance(entity, timestamp)) + .memo(entity.getMemo()) + .deleted(Optional.ofNullable(entity.getDeleted()).orElse(false)) + .receiverSigRequired(entity.getReceiverSigRequired() != null ? entity.getReceiverSigRequired() : false) + .numberOwnedNfts(getOwnedNfts(entity.getId(), timestamp)) + .maxAutoAssociations(Optional.ofNullable(entity.getMaxAutomaticTokenAssociations()) + .orElse(0)) + .numberAssociations(tokenAccountBalances.all()) + .smartContract(CONTRACT.equals(entity.getType())) + .numberPositiveBalances(tokenAccountBalances.positive()) + .ethereumNonce(entity.getEthereumNonce() != null ? entity.getEthereumNonce() : 0L) + .autoRenewAccountId(new AccountID( entity.getShard(), entity.getRealm(), - new OneOf<>(AccountOneOfType.ACCOUNT_NUM, entity.getAutoRenewAccountId())), - entity.getAutoRenewPeriod() != null ? entity.getAutoRenewPeriod() : DEFAULT_AUTO_RENEW_PERIOD, - 0, - getCryptoAllowances(entity.getId(), timestamp), - getApproveForAllNfts(entity.getId(), timestamp), - getFungibleTokenAllowances(entity.getId(), timestamp), - 0, - false, - Bytes.EMPTY, - PendingAirdropId.DEFAULT); + new OneOf<>(AccountOneOfType.ACCOUNT_NUM, entity.getAutoRenewAccountId()))) + .autoRenewSeconds( + entity.getAutoRenewPeriod() != null ? entity.getAutoRenewPeriod() : DEFAULT_AUTO_RENEW_PERIOD) + .cryptoAllowances(getCryptoAllowances(entity.getId(), timestamp)) + .approveForAllNftAllowances(getApproveForAllNfts(entity.getId(), timestamp)) + .tokenAllowances(getFungibleTokenAllowances(entity.getId(), timestamp)) + .expiredAndPendingRemoval(false) + .build(); } private Long getOwnedNfts(Long accountId, final Optional timestamp) { From 0978d0f409944301ad7f96bea9e8b88910b95213 Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Fri, 20 Sep 2024 11:44:15 +0300 Subject: [PATCH 11/13] nit: resolve PR comments Signed-off-by: IvanKavaldzhiev --- .../mirror/web3/common/ContractCallContext.java | 6 +++--- .../mirror/web3/service/ContractDebugService.java | 3 ++- .../web3}/state/AccountReadableKVState.java | 11 +++++------ .../web3}/state/CommonEntityAccessor.java | 2 +- .../web3}/state/AccountReadableKVStateTest.java | 2 +- .../web3}/state/CommonEntityAccessorTest.java | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) rename hedera-mirror-web3/src/main/java/com/hedera/{modularized => mirror/web3}/state/AccountReadableKVState.java (96%) rename hedera-mirror-web3/src/main/java/com/hedera/{modularized => mirror/web3}/state/CommonEntityAccessor.java (98%) rename hedera-mirror-web3/src/test/java/com/hedera/{modularized => mirror/web3}/state/AccountReadableKVStateTest.java (99%) rename hedera-mirror-web3/src/test/java/com/hedera/{modularized => mirror/web3}/state/CommonEntityAccessorTest.java (99%) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/common/ContractCallContext.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/common/ContractCallContext.java index 7dac04a4195..174087f7338 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/common/ContractCallContext.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/common/ContractCallContext.java @@ -71,7 +71,7 @@ public class ContractCallContext { * The timestamp used to fetch the state from the stackedStateFrames. */ @Setter - private long timestamp = 0; + private Optional timestamp = Optional.empty(); private ContractCallContext() {} @@ -121,8 +121,8 @@ public void addOpcodes(Opcode opcode) { */ public void initializeStackFrames(final StackedStateFrames stackedStateFrames) { if (stackedStateFrames != null) { - final var stateTimestamp = this.timestamp > 0 - ? Optional.of(this.timestamp) + final var stateTimestamp = timestamp.isPresent() + ? timestamp : Optional.ofNullable(recordFile).map(RecordFile::getConsensusEnd); stackBase = stack = stackedStateFrames.getInitializedStackBase(stateTimestamp); } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractDebugService.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractDebugService.java index f4195d418d0..21082d13644 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractDebugService.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractDebugService.java @@ -33,6 +33,7 @@ import io.micrometer.core.instrument.MeterRegistry; import jakarta.inject.Named; import jakarta.validation.Valid; +import java.util.Optional; import lombok.CustomLog; import org.springframework.validation.annotation.Validated; @@ -57,7 +58,7 @@ public ContractDebugService( public OpcodesProcessingResult processOpcodeCall( final @Valid ContractDebugParameters params, final OpcodeTracerOptions opcodeTracerOptions) { return ContractCallContext.run(ctx -> { - ctx.setTimestamp(params.getConsensusTimestamp() - 1); + ctx.setTimestamp(Optional.of(params.getConsensusTimestamp() - 1)); ctx.setOpcodeTracerOptions(opcodeTracerOptions); ctx.setContractActions(contractActionRepository.findFailedSystemActionsByConsensusTimestamp( params.getConsensusTimestamp())); diff --git a/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java similarity index 96% rename from hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java rename to hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java index 8ef1de3cf60..6866e154500 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/AccountReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.modularized.state; +package com.hedera.mirror.web3.state; import static com.hedera.mirror.common.domain.entity.EntityType.CONTRACT; @@ -41,6 +41,7 @@ import com.hedera.pbj.runtime.ParseException; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.spi.ReadableKVStateBase; +import jakarta.annotation.Nonnull; import jakarta.inject.Named; import java.util.Collections; import java.util.Iterator; @@ -49,7 +50,6 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import lombok.extern.java.Log; -import org.jetbrains.annotations.NotNull; /** * This class serves as a repository layer between hedera app services read only state and the Postgres database in mirror-node @@ -90,9 +90,8 @@ public AccountReadableKVState( } @Override - protected Account readFromDataSource(@NotNull AccountID key) { - final var timestampValue = ContractCallContext.get().getTimestamp(); - final Optional timestamp = timestampValue != 0 ? Optional.of(timestampValue) : Optional.empty(); + protected Account readFromDataSource(@Nonnull AccountID key) { + final var timestamp = ContractCallContext.get().getTimestamp(); return commonEntityAccessor .get(key, timestamp) .map(entity -> accountFromEntity(entity, timestamp)) @@ -100,7 +99,7 @@ protected Account readFromDataSource(@NotNull AccountID key) { } @Override - protected @NotNull Iterator iterateFromDataSource() { + protected @Nonnull Iterator iterateFromDataSource() { return Collections.emptyIterator(); } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/CommonEntityAccessor.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/CommonEntityAccessor.java similarity index 98% rename from hedera-mirror-web3/src/main/java/com/hedera/modularized/state/CommonEntityAccessor.java rename to hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/CommonEntityAccessor.java index 8e5f93922f8..ef0c96f1190 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/modularized/state/CommonEntityAccessor.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/CommonEntityAccessor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.modularized.state; +package com.hedera.mirror.web3.state; import static com.hedera.services.utils.EntityIdUtils.entityIdFromId; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/modularized/state/AccountReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/AccountReadableKVStateTest.java similarity index 99% rename from hedera-mirror-web3/src/test/java/com/hedera/modularized/state/AccountReadableKVStateTest.java rename to hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/AccountReadableKVStateTest.java index 121d9a4f7c3..62bb3dd03b0 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/modularized/state/AccountReadableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/AccountReadableKVStateTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.modularized.state; +package com.hedera.mirror.web3.state; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.any; diff --git a/hedera-mirror-web3/src/test/java/com/hedera/modularized/state/CommonEntityAccessorTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/CommonEntityAccessorTest.java similarity index 99% rename from hedera-mirror-web3/src/test/java/com/hedera/modularized/state/CommonEntityAccessorTest.java rename to hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/CommonEntityAccessorTest.java index 0f57001348c..fced6c019f8 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/modularized/state/CommonEntityAccessorTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/CommonEntityAccessorTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.hedera.modularized.state; +package com.hedera.mirror.web3.state; import static com.hedera.services.utils.EntityIdUtils.entityIdFromId; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; From 9a9d438fb22fc9769947b3f595df9ff3173987dc Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Fri, 20 Sep 2024 14:59:44 +0300 Subject: [PATCH 12/13] nit: fix compilation errors Signed-off-by: IvanKavaldzhiev --- .../state/AccountReadableKVStateTest.java | 30 +++++++++---------- .../web3/state/CommonEntityAccessorTest.java | 1 - 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/AccountReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/AccountReadableKVStateTest.java index 62bb3dd03b0..8c0370c8fff 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/AccountReadableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/AccountReadableKVStateTest.java @@ -164,7 +164,7 @@ void setup() { @Test void accountFieldsMatchEntityFields() { - when(contractCallContext.getTimestamp()).thenReturn(0L); + when(contractCallContext.getTimestamp()).thenReturn(Optional.empty()); when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); assertThat(accountReadableKVState.get(ACCOUNT_ID)).satisfies(account -> assertThat(account) .returns( @@ -183,7 +183,7 @@ void accountFieldsMatchEntityFields() { @Test void whenExpirationTimestampIsNullThenExpiryIsBasedOnCreatedAndRenewTimestamps() { - when(contractCallContext.getTimestamp()).thenReturn(0L); + when(contractCallContext.getTimestamp()).thenReturn(Optional.empty()); when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); entity.setExpirationTimestamp(null); entity.setCreatedTimestamp(987_000_000L); @@ -196,7 +196,7 @@ void whenExpirationTimestampIsNullThenExpiryIsBasedOnCreatedAndRenewTimestamps() @Test void useDefaultValuesWhenFieldsAreNull() { - when(contractCallContext.getTimestamp()).thenReturn(0L); + when(contractCallContext.getTimestamp()).thenReturn(Optional.empty()); when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); entity.setExpirationTimestamp(null); entity.setCreatedTimestamp(null); @@ -219,7 +219,7 @@ void useDefaultValuesWhenFieldsAreNull() { @Test void accountOwnedNftsMatchesValueFromRepository() { - when(contractCallContext.getTimestamp()).thenReturn(0L); + when(contractCallContext.getTimestamp()).thenReturn(Optional.empty()); when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); long ownedNfts = 20; when(nftRepository.countByAccountIdNotDeleted(any())).thenReturn(ownedNfts); @@ -234,7 +234,7 @@ void accountOwnedNftsMatchesValueFromRepository() { @Test void accountOwnedNftsMatchesValueFromRepositoryHistorical() { - when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(contractCallContext.getTimestamp()).thenReturn(Optional.of(timestamp.get())); when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); long ownedNfts = 20; when(nftRepository.countByAccountIdAndTimestampNotDeleted(entity.getId(), timestamp.get())) @@ -250,7 +250,7 @@ void accountOwnedNftsMatchesValueFromRepositoryHistorical() { @Test void accountBalanceMatchesValueFromRepositoryHistorical() { - when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(contractCallContext.getTimestamp()).thenReturn(Optional.of(timestamp.get())); when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); long balance = 20; when(accountBalanceRepository.findHistoricalAccountBalanceUpToTimestamp(entity.getId(), timestamp.get())) @@ -267,7 +267,7 @@ void accountBalanceMatchesValueFromRepositoryHistorical() { @Test void accountBalanceBeforeAccountCreation() { - when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(contractCallContext.getTimestamp()).thenReturn(Optional.of(timestamp.get())); entity.setCreatedTimestamp(timestamp.get() + 1); when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); long balance = 0; @@ -278,7 +278,7 @@ void accountBalanceBeforeAccountCreation() { @Test void accountBalanceIsZeroHistorical() { - when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(contractCallContext.getTimestamp()).thenReturn(Optional.of(timestamp.get())); entity.setCreatedTimestamp(timestamp.get() - 1); when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); long balance = 0; @@ -296,7 +296,7 @@ void accountBalanceIsZeroHistorical() { @Test void accountBalanceWhenCreatedTimestampIsNull() { - when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(contractCallContext.getTimestamp()).thenReturn(Optional.of(timestamp.get())); when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); long balance = 20; entity.setCreatedTimestamp(null); @@ -314,7 +314,7 @@ void accountBalanceWhenCreatedTimestampIsNull() { @Test void cryptoAllowancesMatchValuesFromRepository() { - when(contractCallContext.getTimestamp()).thenReturn(0L); + when(contractCallContext.getTimestamp()).thenReturn(Optional.empty()); when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); CryptoAllowance firstAllowance = new CryptoAllowance(); firstAllowance.setSpender(123L); @@ -345,7 +345,7 @@ void cryptoAllowancesMatchValuesFromRepository() { @Test void cryptoAllowancesMatchValuesFromRepositoryHistorical() { - when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(contractCallContext.getTimestamp()).thenReturn(Optional.of(timestamp.get())); when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); CryptoAllowance firstAllowance = new CryptoAllowance(); firstAllowance.setSpender(123L); @@ -376,7 +376,7 @@ void cryptoAllowancesMatchValuesFromRepositoryHistorical() { @Test void fungibleTokenAllowancesMatchValuesFromRepository() { - when(contractCallContext.getTimestamp()).thenReturn(0L); + when(contractCallContext.getTimestamp()).thenReturn(Optional.empty()); when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); TokenAllowance firstAllowance = new TokenAllowance(); firstAllowance.setOwner(entity.getId()); @@ -413,7 +413,7 @@ void fungibleTokenAllowancesMatchValuesFromRepository() { @Test void fungibleTokenAllowancesMatchValuesFromRepositoryHistorical() { - when(contractCallContext.getTimestamp()).thenReturn(timestamp.get()); + when(contractCallContext.getTimestamp()).thenReturn(Optional.of(timestamp.get())); when(commonEntityAccessor.get(ACCOUNT_ID, timestamp)).thenReturn(Optional.ofNullable(entity)); TokenAllowance firstAllowance = new TokenAllowance(); firstAllowance.setOwner(entity.getId()); @@ -450,7 +450,7 @@ void fungibleTokenAllowancesMatchValuesFromRepositoryHistorical() { @Test void approveForAllNftsMatchValuesFromRepository() { - when(contractCallContext.getTimestamp()).thenReturn(0L); + when(contractCallContext.getTimestamp()).thenReturn(Optional.empty()); when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); NftAllowance firstAllowance = new NftAllowance(); firstAllowance.setOwner(entity.getId()); @@ -485,7 +485,7 @@ private AccountID getAccountId(final Long num) { @Test void numTokenAssociationsAndNumPositiveBalancesMatchValuesFromRepository() { - when(contractCallContext.getTimestamp()).thenReturn(0L); + when(contractCallContext.getTimestamp()).thenReturn(Optional.empty()); when(commonEntityAccessor.get(ACCOUNT_ID, Optional.empty())).thenReturn(Optional.ofNullable(entity)); when(tokenAccountRepository.countByAccountIdAndAssociatedGroupedByBalanceIsPositive(anyLong())) .thenReturn(associationsCount); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/CommonEntityAccessorTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/CommonEntityAccessorTest.java index fced6c019f8..999f7b74f3e 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/CommonEntityAccessorTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/CommonEntityAccessorTest.java @@ -38,7 +38,6 @@ @ExtendWith(MockitoExtension.class) class CommonEntityAccessorTest { - private static final String HEX = "0x00000000000000000000000000000000000004e4"; private static final String ALIAS_HEX = "0x67d8d32e9bf1a9968a5ff53b87d777aa8ebbee69"; private static final Address ALIAS_ADDRESS = Address.fromHexString(ALIAS_HEX); private static final AccountID ACCOUNT_ALIAS = From 0650e5d4f57ad9bb0562447d23688f2a4f7b3d3c Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Fri, 20 Sep 2024 15:40:16 +0300 Subject: [PATCH 13/13] nit: fix sonar issues Signed-off-by: IvanKavaldzhiev --- .../com/hedera/mirror/web3/state/AccountReadableKVState.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java index 6866e154500..1028d740f25 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java @@ -48,7 +48,6 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import lombok.extern.java.Log; /** @@ -125,7 +124,7 @@ private Account accountFromEntity(Entity entity, final Optional timestamp) .tinybarBalance(getAccountBalance(entity, timestamp)) .memo(entity.getMemo()) .deleted(Optional.ofNullable(entity.getDeleted()).orElse(false)) - .receiverSigRequired(entity.getReceiverSigRequired() != null ? entity.getReceiverSigRequired() : false) + .receiverSigRequired(entity.getReceiverSigRequired() != null && entity.getReceiverSigRequired()) .numberOwnedNfts(getOwnedNfts(entity.getId(), timestamp)) .maxAutoAssociations(Optional.ofNullable(entity.getMaxAutomaticTokenAssociations()) .orElse(0)) @@ -213,7 +212,7 @@ private List getApproveForAllNfts(Long ownerId, ? nftAllowanceRepository.findByOwnerAndTimestampAndApprovedForAllIsTrue(ownerId, timestamp.get()) : nftAllowanceRepository.findByOwnerAndApprovedForAllIsTrue(ownerId); - return nftAllowances.stream().map(this::convertNftAllowance).collect(Collectors.toList()); + return nftAllowances.stream().map(this::convertNftAllowance).toList(); } private AccountApprovalForAllAllowance convertNftAllowance(final NftAllowance nftAllowance) {