From f0702214cbacbaf543560ffcf2890f1da3888004 Mon Sep 17 00:00:00 2001 From: lukelee-sl <109538178+lukelee-sl@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:29:19 -0800 Subject: [PATCH] feat: Implement lazyCreationCostInGas method (#10337) Signed-off-by: lukelee-sl --- .../java/contract/AbstractContractXTest.java | 9 ++-- .../exec/scope/HandleHederaOperations.java | 46 +++++++++++++++++-- .../impl/exec/scope/HederaOperations.java | 4 +- .../exec/scope/QueryHederaOperations.java | 3 +- .../impl/state/ProxyWorldUpdater.java | 2 +- .../scope/HandleHederaOperationsTest.java | 21 +++++++-- .../test/state/ProxyWorldUpdaterTest.java | 6 +-- 7 files changed, 75 insertions(+), 16 deletions(-) diff --git a/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java index 5a38cfa31797..059e264efccb 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java @@ -276,12 +276,17 @@ private void runHtsCallAndExpect( context.exchangeRateInfo().activeRate(Instant.now()), context.resourcePricesFor(HederaFunctionality.CONTRACT_CALL, SubType.DEFAULT), context.resourcePricesFor(HederaFunctionality.CONTRACT_CALL, SubType.DEFAULT)); + final var systemContractGasCalculator = new SystemContractGasCalculator( + tinybarValues, + new CanonicalDispatchPrices(new AssetsLoader()), + (body, payerId) -> context.dispatchComputeFees(body, payerId).totalFee()); final var enhancement = new HederaWorldUpdater.Enhancement( new HandleHederaOperations( component.config().getConfigData(LedgerConfig.class), component.config().getConfigData(ContractsConfig.class), context, tinybarValues, + systemContractGasCalculator, component.config().getConfigData(HederaConfig.class)), new HandleHederaNativeOperations(context), new HandleSystemContractOperations(context)); @@ -290,10 +295,6 @@ private void runHtsCallAndExpect( given(frame.getSenderAddress()).willReturn(sender); final Deque stack = new ArrayDeque<>(); given(initialFrame.getContextVariable(CONFIG_CONTEXT_VARIABLE)).willReturn(component.config()); - final var systemContractGasCalculator = new SystemContractGasCalculator( - tinybarValues, - new CanonicalDispatchPrices(new AssetsLoader()), - (body, payerId) -> context.dispatchComputeFees(body, payerId).totalFee()); given(initialFrame.getContextVariable(SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE)) .willReturn(systemContractGasCalculator); stack.push(initialFrame); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java index 75d528323f80..1dcd74540e39 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java @@ -18,7 +18,11 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.*; +import static com.hedera.node.app.service.mono.txns.crypto.AbstractAutoCreationLogic.LAZY_MEMO; +import static com.hedera.node.app.service.mono.txns.crypto.AbstractAutoCreationLogic.THREE_MONTHS_IN_SECONDS; +import static com.hedera.node.app.spi.key.KeyUtils.IMMUTABILITY_SENTINEL_KEY; import static com.hedera.node.app.spi.workflows.record.ExternalizedRecordCustomizer.SUPPRESSING_EXTERNALIZED_RECORD_CUSTOMIZER; import static java.util.Objects.requireNonNull; @@ -26,9 +30,12 @@ import com.hedera.hapi.node.contract.ContractCreateTransactionBody; import com.hedera.hapi.node.contract.ContractFunctionResult; import com.hedera.hapi.node.token.CryptoCreateTransactionBody; +import com.hedera.hapi.node.token.CryptoUpdateTransactionBody; import com.hedera.hapi.node.transaction.SignedTransaction; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.annotations.TransactionScope; +import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; import com.hedera.node.app.service.contract.impl.records.ContractCreateRecordBuilder; import com.hedera.node.app.service.contract.impl.state.ContractStateStore; @@ -50,6 +57,7 @@ import java.util.List; import java.util.Optional; import javax.inject.Inject; +import org.hyperledger.besu.datatypes.Address; /** * A fully mutable {@link HederaOperations} implementation based on a {@link HandleContext}. @@ -59,10 +67,24 @@ public class HandleHederaOperations implements HederaOperations { public static final Bytes ZERO_ENTROPY = Bytes.fromHex( "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + private static final CryptoUpdateTransactionBody.Builder UPDATE_TXN_BODY_BUILDER = + CryptoUpdateTransactionBody.newBuilder() + .key(Key.newBuilder().ecdsaSecp256k1(Bytes.EMPTY).build()); + + private static final CryptoCreateTransactionBody.Builder CREATE_TXN_BODY_BUILDER = + CryptoCreateTransactionBody.newBuilder() + .initialBalance(0) + .maxAutomaticTokenAssociations(0) + .autoRenewPeriod(Duration.newBuilder().seconds(THREE_MONTHS_IN_SECONDS)) + .key(IMMUTABILITY_SENTINEL_KEY) + .memo(LAZY_MEMO); + private final TinybarValues tinybarValues; private final LedgerConfig ledgerConfig; private final ContractsConfig contractsConfig; private final HederaConfig hederaConfig; + private final SystemContractGasCalculator gasCalculator; + private final HandleContext context; @Inject @@ -71,12 +93,14 @@ public HandleHederaOperations( @NonNull final ContractsConfig contractsConfig, @NonNull final HandleContext context, @NonNull final TinybarValues tinybarValues, + @NonNull final SystemContractGasCalculator gasCalculator, @NonNull final HederaConfig hederaConfig) { this.ledgerConfig = requireNonNull(ledgerConfig); this.contractsConfig = requireNonNull(contractsConfig); this.context = requireNonNull(context); this.tinybarValues = requireNonNull(tinybarValues); this.hederaConfig = requireNonNull(hederaConfig); + this.gasCalculator = requireNonNull(gasCalculator); } /** @@ -154,9 +178,25 @@ public long contractCreationLimit() { * {@inheritDoc} */ @Override - public long lazyCreationCostInGas() { - // TODO - implement correctly - return 1L; + public long lazyCreationCostInGas(@NonNull final Address recipient) { + final var payerId = context.payer(); + // Calculate gas for a CryptoCreateTransactionBody with an alias address + final var createFee = gasCalculator.gasRequirement( + TransactionBody.newBuilder() + .cryptoCreateAccount(CREATE_TXN_BODY_BUILDER.alias(tuweniToPbjBytes(recipient))) + .build(), + DispatchType.CRYPTO_CREATE, + payerId); + + // Calculate gas for an update TransactionBody + final var updateFee = gasCalculator.gasRequirement( + TransactionBody.newBuilder() + .cryptoUpdateAccount(UPDATE_TXN_BODY_BUILDER) + .build(), + DispatchType.CRYPTO_UPDATE, + payerId); + + return createFee + updateFee; } /** diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java index b9bfed6cbaca..e75601780be0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java @@ -32,6 +32,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; +import org.hyperledger.besu.datatypes.Address; /** * Provides the Hedera operations that only a {@link ProxyWorldUpdater} needs (but not a {@link DispatchingEvmFrameState}. @@ -109,10 +110,11 @@ public interface HederaOperations { /** * Returns the lazy creation cost within this scope. + * @param recipient the recipient contract address * * @return the lazy creation cost in gas */ - long lazyCreationCostInGas(); + long lazyCreationCostInGas(@NonNull final Address recipient); /** * Returns the gas price in tinybars within this scope. diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java index 615ddc38ad4b..df5d95b8a42b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java @@ -34,6 +34,7 @@ import java.util.Objects; import java.util.Optional; import javax.inject.Inject; +import org.hyperledger.besu.datatypes.Address; /** * TODO - a read-only {@link HederaOperations} implementation based on a {@link QueryContext}. @@ -128,7 +129,7 @@ public long contractCreationLimit() { * @throws UnsupportedOperationException always */ @Override - public long lazyCreationCostInGas() { + public long lazyCreationCostInGas(@NonNull final Address recipient) { throw new UnsupportedOperationException("Queries cannot get lazy creation cost"); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java index 147304012dda..ac47e1ef3fa8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java @@ -217,7 +217,7 @@ public Optional tryTransfer( @Override public Optional tryLazyCreation( @NonNull final Address recipient, @NonNull final MessageFrame frame) { - final var gasCost = enhancement.operations().lazyCreationCostInGas(); + final var gasCost = enhancement.operations().lazyCreationCostInGas(recipient); if (gasCost > frame.getRemainingGas()) { return Optional.of(INSUFFICIENT_GAS); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java index 423257ac2d30..4a5a7ab83e19 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java @@ -38,6 +38,8 @@ import com.hedera.hapi.node.token.TokenCreateTransactionBody; import com.hedera.hapi.node.transaction.SignedTransaction; import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; import com.hedera.node.app.service.contract.impl.exec.scope.HandleHederaOperations; import com.hedera.node.app.service.contract.impl.exec.scope.HederaOperations; @@ -93,12 +95,20 @@ class HandleHederaOperationsTest { @Mock private FeeCalculator feeCalculator; + @Mock + private SystemContractGasCalculator gasCalculator; + private HandleHederaOperations subject; @BeforeEach void setUp() { subject = new HandleHederaOperations( - DEFAULT_LEDGER_CONFIG, DEFAULT_CONTRACTS_CONFIG, context, tinybarValues, DEFAULT_HEDERA_CONFIG); + DEFAULT_LEDGER_CONFIG, + DEFAULT_CONTRACTS_CONFIG, + context, + tinybarValues, + gasCalculator, + DEFAULT_HEDERA_CONFIG); } @Test @@ -194,8 +204,13 @@ void commitIsNoopUntilSavepointExposesIt() { } @Test - void lazyCreationCostInGasHardcoded() { - assertEquals(1L, subject.lazyCreationCostInGas()); + void lazyCreationCostInGasTest() { + given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); + given(gasCalculator.gasRequirement(any(), eq(DispatchType.CRYPTO_CREATE), eq(A_NEW_ACCOUNT_ID))) + .willReturn(6L); + given(gasCalculator.gasRequirement(any(), eq(DispatchType.CRYPTO_UPDATE), eq(A_NEW_ACCOUNT_ID))) + .willReturn(5L); + assertEquals(11L, subject.lazyCreationCostInGas(NON_SYSTEM_LONG_ZERO_ADDRESS)); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java index efeb4e16d723..8bd0c92a5a06 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java @@ -437,7 +437,7 @@ void delegatesTransfer() { @Test void abortsLazyCreationIfRemainingGasInsufficient() { final var pretendCost = 1_234L; - given(hederaOperations.lazyCreationCostInGas()).willReturn(pretendCost); + given(hederaOperations.lazyCreationCostInGas(SOME_EVM_ADDRESS)).willReturn(pretendCost); given(frame.getRemainingGas()).willReturn(pretendCost - 1); final var maybeHaltReason = subject.tryLazyCreation(SOME_EVM_ADDRESS, frame); assertTrue(maybeHaltReason.isPresent()); @@ -447,7 +447,7 @@ void abortsLazyCreationIfRemainingGasInsufficient() { @Test void delegatesLazyCreationAndDecrementsGasCostOnSuccess() { final var pretendCost = 1_234L; - given(hederaOperations.lazyCreationCostInGas()).willReturn(pretendCost); + given(hederaOperations.lazyCreationCostInGas(SOME_EVM_ADDRESS)).willReturn(pretendCost); given(frame.getRemainingGas()).willReturn(pretendCost * 2); given(evmFrameState.tryLazyCreation(SOME_EVM_ADDRESS)).willReturn(Optional.empty()); final var maybeHaltReason = subject.tryLazyCreation(SOME_EVM_ADDRESS, frame); @@ -459,7 +459,7 @@ void delegatesLazyCreationAndDecrementsGasCostOnSuccess() { void doesntBothDecrementingGasOnLazyCreationFailureSinceAboutToHalt() { final var pretendCost = 1_234L; final var haltReason = Optional.of(FAILURE_DURING_LAZY_ACCOUNT_CREATION); - given(hederaOperations.lazyCreationCostInGas()).willReturn(pretendCost); + given(hederaOperations.lazyCreationCostInGas(SOME_EVM_ADDRESS)).willReturn(pretendCost); given(frame.getRemainingGas()).willReturn(pretendCost * 2); given(evmFrameState.tryLazyCreation(SOME_EVM_ADDRESS)).willReturn(haltReason); final var maybeHaltReason = subject.tryLazyCreation(SOME_EVM_ADDRESS, frame);