|
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 | /* |
@@ -77,4 +85,156 @@ public static double calculateOptimalPoolReward(double totalAvailableRewards, in |
77 | 85 | public static double calculatePoolReward(double optimalPoolReward, double poolPerformance) { |
78 | 86 | return optimalPoolReward * poolPerformance; |
79 | 87 | } |
| 88 | + |
| 89 | + /* |
| 90 | + * This method calculates the pool operator reward regarding the formula described |
| 91 | + * in the shelly-ledger.pdf p. 61, figure 47 |
| 92 | + */ |
| 93 | + public static double calculateLeaderReward(double poolReward, double margin, double poolCost, |
| 94 | + double relativeOwnerStake, double relativeStakeOfPool) { |
| 95 | + if (poolReward <= poolCost) { |
| 96 | + return poolCost; |
| 97 | + } |
| 98 | + |
| 99 | + return poolCost + |
| 100 | + Math.floor((poolReward - poolCost) * |
| 101 | + (margin + (1 - margin) * (relativeOwnerStake / relativeStakeOfPool))); |
| 102 | + } |
| 103 | + |
| 104 | + /* |
| 105 | + * This method calculates the pool member reward regarding the formula described |
| 106 | + * in the shelly-ledger.pdf p. 61, figure 47 |
| 107 | + * |
| 108 | + * See Haskell implementation: https://github.com/input-output-hk/cardano-ledger/blob/aed5dde9cd1096cfc2e255879cd617c0d64f8d9d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rewards.hs#L117 |
| 109 | + */ |
| 110 | + public static double calculateMemberReward(double poolReward, double margin, double poolCost, |
| 111 | + double relativeMemberStake, double relativeStakeOfPool) { |
| 112 | + return Math.floor((poolReward - poolCost) * (1 - margin) * relativeMemberStake / relativeStakeOfPool); |
| 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 | + } |
80 | 240 | } |
0 commit comments