diff --git a/docs/configuration.md b/docs/configuration.md index 180969349b..dcba437778 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -675,55 +675,56 @@ to configure the application. The following table lists the available properties along with their default values. Unless you need to set a non-default value, it is recommended to only populate overridden properties in the custom `application.yml`. -| Name | Default | Description | -| ------------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `hedera.mirror.web3.cache.contract` | expireAfterAccess=60m,maximumSize=1000,recordStats | Cache configuration for contract | -| `hedera.mirror.web3.cache.contractState` | expireAfterWrite=1s,maximumSize=10000,recordStats | Cache configuration for contract state | -| `hedera.mirror.web3.cache.entity ` | expireAfterWrite=1s,maximumSize=10000,recordStats | Cache configuration for entity | -| `hedera.mirror.web3.cache.fee` | expireAfterWrite=10m,maximumSize=20,recordStats | Cache configuration for fee related info | -| `hedera.mirror.web3.cache.token` | expireAfterWrite=1s,maximumSize=10000,recordStats | Cache configuration for token related info | -| `hedera.mirror.web3.db.host` | 127.0.0.1 | The IP or hostname used to connect to the database | -| `hedera.mirror.web3.db.name` | mirror_node | The name of the database | -| `hedera.mirror.web3.db.password` | mirror_web3_pass | The database password used to connect to the database | -| `hedera.mirror.web3.db.port` | 5432 | The port used to connect to the database | -| `hedera.mirror.web3.db.sslMode` | DISABLE | The ssl level of protection against eavesdropping, man-in-the-middle (MITM) and impersonation on the db connection. Accepts either DISABLE, ALLOW, PREFER, REQUIRE, VERIFY_CA or VERIFY_FULL. | -| `hedera.mirror.web3.db.statementTimeout` | 10000 | The number of milliseconds to wait before timing out a query statement | -| `hedera.mirror.web3.db.username` | mirror_web3 | The username used to connect to the database | -| `hedera.mirror.web3.evm.allowTreasuryToOwnNfts` | true | Whether the treasury is allowed to own NFTs | -| `hedera.mirror.web3.evm.autoRenewTargetTypes` | [] | The entities that are auto-renewed | -| `hedera.mirror.web3.evm.estimateGasIterationThresholdPercent` | 0.10 | Percent used during gas estimation algorithm | -| `hedera.mirror.web3.evm.directTokenCall` | true | Flag enabling contract like calls to tokens | -| `hedera.mirror.web3.evm.dynamicEvmVersion` | false | Flag indicating whether a dynamic evm version to be used | -| `hedera.mirror.web3.evm.evmVersion` | v0.34 | The besu EVM version to be used as dynamic one | -| `hedera.mirror.web3.evm.evmSpecVersion` | SHANGHAI | The besu EVM spec version to be used as dynamic one | -| `hedera.mirror.web3.evm.exchangeRateGasReq` | 100 | Gas requirement for ExchangeRatePrecompile. | -| `hedera.mirror.web3.evm.expirationCacheTime` | 10m | Maximum time for contract bytecode's caching | -| `hedera.mirror.web3.evm.fundingAccount` | 0x0000000000000000000000000000000000000062 | Default Hedera funding account | -| `hedera.mirror.web3.evm.htsDefaultGasCost` | 10000 | Default gas cost for Hedera Token Service Precompiles | -| `hedera.mirror.web3.evm.limitTokenAssociations` | false | Whether the TokenAssociations are limited | -| `hedera.mirror.web3.evm.maxAutoRenewDuration` | 8000001 | Maximum duration for auto-renew account | -| `hedera.mirror.web3.evm.maxBatchSizeBurn` | 10 | Maximum number of burn operations in a single transaction | -| `hedera.mirror.web3.evm.maxBatchSizeMint` | 10 | Maximum number of mint operations in a single transaction | -| `hedera.mirror.web3.evm.maxBatchSizeWipe` | 10 | Maximum number of wipe operations in a single transaction | -| `hedera.mirror.web3.evm.maxCustomFeesAllowed` | 10 | Maximum number of custom fees in a single transaction | -| `hedera.mirror.web3.evm.maxDataSize` | 128 KiB | Maximum contract data size in bytes, for both contract create and call. Spring Boot `DataSize` defines suffixes in powers of 2: KB (1024) and MB (1,048,576), aka KiB and MiB. | -| `hedera.mirror.web3.evm.maxGasEstimateRetriesCount` | 20 | Estimate gas contract call retry threshold | -| `hedera.mirror.web3.evm.maxGasRefundPercentage` | 100% | Maximal percent of gas refunding | -| `hedera.mirror.web3.evm.maxGas` | 15000000 | Maximum gas allowed in contract call request | -| `hedera.mirror.web3.evm.maxMemoUtf8Bytes` | 100 | Maximum size in bytes for token memo | -| `hedera.mirror.web3.evm.maxNftMetadataBytes` | 100 | Maximum size in bytes for NFT metadata | -| `hedera.mirror.web3.evm.maxTokenNameUtf8Bytes` | 100 | Maximum size in bytes for token name | -| `hedera.mirror.web3.evm.maxTokensPerAccount` | 1000 | Maximum number token associations per account | -| `hedera.mirror.web3.evm.maxTokenSymbolUtf8Bytes` | 100 | Maximum size in bytes for token symbol | -| `hedera.mirror.web3.evm.minAutoRenewDuration` | 2592000 | Minimum duration for auto-renew account | -| `hedera.mirror.web3.evm.modularizedServices` | false | Flag that indicates if the hedera.app dependency is used. This is under development. It is recommended to be set to false. | -| `hedera.mirror.web3.evm.network` | TESTNET | Which Hedera network to use. Can be either `MAINNET`, `PREVIEWNET`, `TESTNET` or `OTHER` | -| `hedera.mirror.web3.evm.feesTokenTransferUsageMultiplier` | 380 | Used to calculate token transfer fees | -| `hedera.mirror.web3.evm.trace.enabled` | false | Flag enabling tracer | -| `hedera.mirror.web3.evm.trace.contract` | [] | A set with contract addresses to filter. By default it is empty to indicate it will trace all contract addresses. | -| `hedera.mirror.web3.evm.trace.status` | [] | A set with frame statuses to filter. By default it is empty to indicate it will trace all frames regardless of status. | -| `hedera.mirror.web3.maxPayloadLogSize` | 300 | The maximum number of bytes to use to log the request payload. | -| `hedera.mirror.web3.opcode.tracer.enabled` | false | Whether the `/contracts/results/{transactionIdOrHash}/opcodes` endpoint is exposed | -| `hedera.mirror.web3.throttle.gasLimitRefundPercent` | 100 | Maximum gas percent from the passed gas limit in a request to return in the throttle bucket after the request is processed | -| `hedera.mirror.web3.throttle.gasPerSecond` | 1000000000 | Maximum gas limit that can be processed per second | -| `hedera.mirror.web3.throttle.requestsPerSecond` | 500 | Maximum RPS limit | +| Name | Default | Description | +| ------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `hedera.mirror.web3.cache.contract` | expireAfterAccess=60m,maximumSize=1000,recordStats | Cache configuration for contract | +| `hedera.mirror.web3.cache.contractState` | expireAfterWrite=1s,maximumSize=10000,recordStats | Cache configuration for contract state | +| `hedera.mirror.web3.cache.entity ` | expireAfterWrite=1s,maximumSize=10000,recordStats | Cache configuration for entity | +| `hedera.mirror.web3.cache.fee` | expireAfterWrite=10m,maximumSize=20,recordStats | Cache configuration for fee related info | +| `hedera.mirror.web3.cache.token` | expireAfterWrite=1s,maximumSize=10000,recordStats | Cache configuration for token related info | +| `hedera.mirror.web3.db.host` | 127.0.0.1 | The IP or hostname used to connect to the database | +| `hedera.mirror.web3.db.name` | mirror_node | The name of the database | +| `hedera.mirror.web3.db.password` | mirror_web3_pass | The database password used to connect to the database | +| `hedera.mirror.web3.db.port` | 5432 | The port used to connect to the database | +| `hedera.mirror.web3.db.sslMode` | DISABLE | The ssl level of protection against eavesdropping, man-in-the-middle (MITM) and impersonation on the db connection. Accepts either DISABLE, ALLOW, PREFER, REQUIRE, VERIFY_CA or VERIFY_FULL. | +| `hedera.mirror.web3.db.statementTimeout` | 10000 | The number of milliseconds to wait before timing out a query statement | +| `hedera.mirror.web3.db.username` | mirror_web3 | The username used to connect to the database | +| `hedera.mirror.web3.evm.allowTreasuryToOwnNfts` | true | Whether the treasury is allowed to own NFTs | +| `hedera.mirror.web3.evm.autoRenewTargetTypes` | [] | The entities that are auto-renewed | +| `hedera.mirror.web3.evm.estimateGasIterationThresholdPercent` | 0.10 | Percent used during gas estimation algorithm | +| `hedera.mirror.web3.evm.directTokenCall` | true | Flag enabling contract like calls to tokens | +| `hedera.mirror.web3.evm.dynamicEvmVersion` | false | Flag indicating whether a dynamic evm version to be used | +| `hedera.mirror.web3.evm.evmVersion` | v0.34 | The besu EVM version to be used as dynamic one | +| `hedera.mirror.web3.evm.evmSpecVersion` | SHANGHAI | The besu EVM spec version to be used as dynamic one | +| `hedera.mirror.web3.evm.exchangeRateGasReq` | 100 | Gas requirement for ExchangeRatePrecompile. | +| `hedera.mirror.web3.evm.expirationCacheTime` | 10m | Maximum time for contract bytecode's caching | +| `hedera.mirror.web3.evm.fundingAccount` | 0x0000000000000000000000000000000000000062 | Default Hedera funding account | +| `hedera.mirror.web3.evm.htsDefaultGasCost` | 10000 | Default gas cost for Hedera Token Service Precompiles | +| `hedera.mirror.web3.evm.limitTokenAssociations` | false | Whether the TokenAssociations are limited | +| `hedera.mirror.web3.evm.maxAutoRenewDuration` | 8000001 | Maximum duration for auto-renew account | +| `hedera.mirror.web3.evm.maxBatchSizeBurn` | 10 | Maximum number of burn operations in a single transaction | +| `hedera.mirror.web3.evm.maxBatchSizeMint` | 10 | Maximum number of mint operations in a single transaction | +| `hedera.mirror.web3.evm.maxBatchSizeWipe` | 10 | Maximum number of wipe operations in a single transaction | +| `hedera.mirror.web3.evm.maxCustomFeesAllowed` | 10 | Maximum number of custom fees in a single transaction | +| `hedera.mirror.web3.evm.maxDataSize` | 128 KiB | Maximum contract data size in bytes, for both contract create and call. Spring Boot `DataSize` defines suffixes in powers of 2: KB (1024) and MB (1,048,576), aka KiB and MiB. | +| `hedera.mirror.web3.evm.maxGasEstimateRetriesCount` | 20 | Estimate gas contract call retry threshold | +| `hedera.mirror.web3.evm.maxGasRefundPercentage` | 100% | Maximal percent of gas refunding | +| `hedera.mirror.web3.evm.maxGas` | 15000000 | Maximum gas allowed in contract call request | +| `hedera.mirror.web3.evm.maxMemoUtf8Bytes` | 100 | Maximum size in bytes for token memo | +| `hedera.mirror.web3.evm.maxNftMetadataBytes` | 100 | Maximum size in bytes for NFT metadata | +| `hedera.mirror.web3.evm.maxTokenNameUtf8Bytes` | 100 | Maximum size in bytes for token name | +| `hedera.mirror.web3.evm.maxTokensPerAccount` | 1000 | Maximum number token associations per account | +| `hedera.mirror.web3.evm.maxTokenSymbolUtf8Bytes` | 100 | Maximum size in bytes for token symbol | +| `hedera.mirror.web3.evm.minAutoRenewDuration` | 2592000 | Minimum duration for auto-renew account | +| `hedera.mirror.web3.evm.modularizedServices` | false | Flag that indicates if the hedera.app dependency is used. This is under development. It is recommended to be set to false. | +| `hedera.mirror.web3.evm.network` | TESTNET | Which Hedera network to use. Can be either `MAINNET`, `PREVIEWNET`, `TESTNET` or `OTHER` | +| `hedera.mirror.web3.evm.feesTokenTransferUsageMultiplier` | 380 | Used to calculate token transfer fees | +| `hedera.mirror.web3.evm.trace.enabled` | false | Flag enabling tracer | +| `hedera.mirror.web3.evm.trace.contract` | [] | A set with contract addresses to filter. By default it is empty to indicate it will trace all contract addresses. | +| `hedera.mirror.web3.evm.trace.status` | [] | A set with frame statuses to filter. By default it is empty to indicate it will trace all frames regardless of status. | +| `hedera.mirror.web3.maxPayloadLogSize` | 300 | The maximum number of bytes to use to log the request payload. | +| `hedera.mirror.web3.opcode.tracer.enabled` | false | Whether the `/contracts/results/{transactionIdOrHash}/opcodes` endpoint is exposed | +| `hedera.mirror.web3.throttle.gasLimitRefundPercent` | 100 | Maximum gas percent from the passed gas limit in a request to return in the throttle bucket after the request is processed | +| `hedera.mirror.web3.throttle.gasPerSecond` | 1000000000 | Maximum gas limit that can be processed per second. The max value for this property is 1000000000. In case greater gas limit needs to be allowed per second, please refer to the property below. | +| `hedera.mirror.web3.throttle.gasUnit` | 1 | A multiplier that allows the max gas limit per second to be increased proportionally. For example, if the gasPerSecond is set to 1000000000 and the gasUnit is set to 5, the effective gas limit that can be processed per second would be 5000000000. | +| `hedera.mirror.web3.throttle.requestsPerSecond` | 500 | Maximum RPS limit | diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/controller/ContractController.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/controller/ContractController.java index fe76d52786..82fd1bacc1 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/controller/ContractController.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/controller/ContractController.java @@ -26,6 +26,7 @@ import com.hedera.mirror.web3.exception.RateLimitException; import com.hedera.mirror.web3.service.ContractExecutionService; import com.hedera.mirror.web3.service.model.ContractExecutionParameters; +import com.hedera.mirror.web3.throttle.ThrottleProperties; import com.hedera.mirror.web3.viewmodel.ContractCallRequest; import com.hedera.mirror.web3.viewmodel.ContractCallResponse; import com.hedera.node.app.service.evm.store.models.HederaEvmAccount; @@ -57,12 +58,14 @@ class ContractController { private final MirrorNodeEvmProperties evmProperties; + private final ThrottleProperties throttleProperties; + @PostMapping(value = "/call") ContractCallResponse call(@RequestBody @Valid ContractCallRequest request) { if (!rateLimitBucket.tryConsume(1)) { throw new RateLimitException("Requests per second rate limit exceeded."); - } else if (!gasLimitBucket.tryConsume(request.getGas())) { + } else if (!gasLimitBucket.tryConsume(Math.floorDiv(request.getGas(), throttleProperties.getGasUnit()))) { throw new RateLimitException("Gas per second rate limit exceeded."); } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractCallService.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractCallService.java index a1481c5499..f06414600c 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractCallService.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractCallService.java @@ -132,15 +132,18 @@ protected HederaEvmTransactionProcessingResult doProcessCall( } private void restoreGasToBucket(HederaEvmTransactionProcessingResult result, long gasLimit) { + final var gasUnit = throttleProperties.getGasUnit(); // If the transaction fails, gasUsed is equal to gasLimit, so restore the configured refund percent // of the gasLimit value back in the bucket. - final var gasLimitToRestoreBaseline = (long) (gasLimit * throttleProperties.getGasLimitRefundPercent() / 100f); + final var gasLimitToRestoreBaseline = + (long) (Math.floorDiv(gasLimit, gasUnit) * throttleProperties.getGasLimitRefundPercent() / 100f); if (!result.isSuccessful() && gasLimit == result.getGasUsed()) { gasLimitBucket.addTokens(gasLimitToRestoreBaseline); } else { // The transaction was successful or reverted, so restore the remaining gas back in the bucket or // the configured refund percent of the gasLimit value back in the bucket - whichever is lower. - gasLimitBucket.addTokens(Math.min(gasLimit - result.getGasUsed(), gasLimitToRestoreBaseline)); + final var gasRemaining = gasLimit - result.getGasUsed(); + gasLimitBucket.addTokens(Math.min(Math.floorDiv(gasRemaining, gasUnit), gasLimitToRestoreBaseline)); } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/throttle/ThrottleProperties.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/throttle/ThrottleProperties.java index 9cbeecc326..7fcaf91131 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/throttle/ThrottleProperties.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/throttle/ThrottleProperties.java @@ -34,8 +34,13 @@ public class ThrottleProperties { @Getter @Min(21_000) + @Max(1_000_000_000) private long gasPerSecond = 1_000_000_000L; + @Getter + @Min(1) + private int gasUnit = 1; + @Getter @Min(0) @Max(100) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/controller/ContractControllerTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/controller/ContractControllerTest.java index 0863b29981..d3f87d11a2 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/controller/ContractControllerTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/controller/ContractControllerTest.java @@ -20,6 +20,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import static org.springframework.http.HttpStatus.BAD_REQUEST; @@ -42,6 +43,7 @@ import com.hedera.mirror.web3.exception.InvalidParametersException; import com.hedera.mirror.web3.exception.MirrorEvmTransactionException; import com.hedera.mirror.web3.service.ContractExecutionService; +import com.hedera.mirror.web3.throttle.ThrottleProperties; import com.hedera.mirror.web3.viewmodel.BlockType; import com.hedera.mirror.web3.viewmodel.ContractCallRequest; import com.hedera.mirror.web3.viewmodel.GenericErrorResponse; @@ -108,10 +110,15 @@ class ContractControllerTest { @Autowired private MirrorNodeEvmProperties evmProperties; + @MockBean + private ThrottleProperties throttleProperties; + @BeforeEach void setUp() { given(rateLimitBucket.tryConsume(1)).willReturn(true); - given(gasLimitBucket.tryConsume(THROTTLE_GAS_LIMIT)).willReturn(true); + given(throttleProperties.getGasUnit()).willReturn(2); + given(gasLimitBucket.tryConsume(Math.floorDiv(THROTTLE_GAS_LIMIT, throttleProperties.getGasUnit()))) + .willReturn(true); } @SneakyThrows @@ -167,6 +174,7 @@ void estimateGasWithInvalidGasParameter(long gas) throws Exception { ? numberErrorString("gas", "greater", 21000L) : numberErrorString("gas", "less", 15_000_000L); given(gasLimitBucket.tryConsume(gas)).willReturn(true); + given(throttleProperties.getGasUnit()).willReturn(1); final var request = request(); request.setEstimate(true); request.setGas(gas); @@ -188,16 +196,25 @@ void exceedingRateLimit() throws Exception { @Test void exceedingGasLimit() throws Exception { - given(gasLimitBucket.tryConsume(THROTTLE_GAS_LIMIT)).willReturn(false); + given(gasLimitBucket.tryConsume(anyLong())).willReturn(false); contractCall(request()).andExpect(status().isTooManyRequests()); } + @Test + void throttleGasScaledOnConsume() throws Exception { + var request = request(); + request.setGas(200_000L); + given(gasLimitBucket.tryConsume(anyLong())).willReturn(true); + contractCall(request).andExpect(status().isOk()); + verify(gasLimitBucket).tryConsume(100_000L); + } + @Test void restoreGasInThrottleBucketOnValidationFail() throws Exception { var request = request(); request.setData("With invalid symbol!"); contractCall(request).andExpect(status().isBadRequest()); - verify(gasLimitBucket).tryConsume(request.getGas()); + verify(gasLimitBucket).tryConsume(Math.floorDiv(request.getGas(), throttleProperties.getGasUnit())); verify(gasLimitBucket).addTokens(request.getGas()); } @@ -566,5 +583,10 @@ MeterRegistry meterRegistry() { Web3Properties web3Properties() { return new Web3Properties(); } + + @Bean + ThrottleProperties throttleProperties() { + return new ThrottleProperties(); + } } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java index f8c43ede6f..77541ced31 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java @@ -38,8 +38,8 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.params.provider.EnumSource.Mode.INCLUDE; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -73,12 +73,12 @@ import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Address; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; @@ -103,7 +103,7 @@ class ContractCallServiceTest extends AbstractContractCallServiceTest { @Autowired private RecordFileService recordFileService; - @Autowired + @Mock private ThrottleProperties throttleProperties; @Autowired @@ -132,10 +132,12 @@ private static Stream provideCustomBlockTypes() { private static Stream ercPrecompileCallTypeArgumentsProvider() { List gasLimits = List.of(15_000_000L, 30_000L); + List gasUnits = List.of(1, 2); return Arrays.stream(CallType.values()) .filter(callType -> !callType.equals(ERROR)) - .flatMap(callType -> gasLimits.stream().map(gasLimit -> Arguments.of(callType, gasLimit))); + .flatMap(callType -> gasLimits.stream().flatMap(gasLimit -> gasUnits.stream() + .map(gasUnit -> Arguments.of(callType, gasLimit, gasUnit)))); } private static String toHexWith64LeadingZeros(final Long value) { @@ -145,6 +147,14 @@ private static String toHexWith64LeadingZeros(final Long value) { return result; } + @Override + @BeforeEach + protected void setup() { + super.setup(); + given(throttleProperties.getGasLimitRefundPercent()).willReturn(100f); + given(throttleProperties.getGasUnit()).willReturn(1); + } + @Test void callWithoutDataToAddressWithNoBytecodeReturnsEmptyResult() { // Given @@ -726,11 +736,8 @@ void stateChangeWorksWithDynamicEthCall() throws Exception { } @ParameterizedTest - @EnumSource( - value = CallType.class, - names = {"ETH_CALL", "ETH_ESTIMATE_GAS"}, - mode = INCLUDE) - void ercPrecompileExceptionalHaltReturnsExpectedGasToBucket(final CallType callType) { + @MethodSource("provideParametersForErcPrecompileExceptionalHalt") + void ercPrecompileExceptionalHaltReturnsExpectedGasToBucket(final CallType callType, final int gasUnit) { // Given final var token = tokenPersist(); final var contract = testWeb3jService.deploy(ERCTestContract::deploy); @@ -739,8 +746,12 @@ void ercPrecompileExceptionalHaltReturnsExpectedGasToBucket(final CallType callT final var serviceParameters = getContractExecutionParametersWithValue( Bytes.fromHexString(functionCall.encodeFunctionCall()), Address.ZERO, Address.ZERO, callType, 100L); - final var expectedUsedGasByThrottle = - (long) (serviceParameters.getGas() * throttleProperties.getGasLimitRefundPercent() / 100f); + + given(throttleProperties.getGasUnit()).willReturn(gasUnit); + + final long expectedUsedGasByThrottle = (long) + (Math.floorDiv(TRANSACTION_GAS_LIMIT, gasUnit) * throttleProperties.getGasLimitRefundPercent() / 100f); + final var contractCallServiceWithMockedGasLimitBucket = new ContractExecutionService( meterRegistry, binaryGasEstimator, @@ -766,16 +777,19 @@ void ercPrecompileExceptionalHaltReturnsExpectedGasToBucket(final CallType callT @ParameterizedTest @MethodSource("ercPrecompileCallTypeArgumentsProvider") - void ercPrecompileContractRevertReturnsExpectedGasToBucket(final CallType callType, final long gasLimit) { + void ercPrecompileContractRevertReturnsExpectedGasToBucket( + final CallType callType, final long gasLimit, final int gasUnit) { // Given final var contract = testWeb3jService.deploy(ERCTestContract::deploy); final var functionCall = contract.call_nameNonStatic(Address.ZERO.toHexString()); + given(throttleProperties.getGasUnit()).willReturn(gasUnit); final var serviceParameters = getContractExecutionParameters(functionCall, contract, callType, gasLimit); final var expectedGasUsed = gasUsedAfterExecution(serviceParameters); final var gasLimitToRestoreBaseline = - (long) (serviceParameters.getGas() * throttleProperties.getGasLimitRefundPercent() / 100f); - final var expectedUsedGasByThrottle = Math.min(gasLimit - expectedGasUsed, gasLimitToRestoreBaseline); + (long) (Math.floorDiv(gasLimit, gasUnit) * throttleProperties.getGasLimitRefundPercent() / 100f); + final var expectedUsedGasByThrottle = + Math.min(Math.floorDiv(gasLimit - expectedGasUsed, gasUnit), gasLimitToRestoreBaseline); final var contractCallServiceWithMockedGasLimitBucket = new ContractExecutionService( meterRegistry, binaryGasEstimator, @@ -801,17 +815,20 @@ void ercPrecompileContractRevertReturnsExpectedGasToBucket(final CallType callTy @ParameterizedTest @MethodSource("ercPrecompileCallTypeArgumentsProvider") - void ercPrecompileSuccessReturnsExpectedGasToBucket(final CallType callType, final long gasLimit) { + void ercPrecompileSuccessReturnsExpectedGasToBucket( + final CallType callType, final long gasLimit, final int gasUnit) { // Given final var token = tokenPersist(); final var contract = testWeb3jService.deploy(ERCTestContract::deploy); final var functionCall = contract.call_name(toAddress(token.getId()).toHexString()); + given(throttleProperties.getGasUnit()).willReturn(gasUnit); final var serviceParameters = getContractExecutionParameters(functionCall, contract, callType, gasLimit); final var expectedGasUsed = gasUsedAfterExecution(serviceParameters); final var gasLimitToRestoreBaseline = - (long) (serviceParameters.getGas() * throttleProperties.getGasLimitRefundPercent() / 100f); - final var expectedUsedGasByThrottle = Math.min(gasLimit - expectedGasUsed, gasLimitToRestoreBaseline); + (long) (Math.floorDiv(gasLimit, gasUnit) * throttleProperties.getGasLimitRefundPercent() / 100f); + final var expectedUsedGasByThrottle = + Math.min(Math.floorDiv(gasLimit - expectedGasUsed, gasUnit), gasLimitToRestoreBaseline); final var contractCallServiceWithMockedGasLimitBucket = new ContractExecutionService( meterRegistry, binaryGasEstimator, @@ -955,6 +972,10 @@ private ContractExecutionParameters getContractExecutionParametersWithValue( .build(); } + private static Stream provideParametersForErcPrecompileExceptionalHalt() { + return Stream.of(Arguments.of(CallType.ETH_CALL, 1), Arguments.of(CallType.ETH_ESTIMATE_GAS, 2)); + } + private Entity accountPersist() { return domainBuilder.entity().persist(); }