Skip to content

Commit 94468ef

Browse files
chore: 12987 improve token and staking unit test coverage (#18531)
Signed-off-by: Josh Marinacci <[email protected]> Signed-off-by: Josh Marinacci <[email protected]>
1 parent 90b2368 commit 94468ef

File tree

6 files changed

+351
-2
lines changed

6 files changed

+351
-2
lines changed

hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/comparator/TokenComparators.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
public final class TokenComparators {
2020
private TokenComparators() {
21-
throw new IllegalStateException("Utility Class");
21+
throw new UnsupportedOperationException("Utility Class");
2222
}
2323

2424
/**

hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/comparator/TokenComparatorsTest.java

+82
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33

44
import static com.hedera.node.app.service.token.impl.comparator.TokenComparators.ACCOUNT_AMOUNT_COMPARATOR;
55
import static com.hedera.node.app.service.token.impl.comparator.TokenComparators.NFT_TRANSFER_COMPARATOR;
6+
import static com.hedera.node.app.service.token.impl.comparator.TokenComparators.PENDING_AIRDROP_ID_COMPARATOR;
67
import static com.hedera.node.app.service.token.impl.comparator.TokenComparators.TOKEN_ID_COMPARATOR;
78
import static com.hedera.node.app.service.token.impl.comparator.TokenComparators.TOKEN_TRANSFER_LIST_COMPARATOR;
89
import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount;
910
import static com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler.asToken;
11+
import static com.hedera.node.app.service.token.impl.test.util.CommonTestUtils.assertUnsupportedConstructor;
1012

1113
import com.hedera.hapi.node.base.AccountAmount;
1214
import com.hedera.hapi.node.base.AccountID;
15+
import com.hedera.hapi.node.base.NftID;
1316
import com.hedera.hapi.node.base.NftTransfer;
17+
import com.hedera.hapi.node.base.PendingAirdropId;
1418
import com.hedera.hapi.node.base.TokenID;
1519
import com.hedera.hapi.node.base.TokenTransferList;
20+
import com.hedera.node.app.service.token.impl.comparator.TokenComparators;
1621
import org.assertj.core.api.Assertions;
1722
import org.junit.jupiter.api.Nested;
1823
import org.junit.jupiter.api.Test;
@@ -22,6 +27,11 @@ class TokenComparatorsTest {
2227
private static final AccountID ACCOUNT_1234_ID = asAccount(0L, 0L, 1234);
2328
private static final AccountID ACCOUNT_9876_ID = asAccount(0L, 0L, 9876);
2429

30+
@Test
31+
void throwsInConstructor() {
32+
assertUnsupportedConstructor(TokenComparators.class);
33+
}
34+
2535
@Nested
2636
class AccountAmountComparatorTest {
2737
private static final AccountAmount ACC_AMOUNT_1234 =
@@ -146,4 +156,76 @@ void checkComparisons() {
146156
.isPositive();
147157
}
148158
}
159+
160+
@Nested
161+
class PendingAirdropComparatorTest {
162+
private static final PendingAirdropId airdrop1 = PendingAirdropId.newBuilder()
163+
.senderId(asAccount(0L, 0L, 1111))
164+
.receiverId(asAccount(0L, 0L, 2222))
165+
.fungibleTokenType(asToken(1111))
166+
.build();
167+
private static final PendingAirdropId airdrop2 = PendingAirdropId.newBuilder()
168+
.senderId(asAccount(0L, 0L, 1111))
169+
.receiverId(asAccount(0L, 0L, 2222))
170+
.fungibleTokenType(asToken(2222))
171+
.build();
172+
private static final PendingAirdropId airdrop_no_token = PendingAirdropId.newBuilder()
173+
.senderId(asAccount(0L, 0L, 1111))
174+
.receiverId(asAccount(0L, 0L, 2222))
175+
.build();
176+
private static final PendingAirdropId airdrop3 = PendingAirdropId.newBuilder()
177+
.senderId(asAccount(0L, 0L, 1111))
178+
.receiverId(asAccount(0L, 0L, 2222))
179+
.nonFungibleToken(NftID.newBuilder().tokenId(asToken(1111)).serialNumber(3333))
180+
.build();
181+
182+
@Test
183+
void nullChecks() {
184+
// null checks
185+
Assertions.assertThatThrownBy(() -> PENDING_AIRDROP_ID_COMPARATOR.compare(null, null))
186+
.isInstanceOf(NullPointerException.class);
187+
Assertions.assertThatThrownBy(() -> PENDING_AIRDROP_ID_COMPARATOR.compare(airdrop1, null))
188+
.isInstanceOf(NullPointerException.class);
189+
Assertions.assertThatThrownBy(() -> PENDING_AIRDROP_ID_COMPARATOR.compare(null, airdrop1))
190+
.isInstanceOf(NullPointerException.class);
191+
}
192+
193+
@Test
194+
void tokenVsUnset() {
195+
// token vs unset
196+
Assertions.assertThat(PENDING_AIRDROP_ID_COMPARATOR.compare(airdrop1, airdrop_no_token))
197+
.isPositive();
198+
}
199+
200+
@Test
201+
void fungibleTokens() {
202+
// compare by fungible token id
203+
Assertions.assertThat(PENDING_AIRDROP_ID_COMPARATOR.compare(airdrop1, airdrop1))
204+
.isZero();
205+
Assertions.assertThat(PENDING_AIRDROP_ID_COMPARATOR.compare(airdrop1, airdrop2))
206+
.isNegative();
207+
Assertions.assertThat(PENDING_AIRDROP_ID_COMPARATOR.compare(airdrop2, airdrop1))
208+
.isPositive();
209+
}
210+
211+
@Test
212+
void nonFungibleTokens() {
213+
// compare NFTs
214+
Assertions.assertThat(PENDING_AIRDROP_ID_COMPARATOR.compare(airdrop3, airdrop3))
215+
.isZero();
216+
Assertions.assertThatThrownBy(() -> PENDING_AIRDROP_ID_COMPARATOR.compare(null, airdrop3))
217+
.isInstanceOf(NullPointerException.class);
218+
Assertions.assertThatThrownBy(() -> PENDING_AIRDROP_ID_COMPARATOR.compare(airdrop3, null))
219+
.isInstanceOf(NullPointerException.class);
220+
}
221+
222+
@Test
223+
void NFTvsFT() {
224+
// NFTs rank higher than FTs
225+
Assertions.assertThat(PENDING_AIRDROP_ID_COMPARATOR.compare(airdrop1, airdrop3))
226+
.isNegative();
227+
Assertions.assertThat(PENDING_AIRDROP_ID_COMPARATOR.compare(airdrop3, airdrop2))
228+
.isPositive();
229+
}
230+
}
149231
}

hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/EndOfStakingPeriodUtilsTest.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
// SPDX-License-Identifier: Apache-2.0
22
package com.hedera.node.app.service.token.impl.test.handlers.staking;
33

4-
import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUtils.*;
4+
import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUtils.computeExtendedRewardSumHistory;
5+
import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUtils.computeNewStakes;
6+
import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUtils.readableNonZeroHistory;
57
import static com.hedera.node.app.service.token.impl.test.handlers.staking.StakeInfoHelperTest.DEFAULT_CONFIG;
8+
import static com.hedera.node.app.service.token.impl.test.util.CommonTestUtils.assertUnsupportedConstructor;
69

710
import com.hedera.hapi.node.state.token.StakingNodeInfo;
11+
import com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUtils;
812
import com.hedera.node.config.data.StakingConfig;
913
import java.math.BigInteger;
1014
import java.util.Collections;
@@ -32,6 +36,11 @@ class EndOfStakingPeriodUtilsTest {
3236
.build();
3337
private static final StakingConfig STAKING_CONFIG = DEFAULT_CONFIG.getConfigData(StakingConfig.class);
3438

39+
@Test
40+
void throwsInConstructor() {
41+
assertUnsupportedConstructor(EndOfStakingPeriodUtils.class);
42+
}
43+
3544
@Test
3645
void readableNonZeroHistoryFromEmptyRewards() {
3746
final var result = readableNonZeroHistory(Collections.emptyList());

hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakingRewardsHandlerImplTest.java

+143
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,149 @@ void stakingEffectsWorkAsExpectedWhenStakingToAccount() {
712712
assertThat(modifiedPayer.stakePeriodStart()).isEqualTo(stakePeriodStart);
713713
}
714714

715+
@Test
716+
void userSwitchesStakingFromAccountToNode() {
717+
// payer is staked to owner, has account balance of 55L, and no rewards
718+
// payer switches stake from owner to node
719+
// payer should get reward from the node
720+
// owner should get no reward
721+
final var accountBalance = 55L * HBARS_TO_TINYBARS;
722+
final var ownerBalance = 11L * HBARS_TO_TINYBARS;
723+
final var payerAccountBefore = new AccountCustomizer()
724+
.withAccount(account)
725+
.withBalance(accountBalance)
726+
.withStakeAtStartOfLastRewardPeriod(accountBalance / 5)
727+
.withStakedAccountId(ownerId)
728+
.withStakedToMe(0)
729+
.withStakePeriodStart(stakePeriodStart)
730+
.withDeclineReward(false)
731+
.withDeleted(false)
732+
.build();
733+
final var ownerAccountBefore = new AccountCustomizer()
734+
.withAccount(ownerAccount)
735+
.withBalance(ownerBalance)
736+
.withStakeAtStartOfLastRewardPeriod(-1L)
737+
.withStakePeriodStart(stakePeriodStart)
738+
.withDeclineReward(false)
739+
.withStakedToMe(0L)
740+
.withDeleted(false)
741+
.build();
742+
addToState(Map.of(payerId, payerAccountBefore, ownerId, ownerAccountBefore));
743+
744+
// transfer from payer to owner
745+
// change payer stake from owner account to node 1
746+
writableAccountStore.put(payerAccountBefore
747+
.copyBuilder()
748+
.tinybarBalance(accountBalance - HBARS_TO_TINYBARS)
749+
.stakedNodeId(node1Id.number())
750+
.build());
751+
writableAccountStore.put(ownerAccount
752+
.copyBuilder()
753+
.tinybarBalance(ownerBalance + HBARS_TO_TINYBARS)
754+
.build());
755+
756+
// run forward two periods
757+
final Instant nextDayInstant = originalInstant.plus(2, ChronoUnit.DAYS);
758+
given(context.consensusTime()).willReturn(nextDayInstant);
759+
stakePeriodManager.setCurrentStakePeriodFor(nextDayInstant);
760+
761+
mockEntityIdFactory();
762+
final var rewards = subject.applyStakingRewards(context, Collections.emptySet(), emptyMap());
763+
assertThat(rewards).hasSize(0);
764+
}
765+
766+
@Test
767+
void userSwitchesStakingFromNothingToAccount() {
768+
// payer is staked to owner, has account balance of 55L, and no rewards
769+
// payer switches stake from owner to node
770+
// payer should get no reward
771+
// owner should get no reward
772+
final var accountBalance = 55L * HBARS_TO_TINYBARS;
773+
final var ownerBalance = 11L * HBARS_TO_TINYBARS;
774+
final var payerAccountBefore = new AccountCustomizer()
775+
.withAccount(account)
776+
.withBalance(accountBalance)
777+
.withStakedNodeId(-1L)
778+
.withStakedToMe(0)
779+
.withStakePeriodStart(stakePeriodStart)
780+
.withDeclineReward(true)
781+
.build();
782+
addToState(Map.of(payerId, payerAccountBefore));
783+
784+
// transfer from payer to owner
785+
// change payer stake from owner account to node 1
786+
writableAccountStore.put(payerAccountBefore
787+
.copyBuilder()
788+
.tinybarBalance(accountBalance - HBARS_TO_TINYBARS)
789+
.stakedAccountId(ownerId)
790+
.build());
791+
writableAccountStore.put(ownerAccount
792+
.copyBuilder()
793+
.tinybarBalance(ownerBalance + HBARS_TO_TINYBARS)
794+
.build());
795+
796+
// run forward two periods
797+
Instant nextDayInstant = originalInstant.plus(2, ChronoUnit.DAYS);
798+
given(context.consensusTime()).willReturn(nextDayInstant);
799+
stakePeriodManager.setCurrentStakePeriodFor(nextDayInstant);
800+
801+
mockEntityIdFactory();
802+
final var rewards = subject.applyStakingRewards(context, Collections.emptySet(), emptyMap());
803+
// confirm no rewards
804+
assertThat(rewards).hasSize(0);
805+
}
806+
807+
@Test
808+
void userSwitchesStakingFromAccountToNothing() {
809+
// payer is staked to owner, has account balance of 55L, and no rewards
810+
// payer switches stake from owner to nothing (node -1)
811+
// payer should get no reward
812+
// owner should still get the reward from before the switch
813+
final var accountBalance = 55L * HBARS_TO_TINYBARS;
814+
final var ownerBalance = 11L * HBARS_TO_TINYBARS;
815+
final var payerAccountBefore = new AccountCustomizer()
816+
.withAccount(account)
817+
.withBalance(accountBalance)
818+
.withStakedAccountId(ownerId)
819+
.withStakedToMe(0)
820+
.withStakePeriodStart(stakePeriodStart)
821+
.withDeclineReward(true)
822+
.build();
823+
final var ownerAccountBefore = new AccountCustomizer()
824+
.withAccount(ownerAccount)
825+
.withBalance(ownerBalance)
826+
.withStakeAtStartOfLastRewardPeriod(-1L)
827+
.withStakePeriodStart(stakePeriodStart)
828+
.withDeclineReward(false)
829+
.withStakedNodeId(node1Id.number())
830+
.withStakedToMe(0L)
831+
.withDeleted(false)
832+
.build();
833+
addToState(Map.of(payerId, payerAccountBefore, ownerId, ownerAccountBefore));
834+
835+
// transfer from payer to owner
836+
// change payer stake from owner account to node -1
837+
writableAccountStore.put(payerAccountBefore
838+
.copyBuilder()
839+
.tinybarBalance(accountBalance - HBARS_TO_TINYBARS)
840+
.stakedNodeId(-1) // switch to staking to nothing
841+
.build());
842+
writableAccountStore.put(ownerAccount
843+
.copyBuilder()
844+
.tinybarBalance(ownerBalance + HBARS_TO_TINYBARS)
845+
.build());
846+
847+
// run forward two periods
848+
Instant nextDayInstant = originalInstant.plus(2, ChronoUnit.DAYS);
849+
given(context.consensusTime()).willReturn(nextDayInstant);
850+
stakePeriodManager.setCurrentStakePeriodFor(nextDayInstant);
851+
852+
mockEntityIdFactory();
853+
final var rewards = subject.applyStakingRewards(context, Collections.emptySet(), emptyMap());
854+
// check that owner still gets reward, but payer gets nothing
855+
assertThat(rewards).hasSize(1).containsEntry(ownerId, 2200L);
856+
}
857+
715858
@Test
716859
void rewardsUltimateBeneficiaryInsteadOfDeletedAccount() {
717860
final var accountBalance = 555L * HBARS_TO_TINYBARS;

0 commit comments

Comments
 (0)