From b85211ed12bd76e7facfee13f6eb699411de7ab7 Mon Sep 17 00:00:00 2001 From: JivkoKelchev Date: Thu, 28 Nov 2024 04:50:11 +0200 Subject: [PATCH] fix: Set the designated payer when building an EVM transactions from scheduled transactions (#16708) Signed-off-by: Zhivko Kelchev Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Signed-off-by: ibankov Signed-off-by: Michael Tinker Co-authored-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Co-authored-by: ibankov Co-authored-by: Michael Tinker Co-authored-by: Joseph S. <121976561+jsync-swirlds@users.noreply.github.com> --- .../exec/ContextTransactionProcessor.java | 2 +- .../impl/infra/HevmTransactionFactory.java | 12 ++-- .../exec/ContextTransactionProcessorTest.java | 51 +++++++++------ .../EthereumTransactionHandlerTest.java | 10 ++- .../infra/HevmTransactionFactoryTest.java | 64 +++++++++++-------- .../hip423/ScheduleLongTermExecutionTest.java | 48 ++++++++------ 6 files changed, 115 insertions(+), 72 deletions(-) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java index ab4ad6788e99..a4ae69fd18b3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java @@ -174,7 +174,7 @@ public CallOutcome call() { private HederaEvmTransaction safeCreateHevmTransaction() { try { - return hevmTransactionFactory.fromHapiTransaction(context.body()); + return hevmTransactionFactory.fromHapiTransaction(context.body(), context.payer()); } catch (HandleException e) { // Return a HederaEvmTransaction that represents the error in order to charge fees to the sender return hevmTransactionFactory.fromContractTxException(context.body(), e); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java index c215779d6405..df8a14f84f01 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java @@ -144,17 +144,15 @@ public HevmTransactionFactory( * Given a {@link TransactionBody}, creates the implied {@link HederaEvmTransaction}. * * @param body the {@link TransactionBody} to convert + * @param payerId transaction payer id * @return the implied {@link HederaEvmTransaction} * @throws IllegalArgumentException if the {@link TransactionBody} is not a contract operation */ - public HederaEvmTransaction fromHapiTransaction(@NonNull final TransactionBody body) { + public HederaEvmTransaction fromHapiTransaction(@NonNull final TransactionBody body, @NonNull AccountID payerId) { return switch (body.data().kind()) { - case CONTRACT_CREATE_INSTANCE -> fromHapiCreate( - body.transactionIDOrThrow().accountIDOrThrow(), body.contractCreateInstanceOrThrow()); - case CONTRACT_CALL -> fromHapiCall( - body.transactionIDOrThrow().accountIDOrThrow(), body.contractCallOrThrow()); - case ETHEREUM_TRANSACTION -> fromHapiEthereum( - body.transactionIDOrThrow().accountIDOrThrow(), body.ethereumTransactionOrThrow()); + case CONTRACT_CREATE_INSTANCE -> fromHapiCreate(payerId, body.contractCreateInstanceOrThrow()); + case CONTRACT_CALL -> fromHapiCall(payerId, body.contractCallOrThrow()); + case ETHEREUM_TRANSACTION -> fromHapiEthereum(payerId, body.ethereumTransactionOrThrow()); default -> throw new IllegalArgumentException("Not a contract operation"); }; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java index 899c6413df35..ef6b5d29663b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.TimestampSeconds; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.transaction.ExchangeRate; @@ -132,9 +133,7 @@ void callsComponentInfraAsExpectedForValidEthTx() { customGasCharging); givenSenderAccount(); - given(context.body()).willReturn(TransactionBody.DEFAULT); - given(hevmTransactionFactory.fromHapiTransaction(TransactionBody.DEFAULT)) - .willReturn(HEVM_CREATION); + givenBodyWithTxnIdWillReturnHEVM(); given(processor.processTransaction( HEVM_CREATION, rootProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) .willReturn(SUCCESS_RESULT_WITH_SIGNER_NONCE); @@ -171,9 +170,7 @@ void callsComponentInfraAsExpectedForValidEthTxWithoutTo() { customGasCharging); givenSenderAccount(); - given(context.body()).willReturn(TransactionBody.DEFAULT); - given(hevmTransactionFactory.fromHapiTransaction(TransactionBody.DEFAULT)) - .willReturn(HEVM_CREATION); + givenBodyWithTxnIdWillReturnHEVM(); given(processor.processTransaction( HEVM_CREATION, rootProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) .willReturn(SUCCESS_RESULT_WITH_SIGNER_NONCE); @@ -208,9 +205,7 @@ void callsComponentInfraAsExpectedForNonEthTx() { processor, customGasCharging); - given(context.body()).willReturn(TransactionBody.DEFAULT); - given(hevmTransactionFactory.fromHapiTransaction(TransactionBody.DEFAULT)) - .willReturn(HEVM_CREATION); + givenBodyWithTxnIdWillReturnHEVM(); given(processor.processTransaction( HEVM_CREATION, rootProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) .willReturn(SUCCESS_RESULT); @@ -240,9 +235,7 @@ void stillChargesHapiFeesOnAbort() { processor, customGasCharging); - given(context.body()).willReturn(TransactionBody.DEFAULT); - given(hevmTransactionFactory.fromHapiTransaction(TransactionBody.DEFAULT)) - .willReturn(HEVM_CREATION); + givenBodyWithTxnIdWillReturnHEVM(); given(processor.processTransaction( HEVM_CREATION, rootProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) .willThrow(new HandleException(INVALID_CONTRACT_ID)); @@ -272,8 +265,9 @@ void chargeHapiFeeOnFailedEthTransaction() { customGasCharging); given(context.body()).willReturn(TransactionBody.DEFAULT); + given(context.payer()).willReturn(AccountID.DEFAULT); final var ethTx = wellKnownRelayedHapiCallWithGasLimit(1_000_000L); - given(hevmTransactionFactory.fromHapiTransaction(TransactionBody.DEFAULT)) + given(hevmTransactionFactory.fromHapiTransaction(TransactionBody.DEFAULT, context.payer())) .willReturn(ethTx); given(processor.processTransaction( ethTx, rootProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) @@ -308,7 +302,10 @@ void stillChargesGasFeesOnHevmException() { customGasCharging); given(context.body()).willReturn(transactionBody); - given(hevmTransactionFactory.fromHapiTransaction(transactionBody)).willReturn(HEVM_Exception); + final var payer = AccountID.DEFAULT; + given(context.payer()).willReturn(payer); + given(hevmTransactionFactory.fromHapiTransaction(transactionBody, payer)) + .willReturn(HEVM_Exception); given(transactionBody.transactionIDOrThrow()).willReturn(transactionID); given(transactionID.accountIDOrThrow()).willReturn(SENDER_ID); @@ -337,7 +334,10 @@ void doesNotChargeGasFeesOnHevmExceptionIfSoConfigured() { customGasCharging); given(context.body()).willReturn(transactionBody); - given(hevmTransactionFactory.fromHapiTransaction(transactionBody)).willReturn(HEVM_Exception); + final var payer = AccountID.DEFAULT; + given(context.payer()).willReturn(payer); + given(hevmTransactionFactory.fromHapiTransaction(transactionBody, payer)) + .willReturn(HEVM_Exception); given(transactionBody.transactionIDOrThrow()).willReturn(transactionID); given(transactionID.accountIDOrThrow()).willReturn(SENDER_ID); @@ -366,7 +366,9 @@ void stillChargesGasFeesOnExceptionThrown() { customGasCharging); given(context.body()).willReturn(transactionBody); - given(hevmTransactionFactory.fromHapiTransaction(transactionBody)) + final var payer = AccountID.DEFAULT; + given(context.payer()).willReturn(payer); + given(hevmTransactionFactory.fromHapiTransaction(transactionBody, payer)) .willThrow(new HandleException(INVALID_CONTRACT_ID)); given(hevmTransactionFactory.fromContractTxException(any(), any())).willReturn(HEVM_Exception); given(transactionBody.transactionIDOrThrow()).willReturn(transactionID); @@ -399,7 +401,9 @@ void doesNotChargeGasAndHapiFeesOnExceptionThrownIfSoConfigured() { customGasCharging); given(context.body()).willReturn(transactionBody); - given(hevmTransactionFactory.fromHapiTransaction(transactionBody)) + final var payer = AccountID.DEFAULT; + given(context.payer()).willReturn(payer); + given(hevmTransactionFactory.fromHapiTransaction(transactionBody, payer)) .willThrow(new HandleException(INVALID_CONTRACT_ID)); given(hevmTransactionFactory.fromContractTxException(any(), any())).willReturn(HEVM_Exception); given(transactionBody.transactionIDOrThrow()).willReturn(transactionID); @@ -432,9 +436,10 @@ void reThrowsExceptionWhenNotContractCall() { customGasCharging); given(context.body()).willReturn(transactionBody); + given(context.payer()).willReturn(SENDER_ID); given(transactionBody.transactionIDOrThrow()).willReturn(transactionID); given(transactionID.accountIDOrThrow()).willReturn(SENDER_ID); - given(hevmTransactionFactory.fromHapiTransaction(transactionBody)) + given(hevmTransactionFactory.fromHapiTransaction(transactionBody, SENDER_ID)) .willThrow(new HandleException(INVALID_CONTRACT_ID)); given(hevmTransactionFactory.fromContractTxException(any(), any())).willReturn(HEVM_Exception); @@ -467,4 +472,14 @@ void givenSenderAccount() { given(rootProxyWorldUpdater.getHederaAccount(SENDER_ID)).willReturn(senderAccount); given(senderAccount.getNonce()).willReturn(1L); } + + void givenBodyWithTxnIdWillReturnHEVM() { + final var body = TransactionBody.newBuilder() + .transactionID(TransactionID.DEFAULT) + .build(); + final var payer = AccountID.DEFAULT; + given(context.body()).willReturn(body); + given(context.payer()).willReturn(payer); + given(hevmTransactionFactory.fromHapiTransaction(body, payer)).willReturn(HEVM_CREATION); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java index 151706c9caa5..056586e0d673 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java @@ -37,6 +37,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.contract.EthereumTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; @@ -177,7 +179,13 @@ void setUpTransactionProcessing() { customGasCharging); given(component.contextTransactionProcessor()).willReturn(contextTransactionProcessor); - given(hevmTransactionFactory.fromHapiTransaction(handleContext.body())).willReturn(HEVM_CREATION); + final var body = TransactionBody.newBuilder() + .transactionID(TransactionID.DEFAULT) + .build(); + given(handleContext.body()).willReturn(body); + given(handleContext.payer()).willReturn(AccountID.DEFAULT); + given(hevmTransactionFactory.fromHapiTransaction(handleContext.body(), handleContext.payer())) + .willReturn(HEVM_CREATION); given(transactionProcessor.processTransaction( HEVM_CREATION, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/HevmTransactionFactoryTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/HevmTransactionFactoryTest.java index 87c84f80e411..c8c343cb5794 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/HevmTransactionFactoryTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/infra/HevmTransactionFactoryTest.java @@ -528,7 +528,9 @@ void fromHapiCreationSkips0xPrefixFromInitcodeIfPresent() { @Test void fromHapiTransactionThrowsOnNonContractOperation() { - assertThrows(IllegalArgumentException.class, () -> subject.fromHapiTransaction(TransactionBody.DEFAULT)); + assertThrows( + IllegalArgumentException.class, + () -> subject.fromHapiTransaction(TransactionBody.DEFAULT, AccountID.DEFAULT)); } @Test @@ -627,53 +629,65 @@ private void assertCreateFailsWith( @NonNull final Consumer spec) { assertFailsWith( status, - () -> subject.fromHapiTransaction(TransactionBody.newBuilder() - .transactionID(TransactionID.newBuilder().accountID(SENDER_ID)) - .contractCreateInstance(createWith(spec)) - .build())); + () -> subject.fromHapiTransaction( + TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder().accountID(SENDER_ID)) + .contractCreateInstance(createWith(spec)) + .build(), + SENDER_ID)); } private void assertCallFailsWith( @NonNull final ResponseCodeEnum status, @NonNull final Consumer spec) { assertFailsWith( status, - () -> subject.fromHapiTransaction(TransactionBody.newBuilder() - .transactionID(TransactionID.newBuilder().accountID(SENDER_ID)) - .contractCall(callWith(spec)) - .build())); + () -> subject.fromHapiTransaction( + TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder().accountID(SENDER_ID)) + .contractCall(callWith(spec)) + .build(), + SENDER_ID)); } private void assertEthTxFailsWith( @NonNull final ResponseCodeEnum status, @NonNull final Consumer spec) { assertFailsWith( status, - () -> subject.fromHapiTransaction(TransactionBody.newBuilder() - .transactionID(TransactionID.newBuilder().accountID(SENDER_ID)) - .ethereumTransaction(ethTxWith(spec)) - .build())); + () -> subject.fromHapiTransaction( + TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder().accountID(SENDER_ID)) + .ethereumTransaction(ethTxWith(spec)) + .build(), + SENDER_ID)); } private HederaEvmTransaction getManufacturedEthTx(@NonNull final Consumer spec) { - return subject.fromHapiTransaction(TransactionBody.newBuilder() - .transactionID(TransactionID.newBuilder().accountID(RELAYER_ID)) - .ethereumTransaction(ethTxWith(spec)) - .build()); + return subject.fromHapiTransaction( + TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder().accountID(RELAYER_ID)) + .ethereumTransaction(ethTxWith(spec)) + .build(), + RELAYER_ID); } private HederaEvmTransaction getManufacturedCreation( @NonNull final Consumer spec) { - return subject.fromHapiTransaction(TransactionBody.newBuilder() - .transactionID(TransactionID.newBuilder().accountID(SENDER_ID)) - .contractCreateInstance(createWith(spec)) - .build()); + return subject.fromHapiTransaction( + TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder().accountID(SENDER_ID)) + .contractCreateInstance(createWith(spec)) + .build(), + SENDER_ID); } private HederaEvmTransaction getManufacturedCall( @NonNull final Consumer spec) { - return subject.fromHapiTransaction(TransactionBody.newBuilder() - .transactionID(TransactionID.newBuilder().accountID(SENDER_ID)) - .contractCall(callWith(spec)) - .build()); + return subject.fromHapiTransaction( + TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder().accountID(SENDER_ID)) + .contractCall(callWith(spec)) + .build(), + SENDER_ID); } private HederaEvmTransaction getManufacturedCallException( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermExecutionTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermExecutionTest.java index ba89fda4e64d..02aac454f35c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermExecutionTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermExecutionTest.java @@ -16,6 +16,7 @@ package com.hedera.services.bdd.suites.hip423; +import static com.hedera.services.bdd.junit.ContextRequirement.FEE_SCHEDULE_OVERRIDES; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; @@ -86,16 +87,17 @@ import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestLifecycle; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.junit.support.TestLifecycle; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.time.Instant; import java.util.Map; import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; @@ -117,6 +119,7 @@ public class ScheduleLongTermExecutionTest { private static final String FAILED_XFER = "failedXfer"; private static final String WEIRDLY_POPULAR_KEY_TXN = "weirdlyPopularKeyTxn"; private static final String PAYER_TXN = "payerTxn"; + private static final long PAYER_INITIAL_BALANCE = 1000000000000L; @BeforeAll static void beforeAll(@NonNull final TestLifecycle lifecycle) { @@ -558,19 +561,19 @@ public Stream executionWithDefaultPayerWorks() { })); } - @HapiTest + @LeakyHapiTest(requirement = FEE_SCHEDULE_OVERRIDES) @Order(5) - @Disabled - // future: currently contract transactions extract payer id from the trxId, and can't use a custom payer. - // the fix will be in following PR public Stream executionWithContractCallWorksAtExpiry() { + final var payerBalance = new AtomicLong(); return defaultHapiSpec("ExecutionWithContractCallWorksAtExpiry") .given( // upload fees for SCHEDULE_CREATE_CONTRACT_CALL uploadScheduledContractPrices(GENESIS), uploadInitCode(SIMPLE_UPDATE), contractCreate(SIMPLE_UPDATE).gas(500_000L), - cryptoCreate(PAYING_ACCOUNT).balance(1000000000000L).via(PAYING_ACCOUNT_TXN)) + cryptoCreate(PAYING_ACCOUNT) + .balance(PAYER_INITIAL_BALANCE) + .via(PAYING_ACCOUNT_TXN)) .when(scheduleCreate( BASIC_XFER, contractCall(SIMPLE_UPDATE, "set", BigInteger.valueOf(5), BigInteger.valueOf(42)) @@ -591,13 +594,19 @@ public Stream executionWithContractCallWorksAtExpiry() { .hasRecordedScheduledTxn(), sleepFor(5000), cryptoCreate("foo").via(TRIGGERING_TXN), + sleepFor(500), getScheduleInfo(BASIC_XFER).hasCostAnswerPrecheck(INVALID_SCHEDULE_ID), getAccountBalance(PAYING_ACCOUNT) - .hasTinyBars(spec -> - bal -> bal < 1000000000000L ? Optional.empty() : Optional.of("didnt change")), + .hasTinyBars(spec -> bal -> + bal < PAYER_INITIAL_BALANCE ? Optional.empty() : Optional.of("didnt change")) + .exposingBalanceTo(payerBalance::set), withOpContext((spec, opLog) -> { var triggeredTx = getTxnRecord(CREATE_TX).scheduled(); allRunFor(spec, triggeredTx); + final var txnFee = triggeredTx.getResponseRecord().getTransactionFee(); + // check if only designating payer was charged + Assertions.assertEquals(PAYER_INITIAL_BALANCE, txnFee + payerBalance.get()); + Assertions.assertEquals( SUCCESS, triggeredTx.getResponseRecord().getReceipt().getStatus(), @@ -614,15 +623,14 @@ public Stream executionWithContractCallWorksAtExpiry() { @HapiTest @Order(6) - @Disabled - // future: currently contract transactions extract payer id from the trxId, and can't use a custom payer. - // the fix will be in following PR public Stream executionWithContractCreateWorksAtExpiry() { + final var payerBalance = new AtomicLong(); return defaultHapiSpec("ExecutionWithContractCreateWorksAtExpiry") .given( - // overriding(SCHEDULING_WHITELIST, "ContractCreate"), uploadInitCode(SIMPLE_UPDATE), - cryptoCreate(PAYING_ACCOUNT).balance(1000000000000L).via(PAYING_ACCOUNT_TXN)) + cryptoCreate(PAYING_ACCOUNT) + .balance(PAYER_INITIAL_BALANCE) + .via(PAYING_ACCOUNT_TXN)) .when(scheduleCreate( BASIC_XFER, contractCreate(SIMPLE_UPDATE).gas(500_000L).adminKey(PAYING_ACCOUNT)) @@ -642,18 +650,18 @@ public Stream executionWithContractCreateWorksAtExpiry() { .hasRecordedScheduledTxn(), sleepFor(5000), cryptoCreate("foo").via(TRIGGERING_TXN), + sleepFor(2000), getScheduleInfo(BASIC_XFER).hasCostAnswerPrecheck(INVALID_SCHEDULE_ID), - // todo check white list here? - // overriding( - // SCHEDULING_WHITELIST, - // - // HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_WHITELIST)), getAccountBalance(PAYING_ACCOUNT) - .hasTinyBars(spec -> - bal -> bal < 1000000000000L ? Optional.empty() : Optional.of("didnt change")), + .hasTinyBars(spec -> bal -> + bal < PAYER_INITIAL_BALANCE ? Optional.empty() : Optional.of("didnt change")) + .exposingBalanceTo(payerBalance::set), withOpContext((spec, opLog) -> { var triggeredTx = getTxnRecord(CREATE_TX).scheduled(); allRunFor(spec, triggeredTx); + final var txnFee = triggeredTx.getResponseRecord().getTransactionFee(); + // check if only designating payer was charged + Assertions.assertEquals(PAYER_INITIAL_BALANCE, txnFee + payerBalance.get()); Assertions.assertEquals( SUCCESS,