Skip to content

Commit

Permalink
feat: implement base hedera account service system contract classes (#…
Browse files Browse the repository at this point in the history
…13167)

Signed-off-by: lukelee-sl <luke.lee@swirldslabs.com>
  • Loading branch information
lukelee-sl committed May 15, 2024
1 parent 781dd27 commit 0e8eca1
Show file tree
Hide file tree
Showing 21 changed files with 834 additions and 356 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import static com.hedera.node.app.service.contract.impl.ContractServiceImpl.CONTRACT_SERVICE;
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE;
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.DIRECT_OR_TOKEN_REDIRECT;
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.DIRECT_OR_PROXY_REDIRECT;
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE;
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asLongZeroAddress;
import static contract.XTestConstants.PLACEHOLDER_CALL_BODY;
Expand Down Expand Up @@ -288,7 +288,7 @@ private void runHtsCallAndExpect(
given(addressChecks.hasParentDelegateCall(frame)).willReturn(requiresDelegatePermission);
Mockito.lenient().when(frame.getValue()).thenReturn(Wei.MAX_WEI);

final var attempt = callAttemptFactory.createCallAttemptFrom(input, DIRECT_OR_TOKEN_REDIRECT, frame);
final var attempt = callAttemptFactory.createCallAttemptFrom(input, DIRECT_OR_PROXY_REDIRECT, frame);
final var call = attempt.asExecutableCall();

final var pricedResult = requireNonNull(call).execute(frame);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public void externalizeResult(
}

@Override
public Transaction syntheticTransactionForHtsCall(Bytes input, ContractID contractID, boolean isViewCall) {
public Transaction syntheticTransactionForNativeCall(Bytes input, ContractID contractID, boolean isViewCall) {
var functionParameters = tuweniToPbjBytes(input);
var contractCallBodyBuilder =
ContractCallTransactionBody.newBuilder().contractID(contractID).functionParameters(functionParameters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public void externalizeResult(
* {@inheritDoc}
*/
@Override
public Transaction syntheticTransactionForHtsCall(Bytes input, ContractID contractID, boolean isViewCall) {
public Transaction syntheticTransactionForNativeCall(Bytes input, ContractID contractID, boolean isViewCall) {
// no-op
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ void externalizeResult(
* @param isViewCall
* @return
*/
Transaction syntheticTransactionForHtsCall(Bytes input, ContractID contractID, boolean isViewCall);
Transaction syntheticTransactionForNativeCall(Bytes input, ContractID contractID, boolean isViewCall);

/**
* Returns the {@link ExchangeRate} for the current consensus time. This will enable the translation from hbars
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023-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.node.app.service.contract.impl.exec.systemcontracts;

import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedContractId;

import com.hedera.hapi.node.base.ContractID;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractNativeSystemContract;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallFactory;
import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils;
import edu.umd.cs.findbugs.annotations.NonNull;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;

@Singleton
public class HasSystemContract extends AbstractNativeSystemContract implements HederaSystemContract {
public static final String HAS_SYSTEM_CONTRACT_NAME = "HAS";
public static final String HAS_EVM_ADDRESS = "0x16a";
public static final ContractID HAS_CONTRACT_ID = asNumberedContractId(Address.fromHexString(HAS_EVM_ADDRESS));

@Inject
public HasSystemContract(@NonNull final GasCalculator gasCalculator, @NonNull final HasCallFactory callFactory) {
super(HAS_SYSTEM_CONTRACT_NAME, callFactory, HAS_CONTRACT_ID, gasCalculator);
}

// Call type is not relevant for the HAS system contracts
@Override
protected FrameUtils.CallType callTypeOf(MessageFrame frame) {
return FrameUtils.callTypeForAccountOf(frame);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,166 +16,32 @@

package com.hedera.node.app.service.contract.impl.exec.systemcontracts;

import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS;
import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED;
import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS;
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult;
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CallType.UNQUALIFIED_DELEGATE;
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.callTypeOf;
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf;
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor;
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedContractId;
import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes;
import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.contractFunctionResultFailedFor;
import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.successResultOf;
import static com.hedera.node.app.service.evm.utils.ValidationUtils.validateTrue;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TRANSACTION_BODY;
import static java.util.Objects.requireNonNull;
import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION;
import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.PRECOMPILE_ERROR;

import com.hedera.hapi.node.base.ContractID;
import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractNativeSystemContract;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory;
import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater;
import com.hedera.node.app.spi.workflows.HandleException;
import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils;
import edu.umd.cs.findbugs.annotations.NonNull;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;

@Singleton
public class HtsSystemContract extends AbstractFullContract implements HederaSystemContract {
private static final Logger log = LogManager.getLogger(HtsSystemContract.class);

public class HtsSystemContract extends AbstractNativeSystemContract implements HederaSystemContract {
public static final String HTS_SYSTEM_CONTRACT_NAME = "HTS";
public static final String HTS_EVM_ADDRESS = "0x167";
public static final ContractID HTS_CONTRACT_ID = asNumberedContractId(Address.fromHexString(HTS_EVM_ADDRESS));

private final HtsCallFactory callFactory;

@Inject
public HtsSystemContract(@NonNull final GasCalculator gasCalculator, @NonNull final HtsCallFactory callFactory) {
super(HTS_SYSTEM_CONTRACT_NAME, gasCalculator);
this.callFactory = requireNonNull(callFactory);
super(HTS_SYSTEM_CONTRACT_NAME, callFactory, HTS_CONTRACT_ID, gasCalculator);
}

@Override
public FullResult computeFully(@NonNull final Bytes input, @NonNull final MessageFrame frame) {
requireNonNull(input);
requireNonNull(frame);
final var callType = callTypeOf(frame);
if (callType == UNQUALIFIED_DELEGATE) {
return haltResult(PRECOMPILE_ERROR, frame.getRemainingGas());
}
final Call call;
final HtsCallAttempt attempt;
try {
validateTrue(input.size() >= 4, INVALID_TRANSACTION_BODY);
attempt = callFactory.createCallAttemptFrom(input, callType, frame);
call = requireNonNull(attempt.asExecutableCall());
if (frame.isStatic() && !call.allowsStaticFrame()) {
// FUTURE - we should really set an explicit halt reason here; instead we just halt the frame
// without setting a halt reason to simulate mono-service for differential testing
return haltResult(contractsConfigOf(frame).precompileHtsDefaultGasCost());
}
} catch (final Exception ignore) {
// Input that cannot be translated to an executable call, for any
// reason, halts the frame and consumes all remaining gas
return haltResult(INVALID_OPERATION, frame.getRemainingGas());
}
return resultOfExecuting(attempt, call, input, frame);
}

@SuppressWarnings({"java:S2637", "java:S2259"}) // this function is going to be refactored soon.
private static FullResult resultOfExecuting(
@NonNull final HtsCallAttempt attempt,
@NonNull final Call call,
@NonNull final Bytes input,
@NonNull final MessageFrame frame) {
final Call.PricedResult pricedResult;
try {
pricedResult = call.execute(frame);
final var gasRequirement = pricedResult.fullResult().gasRequirement();
final var insufficientGas = frame.getRemainingGas() < gasRequirement;
final var dispatchedRecordBuilder = pricedResult.fullResult().recordBuilder();
if (dispatchedRecordBuilder != null) {
if (insufficientGas) {
dispatchedRecordBuilder.status(INSUFFICIENT_GAS);
dispatchedRecordBuilder.contractCallResult(pricedResult.asResultOfInsufficientGasRemaining(
attempt.senderId(), HTS_CONTRACT_ID, tuweniToPbjBytes(input), frame.getRemainingGas()));
} else {
dispatchedRecordBuilder.contractCallResult(pricedResult.asResultOfCall(
attempt.senderId(), HTS_CONTRACT_ID, tuweniToPbjBytes(input), frame.getRemainingGas()));
}
} else if (pricedResult.isViewCall()) {
final var proxyWorldUpdater = proxyUpdaterFor(frame);
final var enhancement = proxyWorldUpdater.enhancement();
// Insufficient gas preempts any other response code
final var status = insufficientGas ? INSUFFICIENT_GAS : pricedResult.responseCode();
if (status == SUCCESS) {
enhancement
.systemOperations()
.externalizeResult(
successResultOf(
attempt.senderId(),
pricedResult.fullResult(),
frame,
!call.allowsStaticFrame()),
pricedResult.responseCode(),
enhancement
.systemOperations()
.syntheticTransactionForHtsCall(input, HTS_CONTRACT_ID, true));
} else {
externalizeFailure(
gasRequirement,
input,
insufficientGas
? Bytes.EMPTY
: pricedResult.fullResult().output(),
attempt,
status,
enhancement);
}
}
} catch (final HandleException handleException) {
return haltHandleException(handleException, frame.getRemainingGas());
} catch (final Exception internal) {
log.error("Unhandled failure for input {} to HTS system contract", input, internal);
return haltResult(PRECOMPILE_ERROR, frame.getRemainingGas());
}
return pricedResult.fullResult();
}

private static void externalizeFailure(
final long gasRequirement,
@NonNull final Bytes input,
@NonNull final Bytes output,
@NonNull final HtsCallAttempt attempt,
@NonNull final ResponseCodeEnum status,
@NonNull final HederaWorldUpdater.Enhancement enhancement) {
enhancement
.systemOperations()
.externalizeResult(
contractFunctionResultFailedFor(
attempt.senderId(), output, gasRequirement, status.toString(), HTS_CONTRACT_ID),
status,
enhancement.systemOperations().syntheticTransactionForHtsCall(input, HTS_CONTRACT_ID, true));
}

// potentially other cases could be handled here if necessary
private static FullResult haltHandleException(final HandleException handleException, long remainingGas) {
if (handleException.getStatus().equals(MAX_CHILD_RECORDS_EXCEEDED)) {
return haltResult(CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS, remainingGas);
}
throw handleException;
protected FrameUtils.CallType callTypeOf(MessageFrame frame) {
return FrameUtils.callTypeOf(frame);
}
}

0 comments on commit 0e8eca1

Please sign in to comment.