Skip to content

Commit

Permalink
Update inflation calculation for Polkadot
Browse files Browse the repository at this point in the history
  • Loading branch information
valentunn committed Nov 19, 2024
1 parent 9f30cc3 commit 5b03c56
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 29 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ buildscript {

web3jVersion = '4.9.5'

substrateSdkVersion = '2.2.0'
substrateSdkVersion = '2.3.0'

gifVersion = '1.2.19'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ internal class HydrationConversionFeePayment(
) : FeePayment {

override suspend fun modifyExtrinsic(extrinsicBuilder: ExtrinsicBuilder) {
val baseCall = extrinsicBuilder.getCall()
val baseCalls = extrinsicBuilder.getCalls()
extrinsicBuilder.resetCalls()

extrinsicBuilder.setFeeCurrency(hydraDxAssetIdConverter.toOnChainIdOrThrow(paymentAsset))
extrinsicBuilder.call(baseCall)
extrinsicBuilder.calls(baseCalls)
extrinsicBuilder.setFeeCurrency(hydraDxAssetIdConverter.systemAssetId)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_staking_api.domain.api
import io.novafoundation.nova.feature_account_api.data.model.AccountIdMap
import io.novafoundation.nova.feature_staking_api.domain.model.EraIndex
import io.novafoundation.nova.feature_staking_api.domain.model.Exposure
import io.novafoundation.nova.feature_staking_api.domain.model.InflationPredictionInfo
import io.novafoundation.nova.feature_staking_api.domain.model.RewardDestination
import io.novafoundation.nova.feature_staking_api.domain.model.SlashingSpans
import io.novafoundation.nova.feature_staking_api.domain.model.StakingLedger
Expand Down Expand Up @@ -58,6 +59,8 @@ interface StakingRepository {
suspend fun maxNominators(chainId: ChainId): BigInteger?

suspend fun nominatorsCount(chainId: ChainId): BigInteger?

suspend fun getInflationPredictionInfo(chainId: ChainId): InflationPredictionInfo
}

suspend fun StakingRepository.historicalEras(chainId: ChainId): List<BigInteger> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.novafoundation.nova.feature_staking_api.domain.model

import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import io.novafoundation.nova.common.data.network.runtime.binding.castToList
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
import io.novafoundation.nova.common.utils.divideToDecimal
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days

class InflationPredictionInfo(
val nextMint: NextMint
) {

class NextMint(
val toStakers: Balance,
val toTreasury: Balance
)

companion object {

fun fromDecoded(decoded: Any?): InflationPredictionInfo {
val asStruct = decoded.castToStruct()

return InflationPredictionInfo(
nextMint = bindNextMint(asStruct["nextMint"])
)
}

private fun bindNextMint(decoded: Any?): NextMint {
val (toStakersRaw, toTreasuryRaw) = decoded.castToList()

return NextMint(
toStakers = bindNumber(toStakersRaw),
toTreasury = bindNumber(toTreasuryRaw)
)
}
}
}

fun InflationPredictionInfo.calculateStakersInflation(totalIssuance: Balance, eraDuration: Duration): Double {
val periodsInYear = 365.days / eraDuration
val inflationPerMint = nextMint.toStakers.divideToDecimal(totalIssuance)

return inflationPerMint.toDouble() * periodsInYear
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import io.novafoundation.nova.feature_staking_api.domain.model.Exposure
import io.novafoundation.nova.feature_staking_api.domain.model.ExposureOverview
import io.novafoundation.nova.feature_staking_api.domain.model.ExposurePage
import io.novafoundation.nova.feature_staking_api.domain.model.IndividualExposure
import io.novafoundation.nova.feature_staking_api.domain.model.InflationPredictionInfo
import io.novafoundation.nova.feature_staking_api.domain.model.Nominations
import io.novafoundation.nova.feature_staking_api.domain.model.SlashingSpans
import io.novafoundation.nova.feature_staking_api.domain.model.StakingLedger
Expand Down Expand Up @@ -43,6 +44,7 @@ import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.update
import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.activeEraStorageKey
import io.novafoundation.nova.feature_staking_impl.data.repository.datasource.StakingStoriesDataSource
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletConstants
import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
Expand Down Expand Up @@ -75,6 +77,7 @@ class StakingRepositoryImpl(
private val chainRegistry: ChainRegistry,
private val stakingStoriesDataSource: StakingStoriesDataSource,
private val storageCache: StorageCache,
private val multiChainRuntimeCallsApi: MultiChainRuntimeCallsApi,
) : StakingRepository {

override suspend fun eraStartSessionIndex(chainId: ChainId, currentEra: BigInteger): EraIndex {
Expand Down Expand Up @@ -266,6 +269,17 @@ class StakingRepositoryImpl(
chainId = chainId
)

override suspend fun getInflationPredictionInfo(chainId: ChainId): InflationPredictionInfo {
val callApi = multiChainRuntimeCallsApi.forChain(chainId)

return callApi.call(
section = "Inflation",
method = "experimental_inflation_prediction_info",
arguments = emptyMap(),
returnBinding = InflationPredictionInfo::fromDecoded
)
}

private suspend fun <T> queryStorageIfExists(
chainId: ChainId,
storageName: String,
Expand Down Expand Up @@ -318,6 +332,7 @@ class StakingRepositoryImpl(
stashId,
prefs
)

nominations != null -> StakingState.Stash.Nominator(
chain,
chainAsset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ import io.novafoundation.nova.feature_staking_impl.data.repository.datasource.re
import io.novafoundation.nova.feature_staking_impl.data.repository.datasource.reward.PoolStakingRewardsDataSource
import io.novafoundation.nova.feature_staking_impl.data.repository.datasource.reward.RealStakingRewardsDataSourceRegistry
import io.novafoundation.nova.feature_staking_impl.data.repository.datasource.reward.StakingRewardsDataSourceRegistry
import io.novafoundation.nova.feature_staking_impl.data.validators.ValidatorsPreferencesSource
import io.novafoundation.nova.feature_staking_impl.data.validators.NovaValidatorsApi
import io.novafoundation.nova.feature_staking_impl.data.validators.RemoteValidatorsPreferencesSource
import io.novafoundation.nova.feature_staking_impl.data.validators.ValidatorsPreferencesSource
import io.novafoundation.nova.feature_staking_impl.di.staking.DefaultBulkRetriever
import io.novafoundation.nova.feature_staking_impl.di.staking.PayoutsBulkRetriever
import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor
Expand Down Expand Up @@ -177,15 +177,17 @@ class StakingFeatureModule {
stakingStoriesDataSource: StakingStoriesDataSource,
walletConstants: WalletConstants,
chainRegistry: ChainRegistry,
storageCache: StorageCache
storageCache: StorageCache,
multiChainRuntimeCallsApi: MultiChainRuntimeCallsApi
): StakingRepository = StakingRepositoryImpl(
accountStakingDao = accountStakingDao,
remoteStorage = remoteStorageSource,
localStorage = localStorageSource,
stakingStoriesDataSource = stakingStoriesDataSource,
walletConstants = walletConstants,
chainRegistry = chainRegistry,
storageCache = storageCache
storageCache = storageCache,
multiChainRuntimeCallsApi = multiChainRuntimeCallsApi
)

@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.novafoundation.nova.feature_staking_impl.domain.rewards

import io.novafoundation.nova.feature_staking_api.domain.model.InflationPredictionInfo
import io.novafoundation.nova.feature_staking_api.domain.model.calculateStakersInflation
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import kotlin.time.Duration

class InflationPredictionInfoCalculator(
private val inflationPredictionInfo: InflationPredictionInfo,
private val eraDuration: Duration,
private val totalIssuance: Balance,
validators: List<RewardCalculationTarget>
) : InflationBasedRewardCalculator(validators, totalIssuance) {

override fun calculateYearlyInflation(stakedPortion: Double): Double {
return inflationPredictionInfo.calculateStakersInflation(totalIssuance, eraDuration)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import io.novafoundation.nova.feature_staking_impl.data.stakingType
import io.novafoundation.nova.feature_staking_impl.data.unwrapNominationPools
import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation
import io.novafoundation.nova.feature_staking_impl.domain.common.electedExposuresInActiveEra
import io.novafoundation.nova.feature_staking_impl.domain.common.eraTimeCalculator
import io.novafoundation.nova.feature_staking_impl.domain.error.accountIdNotFound
import io.novafoundation.nova.runtime.ext.Geneses
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
Expand Down Expand Up @@ -42,7 +43,8 @@ class RewardCalculatorFactory(
suspend fun create(
stakingOption: StakingOption,
exposures: AccountIdMap<Exposure>,
validatorsPrefs: AccountIdMap<ValidatorPrefs?>
validatorsPrefs: AccountIdMap<ValidatorPrefs?>,
scope: CoroutineScope
): RewardCalculator = withContext(Dispatchers.Default) {
val totalIssuance = totalIssuanceRepository.getTotalIssuance(stakingOption.assetWithChain.chain.id)

Expand All @@ -57,7 +59,7 @@ class RewardCalculatorFactory(
)
}

stakingOption.createRewardCalculator(validators, totalIssuance)
stakingOption.createRewardCalculator(validators, totalIssuance, scope)
}

suspend fun create(stakingOption: StakingOption, scope: CoroutineScope): RewardCalculator = withContext(Dispatchers.Default) {
Expand All @@ -66,13 +68,17 @@ class RewardCalculatorFactory(
val exposures = shareStakingSharedComputation.get().electedExposuresInActiveEra(chainId, scope)
val validatorsPrefs = stakingRepository.getValidatorPrefs(chainId, exposures.keys)

create(stakingOption, exposures, validatorsPrefs)
create(stakingOption, exposures, validatorsPrefs, scope)
}

private suspend fun StakingOption.createRewardCalculator(validators: List<RewardCalculationTarget>, totalIssuance: BigInteger): RewardCalculator {
private suspend fun StakingOption.createRewardCalculator(
validators: List<RewardCalculationTarget>,
totalIssuance: BigInteger,
scope: CoroutineScope
): RewardCalculator {
return when (unwrapNominationPools().stakingType) {
RELAYCHAIN, RELAYCHAIN_AURA -> {
val custom = customRelayChainCalculator(validators, totalIssuance)
val custom = customRelayChainCalculator(validators, totalIssuance, scope)
if (custom != null) return custom

val activePublicParachains = parasRepository.activePublicParachains(assetWithChain.chain.id)
Expand All @@ -88,10 +94,12 @@ class RewardCalculatorFactory(

private suspend fun StakingOption.customRelayChainCalculator(
validators: List<RewardCalculationTarget>,
totalIssuance: BigInteger
totalIssuance: BigInteger,
scope: CoroutineScope
): RewardCalculator? {
return when (chain.id) {
Chain.Geneses.VARA -> Vara(chain.id, validators, totalIssuance)
Chain.Geneses.POLKADOT -> PolkadotInflationPrediction(validators, totalIssuance, scope)
else -> null
}
}
Expand Down Expand Up @@ -119,4 +127,28 @@ class RewardCalculatorFactory(
}
.getOrNull()
}

private suspend fun StakingOption.PolkadotInflationPrediction(
validators: List<RewardCalculationTarget>,
totalIssuance: BigInteger,
scope: CoroutineScope
): RewardCalculator? {
return runCatching {
val eraRewardCalculator = shareStakingSharedComputation.get().eraTimeCalculator(this, scope)
val eraDuration = eraRewardCalculator.eraDuration()

val inflationPredictionInfo = stakingRepository.getInflationPredictionInfo(chain.id)

InflationPredictionInfoCalculator(
inflationPredictionInfo = inflationPredictionInfo,
eraDuration = eraDuration,
totalIssuance = totalIssuance,
validators = validators
)
}
.onFailure {
Log.e("RewardCalculatorFactory", "Failed to create Polkadot Inflation Prediction reward calculator, fallbacking to default", it)
}
.getOrNull()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class ValidatorProvider(
val identities = identityRepository.getIdentitiesFromIdsHex(chainId, requestedValidatorIds)
val slashes = stakingRepository.getSlashes(chainId, requestedValidatorIds)

val rewardCalculator = rewardCalculatorFactory.create(stakingOption, electedValidatorExposures, validatorPrefs)
val rewardCalculator = rewardCalculatorFactory.create(stakingOption, electedValidatorExposures, validatorPrefs, scope)
val maxNominators = stakingConstantsRepository.maxRewardedNominatorPerValidator(chainId)

return requestedValidatorIds.map { accountIdHex ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package io.novafoundation.nova.runtime.call

import io.novafoundation.nova.common.data.network.runtime.binding.fromHexOrIncompatible
import io.novafoundation.nova.runtime.network.rpc.StateCallRequest
import io.novafoundation.nova.runtime.network.rpc.stateCall
import io.novasama.substrate_sdk_android.extensions.requireHexPrefix
import io.novasama.substrate_sdk_android.extensions.toHexString
import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot
import io.novasama.substrate_sdk_android.runtime.definitions.registry.TypeRegistry
import io.novasama.substrate_sdk_android.runtime.definitions.registry.getOrThrow
import io.novasama.substrate_sdk_android.runtime.definitions.types.bytes
import io.novasama.substrate_sdk_android.runtime.metadata.createRequest
import io.novasama.substrate_sdk_android.runtime.metadata.decodeOutput
import io.novasama.substrate_sdk_android.runtime.metadata.method
import io.novasama.substrate_sdk_android.runtime.metadata.runtimeApi
import io.novasama.substrate_sdk_android.wsrpc.SocketService
import io.novasama.substrate_sdk_android.wsrpc.request.runtime.state.StateCallRequest

typealias RuntimeTypeName = String
typealias RuntimeTypeValue = Any?
Expand All @@ -18,13 +22,13 @@ interface RuntimeCallsApi {

val runtime: RuntimeSnapshot

// TODO we can do better than that - it is possible to auto-detect method signature's types
// However it requires a separate research
// We should revisit this when Metadata v15 will take place
/**
* @param arguments - list of pairs [runtimeTypeValue, runtimeTypeName],
* where runtimeTypeValue is value to be encoded and runtimeTypeName is type name that can be found in [TypeRegistry]
* It can also be null, in that case argument is considered as already encoded in hex form
*
* This should only be used if automatic decoding via metadata is not possible
* For the other cases use another [call] overload
*/
suspend fun <R> call(
section: String,
Expand All @@ -33,6 +37,13 @@ interface RuntimeCallsApi {
returnType: RuntimeTypeName,
returnBinding: (Any?) -> R
): R

suspend fun <R> call(
section: String,
method: String,
arguments: Map<String, Any?>,
returnBinding: (Any?) -> R
): R
}

internal class RealRuntimeCallsApi(
Expand All @@ -58,6 +69,22 @@ internal class RealRuntimeCallsApi(
return returnBinding(decoded)
}

override suspend fun <R> call(
section: String,
method: String,
arguments: Map<String, Any?>,
returnBinding: (Any?) -> R
): R {
val apiMethod = runtime.metadata.runtimeApi(section).method(method)
val request = apiMethod.createRequest(runtime, arguments)

val response = socketService.stateCall(request)

val decoded = response?.let { apiMethod.decodeOutput(runtime, it) }

return returnBinding(decoded)
}

private fun decodeResponse(responseHex: String?, returnTypeName: String): Any? {
val returnType = runtime.typeRegistry.getOrThrow(returnTypeName)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.novafoundation.nova.runtime.multiNetwork.runtime

import android.util.Log
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
import io.novafoundation.nova.runtime.network.rpc.StateCallRequest
import io.novafoundation.nova.runtime.network.rpc.stateCall
import io.novasama.substrate_sdk_android.extensions.fromHex
import io.novasama.substrate_sdk_android.runtime.metadata.GetMetadataRequest
Expand All @@ -13,6 +12,7 @@ import io.novasama.substrate_sdk_android.wsrpc.SocketService
import io.novasama.substrate_sdk_android.wsrpc.executeAsync
import io.novasama.substrate_sdk_android.wsrpc.mappers.nonNull
import io.novasama.substrate_sdk_android.wsrpc.mappers.pojo
import io.novasama.substrate_sdk_android.wsrpc.request.runtime.state.StateCallRequest

private const val LATEST_SUPPORTED_METADATA_VERSION = 15

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import io.novasama.substrate_sdk_android.wsrpc.request.runtime.author.SubmitAndW
import io.novasama.substrate_sdk_android.wsrpc.request.runtime.author.SubmitExtrinsicRequest
import io.novasama.substrate_sdk_android.wsrpc.request.runtime.chain.RuntimeVersionFull
import io.novasama.substrate_sdk_android.wsrpc.request.runtime.chain.RuntimeVersionRequest
import io.novasama.substrate_sdk_android.wsrpc.request.runtime.state.StateCallRequest
import io.novasama.substrate_sdk_android.wsrpc.subscriptionFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
Expand Down

This file was deleted.

0 comments on commit 5b03c56

Please sign in to comment.