Skip to content

Commit

Permalink
Fix the rest ContractCallDynamicCallsTest tests (#10381)
Browse files Browse the repository at this point in the history
This PR fixes the setup for the ContractCallDynamicCallsTest tests when the modularizedServices flag is set to true. Also, the TransactionExecutionService is modified in case of a failure in an opcode trace scenario to return a failed result instead of throwing an exception directly. The reason for this is that we need to be able to return the opcode list from the context before throwing an exception.

---------

Signed-off-by: Bilyana Gospodinova <[email protected]>
  • Loading branch information
bilyana-gospodinova authored Feb 14, 2025
1 parent 3f123d1 commit 5393ea9
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public HederaEvmTransactionProcessingResult execute(
if (transactionRecord.receiptOrThrow().status() == com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS) {
result = buildSuccessResult(isContractCreate, transactionRecord, params);
} else {
handleFailedResult(transactionRecord, isContractCreate, gasUsedCounter);
result = handleFailedResult(transactionRecord, isContractCreate, gasUsedCounter);
}
return result;
}
Expand Down Expand Up @@ -128,7 +128,7 @@ private HederaEvmTransactionProcessingResult buildSuccessResult(
params.getReceiver());
}

private void handleFailedResult(
private HederaEvmTransactionProcessingResult handleFailedResult(
final TransactionRecord transactionRecord,
final boolean isContractCreate,
final MeterProvider<Counter> gasUsedCounter)
Expand All @@ -144,7 +144,15 @@ private void handleFailedResult(
var errorMessage = getErrorMessage(result).orElse(Bytes.EMPTY);
var detail = maybeDecodeSolidityErrorStringToReadableMessage(errorMessage);
updateErrorGasUsedMetric(gasUsedCounter, result.gasUsed(), 1);
throw new MirrorEvmTransactionException(status.protoName(), detail, errorMessage.toHexString());
if (ContractCallContext.get().getOpcodeTracerOptions() == null) {
throw new MirrorEvmTransactionException(status.protoName(), detail, errorMessage.toHexString());
} else {
// If we are in an opcode trace scenario, we need to return a failed result in order to get the
// opcode list from the ContractCallContext. If we throw an exception instead of returning a result,
// as in the regular case, we won't be able to get the opcode list.
return HederaEvmTransactionProcessingResult.failed(
result.gasUsed(), 0L, 0L, Optional.of(errorMessage), Optional.empty());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,21 @@
import com.hedera.mirror.web3.common.ContractCallContext;
import com.hedera.mirror.web3.evm.store.Store.OnMissing;
import com.hedera.mirror.web3.exception.MirrorEvmTransactionException;
import com.hedera.mirror.web3.state.MirrorNodeState;
import com.hedera.mirror.web3.utils.ContractFunctionProviderRecord;
import com.hedera.mirror.web3.web3j.generated.DynamicEthCalls;
import com.hedera.mirror.web3.web3j.generated.DynamicEthCalls.AccountAmount;
import com.hedera.mirror.web3.web3j.generated.DynamicEthCalls.NftTransfer;
import com.hedera.mirror.web3.web3j.generated.DynamicEthCalls.TokenTransferList;
import com.hedera.mirror.web3.web3j.generated.DynamicEthCalls.TransferList;
import jakarta.annotation.PostConstruct;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.hyperledger.besu.datatypes.Address;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -235,6 +242,63 @@ void associateTokenTransferEthCallFail() {
verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contractFunctionProvider);
}

@Test
void associateTokenTransferEthCallFailModularized() throws InvocationTargetException, IllegalAccessException {
// Given
final var modularizedServicesFlag = mirrorNodeEvmProperties.isModularizedServices();
final var backupProperties = mirrorNodeEvmProperties.getProperties();

try {
mirrorNodeEvmProperties.setModularizedServices(true);
// Re-init the captors, because the flag was changed.
super.setUpArgumentCaptors();
Method postConstructMethod = Arrays.stream(MirrorNodeState.class.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(PostConstruct.class))
.findFirst()
.orElseThrow(() -> new IllegalStateException("@PostConstruct method not found"));

postConstructMethod.setAccessible(true); // Make the method accessible
postConstructMethod.invoke(state);

final Map<String, String> propertiesMap = new HashMap<>();
propertiesMap.put("contracts.maxRefundPercentOfGasLimit", "100");
propertiesMap.put("contracts.maxGasPerSec", "15000000");
mirrorNodeEvmProperties.setProperties(propertiesMap);

final var treasuryAccount = accountEntityPersist();
final var sender = accountEntityPersist();

final var contract = testWeb3jService.deploy(DynamicEthCalls::deploy);

// When
final var functionCall = contract.send_associateTokenTransfer(
toAddress(EntityId.of(21496934L)).toHexString(), // Not existing address
getAddressFromEntity(treasuryAccount),
getAddressFromEntity(sender),
BigInteger.ZERO,
BigInteger.ONE);

final var contractFunctionProvider = ContractFunctionProviderRecord.builder()
.contractAddress(Address.fromHexString(contract.getContractAddress()))
.expectedErrorMessage("Failed to associate tokens")
.build();

// Then
assertThatThrownBy(functionCall::send)
.isInstanceOf(MirrorEvmTransactionException.class)
.satisfies(ex -> {
MirrorEvmTransactionException exception = (MirrorEvmTransactionException) ex;
assertEquals("Failed to associate tokens", exception.getDetail());
});

verifyOpcodeTracerCall(functionCall.encodeFunctionCall(), contractFunctionProvider);
} finally {
// Restore changed property values.
mirrorNodeEvmProperties.setModularizedServices(modularizedServicesFlag);
mirrorNodeEvmProperties.setProperties(backupProperties);
}
}

@ParameterizedTest
@CsvSource(
textBlock =
Expand Down Expand Up @@ -340,7 +404,6 @@ void approveTokenGetAllowance(final TokenTypeEnum tokenType, final long amount,
: nftPersist(treasuryEntityId, ownerEntityId, spenderEntityId);
final var tokenId = token.getTokenId();
final var tokenAddress = toAddress(tokenId);
final var tokenEntityId = entityIdFromEvmAddress(toAddress(tokenId));

final var contract = testWeb3jService.deploy(DynamicEthCalls::deploy);
final var contractAddress = Address.fromHexString(contract.getContractAddress());
Expand All @@ -350,7 +413,7 @@ void approveTokenGetAllowance(final TokenTypeEnum tokenType, final long amount,
tokenAccountPersist(tokenId, ownerEntityId.getId());

if (tokenType == TokenTypeEnum.NON_FUNGIBLE_UNIQUE) {
nftAllowancePersist(tokenEntityId, contractEntityId, ownerEntityId);
nftAllowancePersist(tokenId, contractEntityId, ownerEntityId);
}

// When
Expand Down Expand Up @@ -390,14 +453,13 @@ void approveTokenTransferFromGetAllowanceGetBalance(
: nftPersist(treasuryEntityId, contractEntityId, spenderEntityId);
final var tokenId = token.getTokenId();
final var tokenAddress = toAddress(tokenId);
final var tokenEntityId = entityIdFromEvmAddress(tokenAddress);

tokenAccountPersist(tokenId, contractEntityId.getId());
tokenAccountPersist(tokenId, spenderEntityId.getId());
tokenAccountPersist(tokenId, ownerEntityId.getId());

if (tokenType == TokenTypeEnum.NON_FUNGIBLE_UNIQUE) {
nftAllowancePersist(tokenEntityId, contractEntityId, ownerEntityId);
nftAllowancePersist(tokenId, contractEntityId, ownerEntityId);
}

// When
Expand Down Expand Up @@ -546,6 +608,7 @@ void approveForAllCryptoTransferGetAllowance() {

tokenAccountPersist(tokenId, spenderEntityId.getId());
tokenAccountPersist(tokenId, contractEntityId.getId());
nftAllowancePersist(tokenId, contractEntityId, contractEntityId);

var tokenTransferList = new TokenTransferList(
tokenAddress.toHexString(),
Expand Down Expand Up @@ -587,6 +650,8 @@ void cryptoTransferFromGetAllowanceGetBalance(

tokenAccountPersist(tokenId, spenderEntityId.getId());
tokenAccountPersist(tokenId, contractEntityId.getId());
nftAllowancePersist(tokenId, spenderEntityId, contractEntityId);
nftAllowancePersist(tokenId, contractEntityId, contractEntityId);

TokenTransferList tokenTransferList;
if (tokenType == TokenTypeEnum.FUNGIBLE_COMMON) {
Expand Down Expand Up @@ -633,6 +698,7 @@ void transferFromNFTGetAllowance() {

tokenAccountPersist(tokenId, spenderEntityId.getId());
tokenAccountPersist(tokenId, contractEntityId.getId());
nftAllowancePersist(tokenId, contractEntityId, spenderEntityId);

// When
final var functionCall = contract.send_transferFromNFTGetAllowance(tokenAddress.toHexString(), BigInteger.ONE);
Expand Down Expand Up @@ -809,10 +875,10 @@ private EntityId spenderEntityPersistWithAlias() {
}

private void nftAllowancePersist(
final EntityId tokenEntityId, final EntityId spenderEntityId, final EntityId ownerEntityId) {
final long tokenEntityId, final EntityId spenderEntityId, final EntityId ownerEntityId) {
domainBuilder
.nftAllowance()
.customize(a -> a.tokenId(tokenEntityId.getId())
.customize(a -> a.tokenId(tokenEntityId)
.spender(spenderEntityId.getId())
.owner(ownerEntityId.getId())
.payerAccountId(ownerEntityId)
Expand Down

0 comments on commit 5393ea9

Please sign in to comment.