Repository: hashgraph/hedera-services
Issue: Fees - FieldSourcedFeeScreening can throw null exception in some scenarios

Created by [261](https://github.com/swirlds/hedera-fpcomplete-audit/pull/261)

## Situation

We have a method named
[canParticipantAfford](https://github.com/hashgraph/hedera-services/blob/e4f825ce23d5e4429192c8bf218ea220064d548e/hedera-node/src/main/java/com/hedera/services/fees/charging/FieldSourcedFeeScreening.java#L84
"canParticipantAfford") in the class `FieldSourcedFeeScreening`
defined like this:

``` java
@Override
public boolean canParticipantAfford(AccountID participant, EnumSet<TxnFeeType> fees) {
    long exemptAmount = 0;
    if (fees.contains(THRESHOLD_RECORD)) {
        exemptAmount += exemptions.isExemptFromRecordFees(participant) ? feeAmounts.get(THRESHOLD_RECORD) : 0;
    } // what if feeAmounts.get doesn't have threshold_record
    long netAmount = totalAmountOf(fees) - exemptAmount;
    return check.canAfford(participant, netAmount);
}
```

The problem here is that the above code can throw a null exception if
the `feeAmounts` doesn't have the key `THRESHOLD_RECORD` in it. We
have a small test to reproduce the above behavior. You can add the
following method in the test file
[FieldSourcedFeeScreeningTest.java](https://github.com/hashgraph/hedera-services/blob/e4f825ce23d5e4429192c8bf218ea220064d548e/hedera-node/src/test/java/com/hedera/services/fees/charging/FieldSourcedFeeScreeningTest.java#L103
"FieldSourcedFeeScreeningTest.java"):

``` java
@Test
public void feeTest() {
    // setup:
    EnumSet<TxnFeeType> thresholdRecordFee = EnumSet.of(THRESHOLD_RECORD);
    subject.setFor(NETWORK, network);
    subject.setFor(SERVICE, service);
    subject.setFor(NODE, node);
    subject.setFor(CACHE_RECORD, cacheRecord);
    // when:
    boolean viability = subject.canParticipantAfford(master, thresholdRecordFee);
    // then:
    assertFalse(viability);
}
```

And when the test is executed, it fails with null exception.

## Problem

When the `fees` contains `THRESHOLD_RECORD` and the `feeAmounts`
doesn't contain `THRESHOLD_RECORD` as its key, in some conditions it
can lead to a null exception.

Also, we could see the same pattern at various other locations where it
might be an issue:
* In [itemizedFess method](https://github.com/hashgraph/hedera-services/blob/e4f825ce23d5e4429192c8bf218ea220064d548e/hedera-node/src/main/java/com/hedera/services/fees/charging/ItemizableFeeCharging.java#L148 "itemizedFess method")
* In [totalAmountOf method](https://github.com/hashgraph/hedera-services/blob/e4f825ce23d5e4429192c8bf218ea220064d548e/hedera-node/src/main/java/com/hedera/services/fees/charging/FieldSourcedFeeScreening.java#L93 "totalAmountOf method")

## Suggestions

There are multiple suggestions based on the situation:

* Document the pre-condition of the call explicitly so that the caller
  is aware of it.
* If the pre-condition doesn't exist, check for null explicitly and
  throw a new custom exception or modify the business logic
  accordingly.


Changed files:
hedera-node/src/main/java/com/hedera/services/fees/charging/FieldSourcedFeeScreening.java
hedera-node/src/test/java/com/hedera/services/fees/charging/FieldSourcedFeeScreeningTest.java

diff --git a/hedera-node/src/main/java/com/hedera/services/fees/charging/FieldSourcedFeeScreening.java b/hedera-node/src/main/java/com/hedera/services/fees/charging/FieldSourcedFeeScreening.java
index f68c753f11..8fe576492a 100644
--- a/hedera-node/src/main/java/com/hedera/services/fees/charging/FieldSourcedFeeScreening.java
+++ b/hedera-node/src/main/java/com/hedera/services/fees/charging/FieldSourcedFeeScreening.java
@@ -83,14 +83,18 @@ public class FieldSourcedFeeScreening implements TxnScopedFeeScreening {
 	@Override
 	public boolean canParticipantAfford(AccountID participant, EnumSet<TxnFeeType> fees) {
 		long exemptAmount = 0;
-		if (fees.contains(THRESHOLD_RECORD)) {
+		if (fees.contains(THRESHOLD_RECORD) && feeAmounts.containsKey(THRESHOLD_RECORD)) {
 			exemptAmount += exemptions.isExemptFromRecordFees(participant) ? feeAmounts.get(THRESHOLD_RECORD) : 0;
 		}
-		long netAmount = totalAmountOf(fees) - exemptAmount;
+
+		final long netAmount = totalAmountOf(fees) - exemptAmount;
 		return check.canAfford(participant, netAmount);
 	}
 
 	protected long totalAmountOf(EnumSet<TxnFeeType> fees) {
-		return fees.stream().mapToLong(feeAmounts::get).sum();
+		return fees.stream()
+				.filter(fee -> feeAmounts.containsKey(fee))
+				.mapToLong(feeAmounts::get)
+				.sum();
 	}
 }
diff --git a/hedera-node/src/test/java/com/hedera/services/fees/charging/FieldSourcedFeeScreeningTest.java b/hedera-node/src/test/java/com/hedera/services/fees/charging/FieldSourcedFeeScreeningTest.java
index 269595a597..f965d64b7e 100644
--- a/hedera-node/src/test/java/com/hedera/services/fees/charging/FieldSourcedFeeScreeningTest.java
+++ b/hedera-node/src/test/java/com/hedera/services/fees/charging/FieldSourcedFeeScreeningTest.java
@@ -41,8 +41,14 @@ import static com.hedera.services.fees.TxnFeeType.*;
 
 @RunWith(JUnitPlatform.class)
 class FieldSourcedFeeScreeningTest {
-	long willingness = 1_000L;
-	long network = 500L, service = 200L, node = 100L, stateRecord = 150L, cacheRecord = 50L;
+
+	final long willingness = 1_000L;
+	final long network = 500L;
+	final long service = 200L;
+	final long node = 100L;
+	final long stateRecord = 150L;
+	final long cacheRecord = 50L;
+
 	AccountID payer = IdUtils.asAccount("0.0.1001");
 	AccountID master = IdUtils.asAccount("0.0.50");
 	AccountID participant = IdUtils.asAccount("0.0.2002");
@@ -183,6 +189,39 @@ class FieldSourcedFeeScreeningTest {
 		verify(check).canAfford(payer, willingness);
 	}
 
+	@Test
+	public void canParticipantAffordTest() {
+		// setup:
+		EnumSet<TxnFeeType> thresholdRecordFee = EnumSet.of(THRESHOLD_RECORD);
+		subject.setFor(NETWORK, network);
+		subject.setFor(SERVICE, service);
+		subject.setFor(NODE, node);
+		subject.setFor(CACHE_RECORD, cacheRecord);
+		// when:
+		boolean viability = subject.canParticipantAfford(master, thresholdRecordFee);
+		// then:
+		assertFalse(viability);
+	}
+
+	@Test
+	public void participantCantAffordTest() {
+		// setup:
+		final BalanceCheck check = (payer, amount) -> amount >= stateRecord;
+
+		final EnumSet<TxnFeeType> thresholdRecordFee = EnumSet.of(THRESHOLD_RECORD);
+		subject.setFor(NETWORK, network);
+		subject.setFor(SERVICE, service);
+		subject.setFor(NODE, node);
+		subject.setFor(CACHE_RECORD, cacheRecord);
+		subject.setFor(THRESHOLD_RECORD, stateRecord);
+		subject.setBalanceCheck(check);
+
+		// when:
+		boolean viability = subject.canParticipantAfford(master, thresholdRecordFee);
+		// then:
+		assertFalse(viability);
+	}
+
 	private void givenKnownFeeAmounts() {
 		subject.setFor(NETWORK, network);
 		subject.setFor(SERVICE, service);