Skip to content

Commit a8ec9d1

Browse files
fix: Fixes response code when failing in HbarChangesStep with custom fees (#12586)
Signed-off-by: Neeharika-Sompalli <[email protected]>
1 parent a69e274 commit a8ec9d1

File tree

2 files changed

+67
-1
lines changed

2 files changed

+67
-1
lines changed

hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/transfer/AdjustHbarChangesStep.java

+31
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,26 @@
1818

1919
import static com.hedera.hapi.node.base.ResponseCodeEnum.AMOUNT_EXCEEDS_ALLOWANCE;
2020
import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_ACCOUNT_BALANCE;
21+
import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE;
2122
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID;
2223
import static com.hedera.hapi.node.base.ResponseCodeEnum.SPENDER_DOES_NOT_HAVE_ALLOWANCE;
2324
import static com.hedera.node.app.service.token.impl.util.TokenHandlerHelper.getIfUsable;
2425
import static com.hedera.node.app.spi.workflows.HandleException.validateTrue;
26+
import static java.util.Collections.emptyList;
2527
import static java.util.Objects.requireNonNull;
2628

2729
import com.hedera.hapi.node.base.AccountID;
2830
import com.hedera.hapi.node.base.TransferList;
2931
import com.hedera.hapi.node.token.CryptoTransferTransactionBody;
32+
import com.hedera.hapi.node.transaction.AssessedCustomFee;
3033
import com.hedera.node.app.service.token.impl.WritableAccountStore;
3134
import com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler;
35+
import com.hedera.node.app.spi.workflows.HandleException;
3236
import edu.umd.cs.findbugs.annotations.NonNull;
3337
import java.util.ArrayList;
3438
import java.util.Collections;
3539
import java.util.LinkedHashMap;
40+
import java.util.List;
3641
import java.util.Map;
3742

3843
public class AdjustHbarChangesStep extends BaseTokenHandler implements TransferStep {
@@ -133,9 +138,35 @@ private void modifyAggregatedTransfers(
133138
accountId, accountStore, transferContext.getHandleContext().expiryValidator(), INVALID_ACCOUNT_ID);
134139
final var currentBalance = account.tinybarBalance();
135140
final var newBalance = currentBalance + amount;
141+
if (newBalance < 0) {
142+
final var assessedCustomFees = transferContext.getAssessedCustomFees();
143+
// Whenever mono-service assessed a fixed fee to an account, it would
144+
// update the "metadata" of that pending balance change to use
145+
// INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE instead of
146+
// INSUFFICIENT_ACCOUNT_BALANCE in the case of an insufficient balance.
147+
// We don't have an equivalent place to store such "metadata" in the
148+
// mod-service implementation; so instead if INSUFFICIENT_ACCOUNT_BALANCE
149+
// happens, we check if there were any custom fee payments that could
150+
// have contributed to the insufficient balance, and translate the
151+
// error to INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE if so.
152+
if (effectivePaymentWasMade(accountId, assessedCustomFees)) {
153+
throw new HandleException(INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE);
154+
}
155+
}
136156
validateTrue(newBalance >= 0, INSUFFICIENT_ACCOUNT_BALANCE);
137157
final var copy = account.copyBuilder();
138158
accountStore.put(copy.tinybarBalance(newBalance).build());
139159
}
140160
}
161+
162+
private boolean effectivePaymentWasMade(
163+
@NonNull final AccountID payer, @NonNull final List<AssessedCustomFee> assessedCustomFees) {
164+
for (final var fee : assessedCustomFees) {
165+
if (fee.tokenId() == null
166+
&& fee.effectivePayerAccountIdOrElse(emptyList()).contains(payer)) {
167+
return true;
168+
}
169+
}
170+
return false;
171+
}
141172
}

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoTransferSuite.java

+36-1
Original file line numberDiff line numberDiff line change
@@ -248,14 +248,49 @@ public List<HapiSpec> getSpecsInSuite() {
248248
canUseAliasAndAccountCombinations(),
249249
testTransferToSystemAccounts(),
250250
testTransferToSystemAccountsAndCheckSenderBalance(),
251-
transferInvalidTokenIdWithDecimals());
251+
transferInvalidTokenIdWithDecimals(),
252+
insufficientBalanceForCustomFeeFails());
252253
}
253254

254255
@Override
255256
public boolean canRunConcurrent() {
256257
return true;
257258
}
258259

260+
@HapiTest
261+
public HapiSpec insufficientBalanceForCustomFeeFails() {
262+
final var operatorKey = "operatorKey";
263+
final var accountId1Key = "accountId1Key";
264+
final var accountId2Key = "accountId2Key";
265+
final var operator = "operator";
266+
final var accountId1 = "accountId1";
267+
final var accountId2 = "accountId2";
268+
final var tokenId = "tokenId";
269+
return defaultHapiSpec("insufficientBalanceForFee", FULLY_NONDETERMINISTIC)
270+
.given(
271+
newKeyNamed(operatorKey),
272+
newKeyNamed(accountId1Key),
273+
newKeyNamed(accountId2Key),
274+
cryptoCreate(accountId1).balance(2 * ONE_HBAR).key(accountId1Key),
275+
cryptoCreate(accountId2).balance(2 * ONE_HBAR).key(accountId2Key),
276+
cryptoCreate(operator).balance(0L).key(operatorKey),
277+
tokenCreate(tokenId)
278+
.name("ffff")
279+
.treasury(operator)
280+
.adminKey(operatorKey)
281+
.feeScheduleKey(operatorKey)
282+
.symbol("F")
283+
.initialSupply(1)
284+
.withCustom(fixedHbarFee(5000_000_000L, "operator"))
285+
.initialSupply(1)
286+
.decimals(0),
287+
tokenAssociate(accountId1, tokenId),
288+
tokenAssociate(accountId2, tokenId))
289+
.when(cryptoTransfer(moving(1L, tokenId).between(operator, accountId1)))
290+
.then(cryptoTransfer(moving(1L, tokenId).between(accountId1, accountId2))
291+
.hasKnownStatus(INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE));
292+
}
293+
259294
@HapiTest
260295
final HapiSpec okToRepeatSerialNumbersInWipeList() {
261296
final var ownerWith4AutoAssoc = "ownerWith4AutoAssoc";

0 commit comments

Comments
 (0)