From 066b8b91afb4ebedd50e770e0a25370ad1b4f468 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Fri, 11 Aug 2023 15:13:39 +0700 Subject: [PATCH] Support case with bag list score being greater then latest bag (#1060) --- .../nova/common/utils/KotlinExt.kt | 4 ---- .../nova/common/utils/SemiUnboundedRange.kt | 18 ++++++++++++++++++ .../domain/StakingInteractorExt.kt | 6 ++++-- .../domain/bagList/BagListLocator.kt | 14 ++------------ .../domain/bagList/BagScoreBoundaries.kt | 6 ++++++ .../domain/bagList/rebag/RebagMovement.kt | 3 ++- .../presentation/formatters/Formatters.kt | 19 +++++++++++++++---- 7 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 common/src/main/java/io/novafoundation/nova/common/utils/SemiUnboundedRange.kt create mode 100644 feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/BagScoreBoundaries.kt diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt index f1f9087b2a..1d8119128f 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt @@ -71,10 +71,6 @@ val BigDecimal.isNonNegative: Boolean val BigInteger.isZero: Boolean get() = signum() == 0 -inline fun , R : Comparable> ClosedRange.map(mapper: (T) -> R): ClosedRange { - return mapper(start)..mapper(endInclusive) -} - fun BigInteger?.orZero(): BigInteger = this ?: BigInteger.ZERO fun BigDecimal?.orZero(): BigDecimal = this ?: 0.toBigDecimal() diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/SemiUnboundedRange.kt b/common/src/main/java/io/novafoundation/nova/common/utils/SemiUnboundedRange.kt new file mode 100644 index 0000000000..d71a048302 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/SemiUnboundedRange.kt @@ -0,0 +1,18 @@ +package io.novafoundation.nova.common.utils + +interface SemiUnboundedRange> { + + val start: T + + val endInclusive: T? +} + +infix operator fun > T.rangeTo(another: T?): SemiUnboundedRange { + return ComparableSemiUnboundedRange(this, another) +} + +inline fun , R : Comparable> SemiUnboundedRange.map(mapper: (T) -> R): SemiUnboundedRange { + return mapper(start)..endInclusive?.let(mapper) +} + +class ComparableSemiUnboundedRange>(override val start: T, override val endInclusive: T?) : SemiUnboundedRange diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractorExt.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractorExt.kt index 9837835039..56fe63f422 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractorExt.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractorExt.kt @@ -86,8 +86,10 @@ fun minimumStake( val lastElectedBag = bagListLocator.bagBoundaries(bagListScoreConverter.scoreOf(minElectedStake)) - val nextBagThreshold = bagListScoreConverter.balanceOf(lastElectedBag.endInclusive) + val nextBagThreshold = bagListScoreConverter.balanceOf(lastElectedBag.endInclusive ?: lastElectedBag.start) val epsilon = Balance.ONE - return nextBagThreshold + epsilon + val nextBagRequiredAmount = nextBagThreshold + epsilon + + return nextBagRequiredAmount.coerceAtLeast(minElectedStake) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/BagListLocator.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/BagListLocator.kt index 189ca295b4..76cc783602 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/BagListLocator.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/BagListLocator.kt @@ -1,16 +1,13 @@ package io.novafoundation.nova.feature_staking_impl.domain.bagList +import io.novafoundation.nova.common.utils.rangeTo import io.novafoundation.nova.feature_staking_impl.domain.model.BagListNode.Score interface BagListLocator { fun bagBoundaries(userScore: Score): BagScoreBoundaries - - fun nextBagBoundaries(previous: BagScoreBoundaries): BagScoreBoundaries } -typealias BagScoreBoundaries = ClosedRange - fun BagListLocator(thresholds: List): BagListLocator = RealBagListLocator(thresholds) private class RealBagListLocator(private val thresholds: List) : BagListLocator { @@ -21,15 +18,8 @@ private class RealBagListLocator(private val thresholds: List) : BagListL return bagBoundariesAt(bagIndex) } - override fun nextBagBoundaries(previous: BagScoreBoundaries): BagScoreBoundaries { - val previousBagIndex = notionalBagIndexFor(previous.endInclusive) - val nextBagIndex = (previousBagIndex + 1).coerceAtMost(thresholds.size - 1) - - return bagBoundariesAt(nextBagIndex) - } - private fun bagBoundariesAt(index: Int): BagScoreBoundaries { - val bagUpper = thresholds[index] + val bagUpper = thresholds.getOrNull(index) val bagLower = if (index > 0) thresholds[index - 1] else Score.zero() return bagLower..bagUpper diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/BagScoreBoundaries.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/BagScoreBoundaries.kt new file mode 100644 index 0000000000..7e578e43ec --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/BagScoreBoundaries.kt @@ -0,0 +1,6 @@ +package io.novafoundation.nova.feature_staking_impl.domain.bagList + +import io.novafoundation.nova.common.utils.SemiUnboundedRange +import io.novafoundation.nova.feature_staking_impl.domain.model.BagListNode.Score + +typealias BagScoreBoundaries = SemiUnboundedRange diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/RebagMovement.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/RebagMovement.kt index c2bd074a64..99e8ff71df 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/RebagMovement.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/RebagMovement.kt @@ -1,8 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.domain.bagList.rebag +import io.novafoundation.nova.common.utils.SemiUnboundedRange import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance -typealias BagAmountBoundaries = ClosedRange +typealias BagAmountBoundaries = SemiUnboundedRange class RebagMovement( val from: BagAmountBoundaries, diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/formatters/Formatters.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/formatters/Formatters.kt index 9612d7d767..405125c77b 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/formatters/Formatters.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/formatters/Formatters.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_api.presentation.formatters +import io.novafoundation.nova.common.utils.SemiUnboundedRange import io.novafoundation.nova.common.utils.formatting.format import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks @@ -12,11 +13,17 @@ fun BigInteger.formatPlanks(chainAsset: Chain.Asset): String { return chainAsset.amountFromPlanks(this).formatTokenAmount(chainAsset) } -fun ClosedRange.formatPlanksRange(chainAsset: Chain.Asset): String { +fun SemiUnboundedRange.formatPlanksRange(chainAsset: Chain.Asset): String { + val end = endInclusive val startFormatted = chainAsset.amountFromPlanks(start).format() - val endFormatted = endInclusive.formatPlanks(chainAsset) - return "$startFormatted — $endFormatted" + return if (end != null) { + val endFormatted = end.formatPlanks(chainAsset) + + "$startFormatted — $endFormatted" + } else { + "$startFormatted+".withTokenSymbol(chainAsset.symbol) + } } fun BigDecimal.formatTokenAmount(chainAsset: Chain.Asset, roundingMode: RoundingMode = RoundingMode.FLOOR): String { @@ -24,7 +31,11 @@ fun BigDecimal.formatTokenAmount(chainAsset: Chain.Asset, roundingMode: Rounding } fun BigDecimal.formatTokenAmount(tokenSymbol: String, roundingMode: RoundingMode = RoundingMode.FLOOR): String { - return "${format(roundingMode)} $tokenSymbol" + return format(roundingMode).withTokenSymbol(tokenSymbol) +} + +fun String.withTokenSymbol(tokenSymbol: String): String { + return "$this $tokenSymbol" } fun BigDecimal.formatTokenChange(chainAsset: Chain.Asset, isIncome: Boolean): String {