Skip to content

Commit 51aebf4

Browse files
Merge pull request #7 from cardano-foundation/release-please--branches--main
chore(main): release 0.0.2-SNAPSHOT
2 parents d2c85a5 + 8722993 commit 51aebf4

23 files changed

+344
-189
lines changed

.github/workflows/publish.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ on:
1010
jobs:
1111
plot-and-publish:
1212
runs-on: ubuntu-latest
13-
if: |
14-
"contains(github.event.head_commit.message, 'release-please--branches--main')" ||
15-
${{ github.event_name == 'pull_request' }}
13+
if: "contains(github.event.head_commit.message, 'release-please--branches--main')"
1614
steps:
1715
- name: ⬇️ Checkout repository
1816
uses: actions/checkout@v3

.github/workflows/release.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ jobs:
1818
uses: google-github-actions/release-please-action@v3
1919
with:
2020
release-type: maven
21+
release-as: 0.1.0
2122
bump-minor-pre-major: true
2223
bump-patch-for-minor-pre-major: true

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ flowchart
3131
D --> | <a href='https://github.com/cardano-foundation/cf-java-rewards-calculation/blob/a794130dc0e320426725a58b8b15f1fbe726b2de/src/main/java/org/cardanofoundation/rewards/calculation/PoolRewardCalculation.java#L87'>Rewards Equation<br /> for Pool 2</a> | H[Stake Pool 2]
3232
D --> I[...]
3333
D --> | <a href='https://github.com/cardano-foundation/cf-java-rewards-calculation/blob/a794130dc0e320426725a58b8b15f1fbe726b2de/src/main/java/org/cardanofoundation/rewards/calculation/PoolRewardCalculation.java#L87'>Rewards Equation<br /> for Pool n</a> | J[Stake Pool n]
34-
J --> | <b><i>margin & minPoolCost</i></b> | K[Operators]
35-
J --> | <b><i>rewards</b></i> | L[Delegators]
34+
J --> | <b><i><a href='https://github.com/cardano-foundation/cf-java-rewards-calculation/blob/a3151888e3133937b6098efdec72b587d88ba4cd/src/main/java/org/cardanofoundation/rewards/calculation/PoolRewardCalculation.java#L85'>margin & minPoolCost</a></i></b> | K[Operators]
35+
J --> | <b><i><a href='https://github.com/cardano-foundation/cf-java-rewards-calculation/blob/a3151888e3133937b6098efdec72b587d88ba4cd/src/main/java/org/cardanofoundation/rewards/calculation/PoolRewardCalculation.java#L102'>rewards</a></b></i> | L[Delegators]
3636
D --> | Capped Pool Rewards | C
3737
L <--> | Stake Key Registration & <br /> Deregistration | M[Deposits]
3838
K <--> | Stake Pool Registration & <br /> Deregistration | M
39-
M --> | Unclaimed Refunds for Retired Pools | C
39+
M --> | <a href='https://github.com/cardano-foundation/cf-java-rewards-calculation/blob/1ed16503d7ed592410d55489cc2144762ce718d5/src/main/java/org/cardanofoundation/rewards/calculation/TreasuryCalculation.java#L122'>Unclaimed Refunds for Retired Pools</a> | C
4040
end
4141
4242
style A fill:#5C8DFF,stroke:#5C8DFF

src/main/java/org/cardanofoundation/rewards/RewardsApplication.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public void run(ApplicationArguments args) throws Exception {
6464

6565
if (!args.getOptionNames().isEmpty()) {
6666
logger.warn("Finished actions. Exiting...");
67-
int exitCode = SpringApplication.exit(context, (ExitCodeGenerator) () -> 0);
67+
int exitCode = SpringApplication.exit(context, () -> 0);
6868
System.exit(exitCode);
6969
}
7070
}

src/main/java/org/cardanofoundation/rewards/calculation/PoolRewardCalculation.java

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package org.cardanofoundation.rewards.calculation;
22

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+
311
public class PoolRewardCalculation {
412

513
/*
@@ -77,4 +85,156 @@ public static double calculateOptimalPoolReward(double totalAvailableRewards, in
7785
public static double calculatePoolReward(double optimalPoolReward, double poolPerformance) {
7886
return optimalPoolReward * poolPerformance;
7987
}
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+
}
80240
}

src/main/java/org/cardanofoundation/rewards/calculation/TreasuryCalculation.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
package org.cardanofoundation.rewards.calculation;
22

33
import org.cardanofoundation.rewards.data.provider.DataProvider;
4-
import org.cardanofoundation.rewards.data.provider.JsonDataProvider;
5-
import org.cardanofoundation.rewards.data.provider.KoiosDataProvider;
64
import org.cardanofoundation.rewards.entity.*;
75
import org.cardanofoundation.rewards.enums.AccountUpdateAction;
6+
import org.cardanofoundation.rewards.enums.MirPot;
87

98
import java.util.ArrayList;
109
import java.util.Comparator;
1110
import java.util.List;
1211

1312
import static org.cardanofoundation.rewards.constants.RewardConstants.*;
14-
import static org.cardanofoundation.rewards.util.CurrencyConverter.lovelaceToAda;
1513

1614
public class TreasuryCalculation {
1715

@@ -106,6 +104,14 @@ public static TreasuryCalculationResult calculateTreasuryForEpoch(int epoch, Dat
106104
// treasury (see: Pool Reap Transition, p.53, figure 40, shely-ledger.pdf)
107105
treasuryForCurrentEpoch += TreasuryCalculation.calculateUnclaimedRefundsForRetiredPools(epoch, dataProvider);
108106

107+
// Check if there was a MIR Certificate in the previous epoch
108+
List<MirCertificate> mirCertificates = dataProvider.getMirCertificatesInEpoch(epoch - 1);
109+
for (MirCertificate mirCertificate : mirCertificates) {
110+
if (mirCertificate.getPot() == MirPot.TREASURY) {
111+
treasuryForCurrentEpoch -= mirCertificate.getTotalRewards();
112+
}
113+
}
114+
109115
return TreasuryCalculationResult.builder()
110116
.calculatedTreasury(treasuryForCurrentEpoch)
111117
.actualTreasury(expectedTreasuryForCurrentEpoch)

src/main/java/org/cardanofoundation/rewards/data/fetcher/KoiosDataFetcher.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
import org.cardanofoundation.rewards.entity.*;
66
import org.slf4j.Logger;
77
import org.slf4j.LoggerFactory;
8+
import rest.koios.client.backend.api.base.exception.ApiException;
9+
import rest.koios.client.backend.api.pool.model.PoolDelegatorHistory;
10+
import rest.koios.client.backend.factory.options.Options;
811

912
import java.io.File;
1013
import java.io.IOException;
14+
import java.util.ArrayList;
1115
import java.util.List;
1216

1317
import static org.cardanofoundation.rewards.enums.DataType.*;

src/main/java/org/cardanofoundation/rewards/data/provider/DataProvider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ public interface DataProvider {
2121
public List<PoolDeregistration> getRetiredPoolsInEpoch(int epoch);
2222

2323
public List<AccountUpdate> getAccountUpdatesUntilEpoch(List<String> stakeAddresses, int epoch);
24+
25+
public List<MirCertificate> getMirCertificatesInEpoch(int epoch);
2426
}

src/main/java/org/cardanofoundation/rewards/data/provider/JsonDataProvider.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ private String getResourceFolder(DataType dataType, Integer epoch, String poolId
2121
return String.format("./src/test/resources/pools/%s/history_epoch_%d.json", poolId, epoch);
2222
} else if (dataType.equals(POOL_OWNER_HISTORY)) {
2323
return String.format("./src/test/resources/pools/%s/owner_account_history_epoch_%d.json", poolId, epoch);
24-
}
25-
else {
24+
} else if (dataType.equals(MIR_CERTIFICATE)) {
25+
return "./src/test/resources/mirCertificates/mirCertificates.json";
26+
} else {
2627
return String.format("./src/test/resources/%s/epoch%d.json", dataType.resourceFolderName, epoch);
2728
}
2829
}
@@ -117,4 +118,16 @@ public List<AccountUpdate> getAccountUpdatesUntilEpoch(List<String> stakeAddress
117118
if (accountUpdates == null) return List.of();
118119
return accountUpdates.stream().filter(accountUpdate -> stakeAddresses.contains(accountUpdate.getStakeAddress())).toList();
119120
}
121+
122+
@Override
123+
public List<MirCertificate> getMirCertificatesInEpoch(int epoch) {
124+
List<MirCertificate> mirCertificates = getListFromJson(MIR_CERTIFICATE, epoch, MirCertificate.class);
125+
Epoch epochInfo = getDataFromJson(EPOCH_INFO, epoch, Epoch.class);
126+
if (mirCertificates == null || epochInfo == null) return List.of();
127+
128+
return mirCertificates.stream()
129+
.filter(mirCertificate -> mirCertificate.getBlockTime() <= epochInfo.getUnixTimeLastBlock())
130+
.filter(mirCertificate -> mirCertificate.getBlockTime() >= epochInfo.getUnixTimeFirstBlock())
131+
.toList();
132+
}
120133
}

src/main/java/org/cardanofoundation/rewards/data/provider/KoiosDataProvider.java

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import rest.koios.client.backend.api.epoch.model.EpochInfo;
1414
import rest.koios.client.backend.api.epoch.model.EpochParams;
1515
import rest.koios.client.backend.api.network.model.Totals;
16+
import rest.koios.client.backend.api.pool.model.PoolDelegatorHistory;
1617
import rest.koios.client.backend.api.pool.model.PoolUpdate;
1718
import rest.koios.client.backend.factory.BackendFactory;
1819
import rest.koios.client.backend.factory.BackendService;
@@ -21,6 +22,7 @@
2122
import rest.koios.client.backend.factory.options.filters.FilterType;
2223
import java.util.ArrayList;
2324
import java.util.List;
25+
import java.util.Objects;
2426

2527
@Service
2628
@RequiredArgsConstructor
@@ -54,21 +56,21 @@ public Epoch getEpochInfo(int epoch) {
5456
.getEpochInformationByEpoch(epoch).getValue();
5557

5658
epochEntity = EpochMapper.fromKoiosEpochInfo(epochInfo);
59+
List<Block> blocks = new ArrayList<>();
60+
for (int offset = 0; offset < epochEntity.getBlockCount(); offset += 1000) {
61+
blocks.addAll(koiosBackendService.getBlockService().getBlockList(Options.builder()
62+
.option(Filter.of("epoch_no", FilterType.EQ, String.valueOf(epoch)))
63+
.option(Offset.of(offset))
64+
.build()).getValue());
65+
}
66+
epochEntity.setPoolsMadeBlocks(blocks.stream().map(Block::getPool).filter(Objects::nonNull).distinct().toList());
5767
if (epoch < 211) {
5868
epochEntity.setOBFTBlockCount(epochEntity.getBlockCount());
5969
epochEntity.setNonOBFTBlockCount(0);
6070
} else if (epoch > 256) {
6171
epochEntity.setOBFTBlockCount(0);
6272
epochEntity.setNonOBFTBlockCount(epochEntity.getBlockCount());
6373
} else {
64-
List<Block> blocks = new ArrayList<>();
65-
for (int offset = 0; offset < epochEntity.getBlockCount(); offset += 1000) {
66-
blocks.addAll(koiosBackendService.getBlockService().getBlockList(Options.builder()
67-
.option(Filter.of("epoch_no", FilterType.EQ, String.valueOf(epoch)))
68-
.option(Offset.of(offset))
69-
.build()).getValue());
70-
}
71-
7274
epochEntity.setOBFTBlockCount((int) blocks.stream().filter(block -> block.getPool() == null).count());
7375
epochEntity.setNonOBFTBlockCount((int) blocks.stream().filter(block -> block.getPool() != null).count());
7476
}
@@ -103,7 +105,13 @@ public PoolHistory getPoolHistory(String poolId, int epoch) {
103105
e.printStackTrace();
104106
}
105107

106-
return PoolHistoryMapper.fromKoiosPoolHistory(poolHistory);
108+
PoolHistory history = PoolHistoryMapper.fromKoiosPoolHistory(poolHistory);
109+
110+
if (history == null) return null;
111+
112+
List<Delegator> poolMemberInEpoch = getPoolMemberInEpoch(poolId, epoch);
113+
history.setDelegators(poolMemberInEpoch);
114+
return history;
107115
}
108116

109117
@Override
@@ -217,4 +225,26 @@ public List<org.cardanofoundation.rewards.entity.AccountUpdate> getAccountUpdate
217225

218226
return accountUpdates;
219227
}
228+
229+
@Override
230+
public List<MirCertificate> getMirCertificatesInEpoch(int epoch) {
231+
return null;
232+
}
233+
234+
private List<Delegator> getPoolMemberInEpoch(String poolId, int epoch) {
235+
List<Delegator> delegators = new ArrayList<>();
236+
try {
237+
List<PoolDelegatorHistory> poolDelegatorsHistory = koiosBackendService
238+
.getPoolService().getPoolDelegatorsHistory(poolId, epoch, Options.EMPTY).getValue();
239+
for (PoolDelegatorHistory poolDelegator : poolDelegatorsHistory) {
240+
delegators.add(Delegator.builder()
241+
.activeStake(Double.valueOf(poolDelegator.getAmount()))
242+
.stakeAddress(poolDelegator.getStakeAddress())
243+
.build());
244+
}
245+
} catch (ApiException e) {
246+
e.printStackTrace();
247+
}
248+
return delegators;
249+
}
220250
}

0 commit comments

Comments
 (0)