Skip to content

Commit

Permalink
Refactor ContractCallSystemPrecompileHistoricalTest (#9185)
Browse files Browse the repository at this point in the history
This PR refactors ContractCallSystemPrecompileHistoricalTest to use the web3j plugin test.

This PR modifies:

ContractCallSystemPrecompileHistoricalTest now uses the web3j plugin.
Added prng and exchange rate historical solidity files.
The rest of the modifications are via the spotlessApply

---------

Signed-off-by: Kristiyan Selveliev <[email protected]>
  • Loading branch information
kselveliev authored Aug 30, 2024
1 parent 44a47ac commit 764b172
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,74 +16,66 @@

package com.hedera.mirror.web3.service;

import static com.hedera.mirror.web3.service.model.CallServiceParameters.CallType.ETH_CALL;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.hedera.mirror.web3.utils.ContractFunctionProviderEnum;
import com.google.common.collect.Range;
import com.hedera.mirror.web3.viewmodel.BlockType;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import com.hedera.mirror.web3.web3j.generated.ExchangeRatePrecompileHistorical;
import com.hedera.mirror.web3.web3j.generated.PrngSystemContractHistorical;
import java.math.BigInteger;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.CsvSource;

public class ContractCallSystemPrecompileHistoricalTest extends ContractCallTestSetup {

private static final List<BlockType> BLOCK_NUMBERS_FOR_EVM_VERSION = List.of(
BlockType.of(String.valueOf(EVM_V_46_BLOCK)),
BlockType.of(String.valueOf(EVM_V_38_BLOCK)),
BlockType.of(String.valueOf(EVM_V_34_BLOCK)),
BlockType.of(String.valueOf(EVM_V_34_BLOCK - 1)));

private static Stream<Arguments> exchangeRateFunctionsProviderHistorical() {
return Arrays.stream(ExchangeRateFunctions.values())
.flatMap(exchangeRateFunction -> BLOCK_NUMBERS_FOR_EVM_VERSION.stream()
.map(blockNumber -> Arguments.of(exchangeRateFunction, blockNumber)));
}

private static Stream<Arguments> blockNumberForDifferentEvmVersionsProviderHistorical() {
return BLOCK_NUMBERS_FOR_EVM_VERSION.stream().map(Arguments::of);
}
public class ContractCallSystemPrecompileHistoricalTest extends AbstractContractCallServiceTest {

@ParameterizedTest
@MethodSource("exchangeRateFunctionsProviderHistorical")
void exchangeRateFunctionsTestEthCallHistorical(final ExchangeRateFunctions contractFunc, BlockType blockNumber) {
final var functionHash = functionEncodeDecoder.functionHashFor(
contractFunc.name, EXCHANGE_RATE_PRECOMPILE_ABI_PATH, contractFunc.functionParameters);
final var serviceParameters = serviceParametersForExecution(
functionHash, EXCHANGE_RATE_PRECOMPILE_CONTRACT_ADDRESS, ETH_CALL, 0L, blockNumber);
final var successfulResponse = functionEncodeDecoder.encodedResultFor(
contractFunc.name, EXCHANGE_RATE_PRECOMPILE_ABI_PATH, contractFunc.expectedResultFields);

assertThat(contractCallService.processCall(serviceParameters)).isEqualTo(successfulResponse);
@CsvSource({"150", "100", "50", "49"})
void exchangeRatePrecompileTinycentsToTinybars(long blockNumber) throws Exception {
// Given
final var recordFile =
domainBuilder.recordFile().customize(f -> f.index(blockNumber)).persist();
testWeb3jService.setBlockType(BlockType.of(String.valueOf(blockNumber)));
testWeb3jService.setHistoricalRange(
Range.closedOpen(recordFile.getConsensusStart(), recordFile.getConsensusEnd()));
final var contract = testWeb3jService.deploy(ExchangeRatePrecompileHistorical::deploy);
// When
final var result =
contract.call_tinycentsToTinybars(BigInteger.valueOf(100L)).send();
// Then
assertThat(result).isEqualTo(BigInteger.valueOf(8L));
}

@ParameterizedTest
@MethodSource("blockNumberForDifferentEvmVersionsProviderHistorical")
void pseudoRandomGeneratorPrecompileFunctionsTestEthCallHistorical(BlockType blockNumber) {
final var functionName = "getPseudorandomSeed";
final var functionHash = functionEncodeDecoder.functionHashFor(functionName, PRNG_PRECOMPILE_ABI_PATH);
final var serviceParameters =
serviceParametersForExecution(functionHash, PRNG_CONTRACT_ADDRESS, ETH_CALL, 0L, blockNumber);

final var result = contractCallService.processCall(serviceParameters);

// Length of "0x" + 64 hex characters (2 per byte * 32 bytes)
assertEquals(66, result.length(), "The string should represent a 32-byte long array");
@CsvSource({"150", "100", "50", "49"})
void exchangeRatePrecompileTinybarsToTinycents(long blockNumber) throws Exception {
// Given
final var recordFile =
domainBuilder.recordFile().customize(f -> f.index(blockNumber)).persist();
testWeb3jService.setBlockType(BlockType.of(String.valueOf(blockNumber)));
testWeb3jService.setHistoricalRange(
Range.closedOpen(recordFile.getConsensusStart(), recordFile.getConsensusEnd()));
final var contract = testWeb3jService.deploy(ExchangeRatePrecompileHistorical::deploy);
// When
final var result =
contract.call_tinybarsToTinycents(BigInteger.valueOf(100L)).send();
// Then
assertThat(result).isEqualTo(BigInteger.valueOf(1200L));
}

@Getter
@RequiredArgsConstructor
enum ExchangeRateFunctions implements ContractFunctionProviderEnum {
TINYCENTS_TO_TINYBARS("tinycentsToTinybars", new Object[] {100L}, new Long[] {8L}),
TINYBARS_TO_TINYCENTS("tinybarsToTinycents", new Object[] {100L}, new Object[] {1200L});

private final String name;
private final Object[] functionParameters;
private final Object[] expectedResultFields;
@ParameterizedTest
@CsvSource({"150", "100", "50", "49"})
void pseudoRandomGeneratorPrecompileFunctionsTestEthCallHistorical(long blockNumber) throws Exception {
// Given
final var recordFile =
domainBuilder.recordFile().customize(f -> f.index(blockNumber)).persist();
testWeb3jService.setBlockType(BlockType.of(String.valueOf(blockNumber)));
testWeb3jService.setHistoricalRange(
Range.closedOpen(recordFile.getConsensusStart(), recordFile.getConsensusEnd()));
final var contract = testWeb3jService.deploy(PrngSystemContractHistorical::deploy);
// When
final var result = contract.call_getPseudorandomSeed().send();
// Then
assertEquals(32, result.length, "The string should represent a 32-byte long array");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1757,32 +1757,6 @@ protected void contractAllowancesPersist(
.persist();
}

// Contracts persist
protected void evmCodesContractPersist() {
final var evmCodesContractBytes = functionEncodeDecoder.getContractBytes(EVM_CODES_BYTES_PATH);
final var evmCodesContractEntityId = entityIdFromEvmAddress(EVM_CODES_CONTRACT_ADDRESS);
final var evmCodesContractEvmAddress = toEvmAddress(evmCodesContractEntityId);

domainBuilder
.entity()
.customize(e -> e.id(evmCodesContractEntityId.getId())
.num(evmCodesContractEntityId.getNum())
.evmAddress(evmCodesContractEvmAddress)
.type(CONTRACT)
.balance(1500L))
.persist();

domainBuilder
.contract()
.customize(c -> c.id(evmCodesContractEntityId.getId()).runtimeBytecode(evmCodesContractBytes))
.persist();

domainBuilder
.recordFile()
.customize(f -> f.bytes(evmCodesContractBytes))
.persist();
}

protected EntityId dynamicEthCallContractPresist() {
final var contractBytes = functionEncodeDecoder.getContractBytes(DYNAMIC_ETH_CALLS_BYTES_PATH);
final var contractEntityId = entityIdFromEvmAddress(DYNAMIC_ETH_CALLS_CONTRACT_ADDRESS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,17 @@ private enum NestedEthCallContractFunctions implements ContractFunctionProviderE
private final Object[] expectedResultFields;
}

@Getter
@RequiredArgsConstructor
enum ExchangeRateFunctions implements ContractFunctionProviderEnum {
TINYCENTS_TO_TINYBARS("tinycentsToTinybars", new Object[] {100L}, new Long[] {8L}),
TINYBARS_TO_TINYCENTS("tinybarsToTinycents", new Object[] {100L}, new Object[] {1200L});

private final String name;
private final Object[] functionParameters;
private final Object[] expectedResultFields;
}

@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DisplayName("processOpcodeCall")
Expand Down Expand Up @@ -1111,7 +1122,7 @@ void callWithContractResultNotFoundExceptionTest() {
contractEntityId = systemExchangeRateContractPersist();

final TransactionIdOrHashParameter transactionIdOrHash = setUp(
ContractCallSystemPrecompileHistoricalTest.ExchangeRateFunctions.TINYBARS_TO_TINYCENTS,
ExchangeRateFunctions.TINYBARS_TO_TINYCENTS,
TransactionType.CONTRACTCALL,
EXCHANGE_RATE_PRECOMPILE_CONTRACT_ADDRESS,
EXCHANGE_RATE_PRECOMPILE_ABI_PATH,
Expand All @@ -1129,7 +1140,7 @@ void callWithTransactionNotFoundExceptionTest() {
contractEntityId = systemExchangeRateContractPersist();

final TransactionIdOrHashParameter transactionIdOrHash = setUp(
ContractCallSystemPrecompileHistoricalTest.ExchangeRateFunctions.TINYCENTS_TO_TINYBARS,
ExchangeRateFunctions.TINYCENTS_TO_TINYBARS,
TransactionType.CONTRACTCALL,
EXCHANGE_RATE_PRECOMPILE_CONTRACT_ADDRESS,
EXCHANGE_RATE_PRECOMPILE_ABI_PATH,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public class ContractCallTestUtil {
* Checks if the *actual* gas usage is within 5-20% greater than the *expected* gas used from the initial call.
*
* @param estimatedGas The expected gas used from the initial call.
* @param actualGas The actual gas used.
* @param actualGas The actual gas used.
* @return {@code true} if the actual gas usage is within the expected range, otherwise {@code false}.
*/
public static boolean isWithinExpectedGasRange(final long estimatedGas, final long actualGas) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.5.0 <0.9.0;

contract ExchangeRatePrecompileHistorical {

uint256 constant TINY_PARTS_PER_WHOLE = 100_000_000;
address constant PRECOMPILE_ADDRESS = address(0x168);
bytes4 constant TINYCENTS_TO_TINYBARS = bytes4(keccak256("tinycentsToTinybars(uint256)"));
bytes4 constant TINYBARS_TO_TINYCENTS = bytes4(keccak256("tinybarsToTinycents(uint256)"));

function tinycentsToTinybars(uint256 tinycents) external payable returns (uint256 tinybars) {
(bool success, bytes memory result) = PRECOMPILE_ADDRESS.call{value: msg.value}(
abi.encodeWithSelector(TINYCENTS_TO_TINYBARS, tinycents));
require(success);
tinybars = abi.decode(result, (uint256));
}

function tinybarsToTinycents(uint256 tinybars) external payable returns (uint256 tinycents) {
(bool success, bytes memory result) = PRECOMPILE_ADDRESS.call{value: msg.value}(
abi.encodeWithSelector(TINYBARS_TO_TINYCENTS, tinybars));
require(success);
tinycents = abi.decode(result, (uint256));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.5.0 <0.9.0;

interface IPrngSystemContractHistorical {
// Generates a 256-bit pseudorandom seed using the first 256-bits of running hash from the latest RecordFile in the database.
// Users can generate a pseudorandom number in a specified range using the seed by (integer value of seed % range)
function getPseudorandomSeed() external returns (bytes32);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.5.0 <0.9.0;

import "./IPrngSystemContractHistorical.sol";

contract PrngSystemContractHistorical {
address constant PRECOMPILE_ADDRESS = address(0x169);

function getPseudorandomSeed() external payable returns (bytes32 randomBytes) {
(bool success, bytes memory result) = PRECOMPILE_ADDRESS.call{value: msg.value}(
abi.encodeWithSelector(IPrngSystemContractHistorical.getPseudorandomSeed.selector));
require(success);
randomBytes = abi.decode(result, (bytes32));
}
}

0 comments on commit 764b172

Please sign in to comment.