|
1 | 1 | package org.cardanofoundation.rewards.calculation; |
2 | 2 |
|
| 3 | +import org.cardanofoundation.rewards.data.provider.DataProvider; |
| 4 | +import org.cardanofoundation.rewards.entity.*; |
| 5 | + |
| 6 | +import java.util.ArrayList; |
| 7 | +import java.util.List; |
| 8 | + |
| 9 | +import static org.cardanofoundation.rewards.constants.RewardConstants.TOTAL_LOVELACE; |
| 10 | + |
3 | 11 | public class PoolRewardCalculation { |
4 | 12 |
|
5 | 13 | /* |
@@ -103,4 +111,130 @@ public static double calculateMemberReward(double poolReward, double margin, dou |
103 | 111 | double relativeMemberStake, double relativeStakeOfPool) { |
104 | 112 | return Math.floor((poolReward - poolCost) * (1 - margin) * relativeMemberStake / relativeStakeOfPool); |
105 | 113 | } |
| 114 | + |
| 115 | + public static PoolRewardCalculationResult calculatePoolRewardInEpoch(String poolId, int epoch, DataProvider dataProvider) { |
| 116 | + // Step 1: Get Pool information of current epoch |
| 117 | + // Example: https://api.koios.rest/api/v0/pool_history?_pool_bech32=pool1z5uqdk7dzdxaae5633fqfcu2eqzy3a3rgtuvy087fdld7yws0xt&_epoch_no=210 |
| 118 | + PoolRewardCalculationResult poolRewardCalculationResult = PoolRewardCalculationResult.builder() |
| 119 | + .epoch(epoch) |
| 120 | + .poolId(poolId) |
| 121 | + .poolReward(0.0) |
| 122 | + .build(); |
| 123 | + |
| 124 | + PoolHistory poolHistoryCurrentEpoch = dataProvider.getPoolHistory(poolId, epoch); |
| 125 | + if(poolHistoryCurrentEpoch == null) { |
| 126 | + return poolRewardCalculationResult; |
| 127 | + } |
| 128 | + |
| 129 | + double poolStake = poolHistoryCurrentEpoch.getActiveStake(); |
| 130 | + double poolFees = poolHistoryCurrentEpoch.getPoolFees(); |
| 131 | + double poolMargin = poolHistoryCurrentEpoch.getMargin(); |
| 132 | + double poolFixedCost = poolHistoryCurrentEpoch.getFixedCost(); |
| 133 | + int blocksPoolHasMinted = poolHistoryCurrentEpoch.getBlockCount(); |
| 134 | + |
| 135 | + poolRewardCalculationResult.setPoolFee(poolFees); |
| 136 | + poolRewardCalculationResult.setPoolMargin(poolMargin); |
| 137 | + poolRewardCalculationResult.setPoolCost(poolFixedCost); |
| 138 | + |
| 139 | + if (blocksPoolHasMinted == 0) { |
| 140 | + return poolRewardCalculationResult; |
| 141 | + } |
| 142 | + |
| 143 | + // Step 2: Get Epoch information of current epoch |
| 144 | + // Source: https://api.koios.rest/api/v0/epoch_info?_epoch_no=211 |
| 145 | + Epoch epochInfo = dataProvider.getEpochInfo(epoch); |
| 146 | + |
| 147 | + double activeStakeInEpoch = 0; |
| 148 | + if (epochInfo.getActiveStake() != null) { |
| 149 | + activeStakeInEpoch = epochInfo.getActiveStake(); |
| 150 | + } |
| 151 | + |
| 152 | + // The Shelley era and the ada pot system started on mainnet in epoch 208. |
| 153 | + // Fee and treasury values are 0 for epoch 208. |
| 154 | + double totalFeesForCurrentEpoch = 0.0; |
| 155 | + if (epoch > 209) { |
| 156 | + totalFeesForCurrentEpoch = epochInfo.getFees(); |
| 157 | + } |
| 158 | + |
| 159 | + int totalBlocksInEpoch = epochInfo.getBlockCount(); |
| 160 | + |
| 161 | + if (epoch > 212 && epoch < 255) { |
| 162 | + totalBlocksInEpoch = epochInfo.getNonOBFTBlockCount(); |
| 163 | + } |
| 164 | + |
| 165 | + // Get the ada reserves for the next epoch because it was already updated yet |
| 166 | + AdaPots adaPotsForNextEpoch = dataProvider.getAdaPotsForEpoch(epoch + 1); |
| 167 | + double reserves = adaPotsForNextEpoch.getReserves(); |
| 168 | + |
| 169 | + // Step 3: Get total ada in circulation |
| 170 | + double adaInCirculation = TOTAL_LOVELACE - reserves; |
| 171 | + |
| 172 | + // Step 4: Get protocol parameters for current epoch |
| 173 | + ProtocolParameters protocolParameters = dataProvider.getProtocolParametersForEpoch(epoch); |
| 174 | + double decentralizationParameter = protocolParameters.getDecentralisation(); |
| 175 | + int optimalPoolCount = protocolParameters.getOptimalPoolCount(); |
| 176 | + double influenceParam = protocolParameters.getPoolOwnerInfluence(); |
| 177 | + double monetaryExpandRate = protocolParameters.getMonetaryExpandRate(); |
| 178 | + double treasuryGrowRate = protocolParameters.getTreasuryGrowRate(); |
| 179 | + |
| 180 | + // Step 5: Calculate apparent pool performance |
| 181 | + double apparentPoolPerformance = |
| 182 | + PoolRewardCalculation.calculateApparentPoolPerformance(poolStake, activeStakeInEpoch, |
| 183 | + blocksPoolHasMinted, totalBlocksInEpoch, decentralizationParameter); |
| 184 | + poolRewardCalculationResult.setApparentPoolPerformance(apparentPoolPerformance); |
| 185 | + // Step 6: Calculate total available reward for pools (total reward pot after treasury cut) |
| 186 | + // ----- |
| 187 | + double totalRewardPot = TreasuryCalculation.calculateTotalRewardPotWithEta( |
| 188 | + monetaryExpandRate, totalBlocksInEpoch, decentralizationParameter, reserves, totalFeesForCurrentEpoch); |
| 189 | + |
| 190 | + double stakePoolRewardsPot = totalRewardPot - Math.floor(totalRewardPot * treasuryGrowRate); |
| 191 | + poolRewardCalculationResult.setStakePoolRewardsPot(stakePoolRewardsPot); |
| 192 | + // shelley-delegation.pdf 5.5.3 |
| 193 | + // "[...]the relative stake of the pool owner(s) (the amount of ada |
| 194 | + // pledged during pool registration)" |
| 195 | + |
| 196 | + // Step 7: Get the latest pool update before this epoch and extract the pledge |
| 197 | + double poolPledge = dataProvider.getPoolPledgeInEpoch(poolId, epoch); |
| 198 | + |
| 199 | + PoolOwnerHistory poolOwnersHistoryInEpoch = dataProvider.getHistoryOfPoolOwnersInEpoch(poolId, epoch); |
| 200 | + double totalActiveStakeOfOwners = poolOwnersHistoryInEpoch.getActiveStake(); |
| 201 | + |
| 202 | + if (totalActiveStakeOfOwners < poolPledge) { |
| 203 | + return poolRewardCalculationResult; |
| 204 | + } |
| 205 | + |
| 206 | + double relativeStakeOfPoolOwner = poolPledge / adaInCirculation; |
| 207 | + double relativePoolStake = poolStake / adaInCirculation; |
| 208 | + |
| 209 | + // Step 8: Calculate optimal pool reward |
| 210 | + double optimalPoolReward = |
| 211 | + PoolRewardCalculation.calculateOptimalPoolReward( |
| 212 | + stakePoolRewardsPot, |
| 213 | + optimalPoolCount, |
| 214 | + influenceParam, |
| 215 | + relativePoolStake, |
| 216 | + relativeStakeOfPoolOwner); |
| 217 | + poolRewardCalculationResult.setOptimalPoolReward(optimalPoolReward); |
| 218 | + |
| 219 | + // Step 9: Calculate pool reward as optimal pool reward * apparent pool performance |
| 220 | + double poolReward = PoolRewardCalculation.calculatePoolReward(optimalPoolReward, apparentPoolPerformance); |
| 221 | + poolRewardCalculationResult.setPoolReward(poolReward); |
| 222 | + |
| 223 | + // Step 10: Calculate pool operator reward |
| 224 | + double poolOperatorReward = PoolRewardCalculation.calculateLeaderReward(poolReward, poolMargin, poolFixedCost, |
| 225 | + totalActiveStakeOfOwners / adaInCirculation, relativePoolStake); |
| 226 | + poolRewardCalculationResult.setOperatorReward(poolOperatorReward); |
| 227 | + // Step 11: Calculate pool member reward |
| 228 | + List<Reward> memberRewards = new ArrayList<>(); |
| 229 | + for (Delegator delegator : poolHistoryCurrentEpoch.getDelegators()) { |
| 230 | + double memberReward = PoolRewardCalculation.calculateMemberReward(poolReward, poolMargin, |
| 231 | + poolFixedCost, delegator.getActiveStake() / adaInCirculation, relativePoolStake); |
| 232 | + memberRewards.add(Reward.builder() |
| 233 | + .amount(memberReward) |
| 234 | + .stakeAddress(delegator.getStakeAddress()) |
| 235 | + .build()); |
| 236 | + } |
| 237 | + poolRewardCalculationResult.setMemberRewards(memberRewards); |
| 238 | + return poolRewardCalculationResult; |
| 239 | + } |
106 | 240 | } |
0 commit comments