@marinade.finance/scoring 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/computing/cluster.ts +102 -0
  2. package/computing/eligibility.ts +163 -0
  3. package/computing/score.ts +164 -0
  4. package/computing/stake.ts +246 -0
  5. package/computing/validators.ts +133 -0
  6. package/{src/dto → dto}/bonds.dto.ts +0 -2
  7. package/{src/dto → dto}/cluster.dto.ts +0 -1
  8. package/{src/dto → dto}/eligibility.dto.ts +0 -5
  9. package/{src/dto → dto}/jito.dto.ts +1 -2
  10. package/{src/dto → dto}/marinade.dto.ts +1 -1
  11. package/{src/dto → dto}/rewards.dto.ts +1 -3
  12. package/{src/dto → dto}/scoring.dto.ts +1 -7
  13. package/{src/dto → dto}/snapshots.dto.ts +0 -3
  14. package/{src/dto → dto}/stakes.dto.ts +0 -4
  15. package/{src/dto → dto}/validators.dto.ts +1 -37
  16. package/package.json +17 -13
  17. package/{src/providers → providers}/api-data.provider.ts +58 -58
  18. package/{src/providers → providers}/file-data.provider.ts +7 -15
  19. package/{src/utils → utils}/csv.ts +3 -3
  20. package/src/cluster/cluster.module.ts +0 -8
  21. package/src/cluster/cluster.service.ts +0 -108
  22. package/src/config/config.module.ts +0 -8
  23. package/src/config/config.service.ts +0 -136
  24. package/src/eligibility/eligibility.module.ts +0 -11
  25. package/src/eligibility/eligibility.service.ts +0 -183
  26. package/src/scoring/scoring.module.ts +0 -11
  27. package/src/scoring/scoring.service.ts +0 -184
  28. package/src/stake/stake.module.ts +0 -10
  29. package/src/stake/stake.service.ts +0 -242
  30. package/src/validators/validators.module.ts +0 -48
  31. package/src/validators/validators.service.ts +0 -177
  32. /package/{src/constants → constants}/marinade.json +0 -0
  33. /package/{src/errors → errors}/fetching.ts +0 -0
  34. /package/{src/interfaces → interfaces}/data-provider.interface.ts +0 -0
  35. /package/{src/utils → utils}/maths.ts +0 -0
  36. /package/{src/utils → utils}/solana.ts +0 -0
  37. /package/{src/utils → utils}/zip.ts +0 -0
@@ -1,183 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
- import { ConfigService } from '../config/config.service';
3
- import { Bonds } from '../dto/bonds.dto';
4
- import { ClusterInfo } from '../dto/cluster.dto';
5
- import { EligibilityConfig, EvaluationResult, Issue, IssueType, ValidatorEligibility, ValidatorsEligibilities } from '../dto/eligibility.dto';
6
- import { ScoreDto, Scores } from '../dto/scoring.dto';
7
- import { AggregatedValidator, AggregatedValidators } from '../dto/validators.dto';
8
- import { ValidatorsService } from '../validators/validators.service';
9
-
10
- export class EligibilityService {
11
-
12
- constructor (private validatorsService: ValidatorsService, private configService: ConfigService) {}
13
-
14
- evalLowCreditsIssue (validator: AggregatedValidator, epochIndex: number, eligibilityConfig: EligibilityConfig, clusterInfo: ClusterInfo): Issue | null {
15
- if (!validator.epochs[epochIndex]) {
16
- return null;
17
- }
18
-
19
- const targetCredits = clusterInfo.targetCreditsByEpoch.get(validator.epochs[epochIndex]!);
20
- if (!targetCredits) {
21
- return null;
22
- }
23
-
24
- const actualCredits = validator.credits[epochIndex] || 0;
25
- const creditsPct = Math.round((100 * actualCredits) / targetCredits);
26
-
27
- if (creditsPct < eligibilityConfig.voteCreditsLow) {
28
- return { type: IssueType.VOTE_CREDITS_LOW, message: `Credits @ ${creditsPct}%` };
29
- } else if (creditsPct < eligibilityConfig.voteCreditsWarning) {
30
- return { type: IssueType.VOTE_CREDITS_WARNING, message: `(Warning) Credits @ ${creditsPct}%` };
31
- }
32
-
33
- return null;
34
- }
35
-
36
- evalLowExternalStakeIssue (validator: AggregatedValidator, epochIndex: number, eligibilityConfig: EligibilityConfig): Issue | undefined {
37
- const externalStake = validator.externalStake[epochIndex];
38
-
39
- if (!externalStake) {
40
- return undefined;
41
- }
42
-
43
- if (externalStake < eligibilityConfig.minExternalStake) {
44
- return { type: IssueType.EXTERNAL_STAKE, message: `External stake: ${externalStake}` };
45
- }
46
- return undefined;
47
- }
48
-
49
- evalBlacklistIssue (validator: AggregatedValidator): Issue | undefined {
50
- if (validator.blacklisted) {
51
- return { type: IssueType.BLACKLIST, message: 'Validator is blacklisted' };
52
- }
53
- return undefined;
54
- }
55
-
56
- evalHighCommissionIssue (validator: AggregatedValidator, epochIndex: number, eligibilityConfig: EligibilityConfig): Issue | undefined {
57
- const commission = validator.commission[epochIndex];
58
-
59
- if (!commission) {
60
- return undefined;
61
- }
62
-
63
- if (eligibilityConfig.maxCommission < commission!) {
64
- return { type: IssueType.COMMISSION, message: `Commission: ${commission}%` };
65
- }
66
- return undefined;
67
- }
68
-
69
- evalScoreIssue (epochIndex: number, eligibilityConfig: EligibilityConfig, { concentrationScore }: ScoreDto): Issue | undefined {
70
- if (epochIndex === 0 && concentrationScore < eligibilityConfig.minScore) {
71
- return { type: IssueType.SCORE, message: `Concentration Score: ${concentrationScore}` };
72
- }
73
- return undefined;
74
- }
75
-
76
- evalBondIssue (validator: AggregatedValidator, bonds: Bonds): Issue | undefined {
77
- if (!(validator.voteAccount in bonds)) {
78
- return { type: IssueType.NO_BOND, message: 'Validator has no bond' };
79
- }
80
- return undefined;
81
- }
82
-
83
- getValidatorIssuesInEpoch (validator: AggregatedValidator, bonds: Bonds, epochIndex: number, eligibilityConfig: EligibilityConfig, clusterInfo: ClusterInfo, score: ScoreDto): Issue[] {
84
- if (!validator.dataAvailable[epochIndex]) {
85
- return [{ type: IssueType.NO_DATA, message: `No data for validator in epoch ${validator.epochs[epochIndex]}` }];
86
- }
87
- return [
88
- this.evalBlacklistIssue(validator),
89
- this.evalLowCreditsIssue(validator, epochIndex, eligibilityConfig, clusterInfo),
90
- this.evalLowExternalStakeIssue(validator, epochIndex, eligibilityConfig),
91
- this.evalHighCommissionIssue(validator, epochIndex, eligibilityConfig),
92
- this.evalScoreIssue(epochIndex, eligibilityConfig, score),
93
- this.evalBondIssue(validator, bonds),
94
- ].filter(Boolean) as Issue[];
95
- }
96
-
97
- evalIssuesInEpoch (issues: Issue[]): EvaluationResult {
98
- let criticals = 0;
99
- let warnings = 0;
100
-
101
- const criticalTypes = new Set([
102
- IssueType.NO_BOND,
103
- IssueType.NO_DATA,
104
- IssueType.BLACKLIST,
105
- IssueType.COMMISSION,
106
- IssueType.EXTERNAL_STAKE,
107
- IssueType.CENTRALIZATION,
108
- IssueType.SCORE,
109
- IssueType.VOTE_CREDITS_LOW,
110
- ]);
111
-
112
- const warningTypes = new Set([
113
- IssueType.VOTE_CREDITS_WARNING,
114
- ]);
115
-
116
- issues.forEach(issue => {
117
- if (criticalTypes.has(issue.type)) {
118
- criticals++;
119
- } else if (warningTypes.has(issue.type)) {
120
- warnings++;
121
- } else {
122
- throw new Error(`Unexpected issue: ${issue.type}`);
123
- }
124
- });
125
-
126
- return { criticals, warnings };
127
- }
128
-
129
- evalIssues (issuesCollection: Issue[][], epochs: number): EvaluationResult {
130
- let totalCriticals = 0;
131
- let totalWarnings = 0;
132
- for (let epochIndex = 0; epochIndex < epochs; epochIndex++) {
133
- if (!issuesCollection[epochIndex]) {
134
- continue;
135
- }
136
- const { criticals, warnings } = this.evalIssuesInEpoch(issuesCollection[epochIndex]!);
137
- totalCriticals += criticals;
138
- totalWarnings += warnings;
139
- }
140
- return { criticals: totalCriticals, warnings: totalWarnings };
141
- }
142
-
143
- isBasicEligible (issuesCollection: Issue[][], eligibilityConfig: EligibilityConfig): boolean {
144
- const { criticals, warnings } = this.evalIssues(issuesCollection, eligibilityConfig.basicEligibilityEpochs + 1);
145
- return criticals === 0 && warnings <= eligibilityConfig.maxWarnings;
146
- }
147
-
148
- isBonusEligible (issuesCollection: Issue[][], eligibilityConfig: EligibilityConfig): boolean {
149
- const totalEpochs = eligibilityConfig.basicEligibilityEpochs + eligibilityConfig.bonusEligibilityExtraEpochs + 1;
150
- const { criticals, warnings } = this.evalIssues(issuesCollection, totalEpochs);
151
- return criticals === 0 && warnings <= eligibilityConfig.maxWarnings;
152
- }
153
-
154
- getIssuesCollection (clusterInfo: ClusterInfo, aggregatedValidator: AggregatedValidator, bonds: Bonds, score: ScoreDto, eligibilityConfig: EligibilityConfig): Issue[][] {
155
- return Array.from({ length: eligibilityConfig.basicEligibilityEpochs + eligibilityConfig.bonusEligibilityExtraEpochs + 1 }, (_, epochIndex) =>
156
- this.getValidatorIssuesInEpoch(aggregatedValidator, bonds, epochIndex, eligibilityConfig, clusterInfo, score)
157
- );
158
- }
159
-
160
- computeValidatorEligibility (clusterInfo: ClusterInfo, aggregatedValidator: AggregatedValidator, bonds: Bonds, scores: ScoreDto, eligibilityConfig: EligibilityConfig): ValidatorEligibility {
161
- const issuesCollection = this.getIssuesCollection(clusterInfo, aggregatedValidator, bonds, scores, eligibilityConfig);
162
- const minExternalStake = this.validatorsService.selectExternalStakeMin(aggregatedValidator, eligibilityConfig.basicEligibilityEpochs);
163
-
164
- return {
165
- basicEligibility: this.isBasicEligible(issuesCollection, eligibilityConfig),
166
- bonusEligibility: this.isBonusEligible(issuesCollection, eligibilityConfig),
167
- issuesCollection,
168
- capFromBond: this.configService.getScoringConfig().CAP_FROM_BOND,
169
- capFromExternalStake: minExternalStake * (eligibilityConfig.maxStakeShare / (1 - eligibilityConfig.maxStakeShare)),
170
- };
171
- }
172
-
173
- computeValidatorsEligibilities (clusterInfo: ClusterInfo, scores: Scores, aggregatedValidators: AggregatedValidators, bonds: Bonds, eligibilityConfig: EligibilityConfig): ValidatorsEligibilities {
174
- const result: ValidatorsEligibilities = {};
175
- for (const [voteAccount, validator] of Object.entries(aggregatedValidators)) {
176
- if (!scores[voteAccount]) {
177
- continue;
178
- }
179
- result[voteAccount] = this.computeValidatorEligibility(clusterInfo, validator, bonds, scores[voteAccount]!, eligibilityConfig);
180
- }
181
- return result;
182
- }
183
- }
@@ -1,11 +0,0 @@
1
- import { Module } from '@nestjs/common';
2
- import { ScoringService } from './scoring.service';
3
- import { ValidatorsModule } from '../validators/validators.module';
4
- import { ConfigModule } from '../config/config.module';
5
-
6
- @Module({
7
- imports: [ValidatorsModule, ConfigModule],
8
- providers: [ScoringService],
9
- exports: [ScoringService],
10
- })
11
- export class ScoringModule {}
@@ -1,184 +0,0 @@
1
- /* eslint-disable no-console */
2
- /* eslint-disable @typescript-eslint/no-unused-vars */
3
- import { ClusterInfo } from '../dto/cluster.dto';
4
- import { ScoreDto, ScoreConfig, Scores, Variables } from '../dto/scoring.dto';
5
- import { AggregatedValidator, AggregatedValidators } from '../dto/validators.dto';
6
- import { ValidatorsService } from '../validators/validators.service';
7
- import { Parser } from 'expr-eval';
8
- import { mathUtilityFunctions } from '../utils/maths';
9
- import { zip } from '../utils/zip';
10
- import { ValidatorsEligibilities } from '../dto/eligibility.dto';
11
- import { ConfigService } from '../config/config.service';
12
-
13
- export class ScoringService {
14
- constructor (private readonly validatorsService: ValidatorsService, private readonly configService: ConfigService) {}
15
-
16
- selectVariables (clusterInfo: ClusterInfo, aggregatedValidator: AggregatedValidator, scoreConfig: ScoreConfig): Variables {
17
- return {
18
- bp_cluster_mean: clusterInfo.meanBlockProductionOverBasicEligibilityPeriod,
19
- bp_cluster_std_dev: clusterInfo.stdDevBlockProductionOverBasicEligibilityPeriod,
20
- credits_pct_mean: this.validatorsService.selectCreditsPctMean(aggregatedValidator, clusterInfo, scoreConfig.epochs),
21
- bp_mean: this.validatorsService.selectBlockProductionMean(aggregatedValidator, scoreConfig.epochs),
22
- commission_inflation_max: this.validatorsService.selectCommissonInflationMax(aggregatedValidator, scoreConfig.epochs + 1),
23
- commission_mev: this.validatorsService.selectCommissonMEV(aggregatedValidator),
24
- country_stake_concentration_last: this.validatorsService.selectCountryStakeConcentration(aggregatedValidator, clusterInfo),
25
- city_stake_concentration_last: this.validatorsService.selectCityStakeConcentration(aggregatedValidator, clusterInfo),
26
- aso_stake_concentration_last: this.validatorsService.selectASOStakeConcentration(aggregatedValidator, clusterInfo),
27
- node_stake_last: this.validatorsService.selectNodeStake(aggregatedValidator),
28
- };
29
- }
30
-
31
- computeValidatorScore (clusterInfo: ClusterInfo, aggregatedValidator: AggregatedValidator, formulas: string[], weights: number[], scoreConfig: ScoreConfig): ScoreDto {
32
- const scoreErrors: boolean[] = [];
33
- const scores: number[] = [];
34
- const values: number[] = [];
35
- const tooltips: string[] = [];
36
- let totalWeight = 0;
37
- let totalScore = 0;
38
- let totalConcentrationWeight = 0;
39
- let totalConcentrationScore = 0;
40
- const variables = { ...this.selectVariables(clusterInfo, aggregatedValidator, scoreConfig), ...mathUtilityFunctions() };
41
-
42
- for (const [formula, weight, tooltipBuilder] of zip(formulas, weights, this.getScoreTooltipBuilders())) {
43
- let score = 0;
44
- try {
45
- values.push(0);
46
- score = Parser.evaluate(formula, variables);
47
- scoreErrors.push(false);
48
- } catch (err) {
49
- console.error('Failed to calculate', formula, 'for', aggregatedValidator.voteAccount, 'err:', err);
50
- scoreErrors.push(true);
51
- }
52
- totalWeight += weight;
53
- totalScore += weight * score;
54
- if (scoreConfig.concentrationParams.includes(scores.length)) {
55
- totalConcentrationWeight += weight;
56
- totalConcentrationScore += weight * score;
57
- }
58
- tooltips.push(tooltipBuilder(aggregatedValidator, clusterInfo));
59
- scores.push(score);
60
- }
61
-
62
- return {
63
- score: totalScore / totalWeight,
64
- concentrationScore: totalConcentrationScore / totalConcentrationWeight,
65
- scores,
66
- values,
67
- scoreErrors,
68
- tooltips,
69
- };
70
- }
71
-
72
- computeValidatorsScores (clusterInfo: ClusterInfo, aggregatedValidators: AggregatedValidators, formulas: string[], weights: number[], scoreConfig: ScoreConfig): Scores {
73
- const result: Scores = {};
74
-
75
- for (const [voteAccount, aggregatedValidator] of Object.entries(aggregatedValidators)) {
76
- result[voteAccount] = this.computeValidatorScore(clusterInfo, aggregatedValidator, formulas, weights, scoreConfig);
77
- }
78
-
79
- return result;
80
- }
81
-
82
- scoreTooltipCredits (validator: AggregatedValidator, clusterInfo: ClusterInfo): string {
83
- return `Credits: ${validator.credits.slice(1).map((credits, e) => {
84
- const epoch = validator.epochs[e + 1];
85
- if (epoch !== undefined) {
86
- const targetCredits = clusterInfo.targetCreditsByEpoch.get(epoch) || 1;
87
- return `${(100 * credits / targetCredits).toFixed(2)}%`;
88
- } else {
89
- return 'N/A';
90
- }
91
- }).join(', ')}`;
92
- }
93
-
94
- scoreTooltipBlockProduction (validator: AggregatedValidator, clusterInfo: ClusterInfo): string {
95
- return `Block production: ${validator.blocksProduced.map((blocksProduced, e) => {
96
- const leaderSlot = validator.leaderSlots[e];
97
- if (leaderSlot) {
98
- return `${(100 * blocksProduced / leaderSlot).toFixed(2)}%`;
99
- } else {
100
- return '-';
101
- }
102
- }).join(', ')}`;
103
- }
104
-
105
- scoreTooltipCommissionInflation (validator: AggregatedValidator, clusterInfo: ClusterInfo): string {
106
- return `Inflation commission: ${validator.commission.map(c => `${c}%`).join(', ')}`;
107
- }
108
-
109
- scoreTooltipCommissionMEV (validator: AggregatedValidator, clusterInfo: ClusterInfo): string {
110
- return `MEV commission: ${validator.mevCommission} %`;
111
- }
112
-
113
- scoreTooltipCountryStakeConcentration (validator: AggregatedValidator, clusterInfo: ClusterInfo): string {
114
- return `Country: ${validator.country}`;
115
- }
116
-
117
- scoreTooltipCityStakeConcentration (validator: AggregatedValidator, clusterInfo: ClusterInfo): string {
118
- return `City: ${validator.city}`;
119
- }
120
-
121
- scoreTooltipNodeStake (validator: AggregatedValidator, clusterInfo: ClusterInfo): string {
122
- return `Stake of the node: ${Math.round(validator.stake[0] ?? 0).toLocaleString()} SOL`;
123
- }
124
-
125
- scoreTooltipASOStakeConcentration (validator: AggregatedValidator, clusterInfo: ClusterInfo): string {
126
- return `ASO: ${validator.aso}`;
127
- }
128
-
129
- getScoreTooltipBuilders (): ((validator: AggregatedValidator, clusterInfo: ClusterInfo) => string)[] {
130
- return [
131
- this.scoreTooltipCredits,
132
- this.scoreTooltipBlockProduction,
133
- this.scoreTooltipCommissionInflation,
134
- this.scoreTooltipCommissionMEV,
135
- this.scoreTooltipCountryStakeConcentration,
136
- this.scoreTooltipCityStakeConcentration,
137
- this.scoreTooltipASOStakeConcentration,
138
- this.scoreTooltipNodeStake,
139
- ];
140
- }
141
-
142
- getDefaultFormulas (): string[] {
143
- return [
144
- this.configService.getScoringConfig().formulaVoteCredits,
145
- this.configService.getScoringConfig().formulaBlockProduction,
146
- this.configService.getScoringConfig().formulaInflationCommission,
147
- this.configService.getScoringConfig().formulaMEVCommission,
148
- this.configService.getScoringConfig().formulaStakeConcentrationCountry,
149
- this.configService.getScoringConfig().formulaStakeConcentrationCity,
150
- this.configService.getScoringConfig().formulaStakeConcentrationASO,
151
- this.configService.getScoringConfig().formulaStakeConcentrationNode,
152
- ];
153
- }
154
-
155
- getDefaultWeights (): number[] {
156
-
157
- return [
158
- this.configService.getScoringConfig().weightVoteCredits,
159
- this.configService.getScoringConfig().weightBlockProduction,
160
- this.configService.getScoringConfig().weightInflationCommission,
161
- this.configService.getScoringConfig().weightMEVCommission,
162
- this.configService.getScoringConfig().weightStakeConcentrationCountry,
163
- this.configService.getScoringConfig().weightStakeConcentrationCity,
164
- this.configService.getScoringConfig().weightStakeConcentrationASO,
165
- this.configService.getScoringConfig().weightStakeConcentrationNode,
166
- ];
167
- }
168
-
169
- orderByEligibiltyAndScore (scores: Scores, eligibilities: ValidatorsEligibilities): string[] {
170
- const voteAccounts = Object.keys(scores);
171
- voteAccounts.sort((a, b) => {
172
- const eligibilityOrder = Number(eligibilities[b]?.basicEligibility ?? 0) - Number(eligibilities[a]?.basicEligibility ?? 0);
173
- if (eligibilityOrder === 0) {
174
- const scoreA = scores[a]?.score ?? 0;
175
- const scoreB = scores[b]?.score ?? 0;
176
-
177
- return scoreB - scoreA;
178
- }
179
- return eligibilityOrder;
180
- });
181
-
182
- return voteAccounts;
183
- }
184
- }
@@ -1,10 +0,0 @@
1
- import { Module } from '@nestjs/common';
2
- import { StakeService } from './stake.service';
3
- import { ConfigModule } from '../config/config.module';
4
-
5
- @Module({
6
- imports: [ConfigModule],
7
- providers: [StakeService],
8
- exports: [StakeService],
9
- })
10
- export class StakeModule {}
@@ -1,242 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
- import { ValidatorsEligibilities } from '../dto/eligibility.dto';
3
- import { Scores } from '../dto/scoring.dto';
4
- import { Votes } from '../dto/snapshots.dto';
5
- import { ProcessedVotesAndTVLs, Stakes, StakesConfig, StakingVariables } from '../dto/stakes.dto';
6
- import { AggregatedValidators } from '../dto/validators.dto';
7
- import { Parser } from 'expr-eval';
8
- import { sum } from '../utils/maths';
9
- import { ConfigService } from '../config/config.service';
10
-
11
- export class StakeService {
12
- constructor (private readonly configService: ConfigService) { }
13
-
14
- private removeFromVotes (votes: Votes, toRemove: string): Votes {
15
- const votesCopy = { ...votes };
16
- delete votesCopy[toRemove];
17
- return votesCopy;
18
- }
19
-
20
- private processVotesAndCalculateTVLs (
21
- mSolVotes: Votes,
22
- veMndeVotes: Votes,
23
- stakesConfig: StakesConfig
24
- ): ProcessedVotesAndTVLs {
25
- const updatedVeMndeVotes = this.removeFromVotes({ ...veMndeVotes }, this.configService.getScoringConfig().DS_PUBKEY);
26
- const veMndeVotesForDS = veMndeVotes[this.configService.getScoringConfig().DS_PUBKEY] ?? 0;
27
- const totalVeMndeVotes = sum(Object.values(veMndeVotes));
28
- const veMndeTvl = Math.round(
29
- totalVeMndeVotes > 0
30
- ? (1 - veMndeVotesForDS / totalVeMndeVotes) * stakesConfig.veMndeControl * stakesConfig.tvl
31
- : 0
32
- );
33
-
34
- const updatedMSolVotes = this.removeFromVotes({ ...mSolVotes }, this.configService.getScoringConfig().DS_PUBKEY);
35
- const mSolVotesForDS = mSolVotes[this.configService.getScoringConfig().DS_PUBKEY] ?? 0;
36
- const totalMSolVotes = sum(Object.values(mSolVotes));
37
- const mSolTvl = Math.round(
38
- totalMSolVotes > 0
39
- ? (1 - mSolVotesForDS / totalMSolVotes) * stakesConfig.mSolControl * stakesConfig.tvl
40
- : 0
41
- );
42
-
43
- return {
44
- updatedMSolVotes,
45
- updatedVeMndeVotes,
46
- mSolTvl,
47
- veMndeTvl,
48
- totalTvl: stakesConfig.tvl
49
- };
50
- }
51
-
52
- public filterEligibleValidators (aggregatedValidators: AggregatedValidators, eligibilities: ValidatorsEligibilities): string[] {
53
- return Object.keys(aggregatedValidators).filter(voteAccount => eligibilities[voteAccount]?.basicEligibility);
54
- }
55
-
56
- private initStakes (eligibleValidators: string[]): Stakes {
57
- const result: Stakes = {};
58
- for (const voteAccount of eligibleValidators) {
59
- result[voteAccount] = {
60
- algoStake: 0,
61
- mSolStake: 0,
62
- veMndeStake: 0,
63
- totalStake: 0,
64
- algoStakeFromOverflow: 0,
65
- mSolStakeFromOverflow: 0,
66
- veMndeStakeFromOverflow: 0,
67
- totalStakeFromOverlfow: 0,
68
- };
69
- }
70
- return result;
71
- }
72
-
73
- private computeStakesForValidators (
74
- stakingVariables: StakingVariables,
75
- eligibleValidators: string[],
76
- scores: Scores,
77
- eligibilities: ValidatorsEligibilities,
78
- stakesConfig: StakesConfig,
79
- mSolVotes: Votes,
80
- veMndeVotes: Votes,
81
- totalMsolVotes: number,
82
- totalVeMndeVotes: number,
83
- mSolTvl: number,
84
- veMndeTvl: number,
85
- algoTvl: number,
86
- blockSize: number
87
- ): Stakes {
88
- let updatedTotalMSolVotes = totalMsolVotes;
89
- let updatedTotalVeMndeVotes = totalVeMndeVotes;
90
- let algoStakeDistributed = 0;
91
- let mSolStakeDistributed = 0;
92
- let veMndeStakeDistributed = 0;
93
- const stakes = this.initStakes(eligibleValidators);
94
-
95
- let round = 1;
96
- while (round <= 100 && (mSolTvl - mSolStakeDistributed > 1 || veMndeTvl - veMndeStakeDistributed > 1)) {
97
- let someStakeIncreased = false;
98
- const mSolStakeDistributedBeforeRound = mSolStakeDistributed;
99
- const veMndeStakeDistributedBeforeRound = veMndeStakeDistributed;
100
- for (const voteAccount of eligibleValidators) {
101
- if (!stakes[voteAccount] || !scores[voteAccount] || !eligibilities[voteAccount]) {
102
- continue;
103
- }
104
-
105
- let blocks = 0;
106
-
107
- if (round === 1) {
108
- if (eligibilities[voteAccount]!.bonusEligibility) {
109
- blocks += stakesConfig.stakeBlocksFromBonus;
110
- }
111
-
112
- blocks += Parser.evaluate(stakesConfig.formulaStakeBlocksFromScore, { ...stakingVariables, score: scores[voteAccount]!.score });
113
- }
114
-
115
- const algoStake = Math.round(Math.min(blockSize * blocks, Math.max(algoTvl - algoStakeDistributed, 0)));
116
- const mSolStake = updatedTotalMSolVotes > 0 ? Math.round((mSolVotes[voteAccount] ?? 0) / updatedTotalMSolVotes * Math.max(mSolTvl - mSolStakeDistributedBeforeRound, 0)) : 0;
117
- const veMndeStake = updatedTotalVeMndeVotes > 0 ? Math.round((veMndeVotes[voteAccount] ?? 0) / updatedTotalVeMndeVotes * Math.max(veMndeTvl - veMndeStakeDistributedBeforeRound, 0)) : 0;
118
- const stake = algoStake + veMndeStake + mSolStake;
119
-
120
- if (!stake) {
121
- continue;
122
- }
123
-
124
- const cappedStake = Math.min(eligibilities[voteAccount]!.capFromBond - stakes[voteAccount]!.totalStake, eligibilities[voteAccount]!.capFromExternalStake - stakes[voteAccount]!.totalStake, stake);
125
- const stakeOverflow = stake - cappedStake;
126
- const algoStakeOverflow = Math.round(algoStake / stake * stakeOverflow);
127
- const mSolStakeOverflow = Math.round(mSolStake / stake * stakeOverflow);
128
- const veMndeStakeOverflow = Math.round(veMndeStake / stake * stakeOverflow);
129
- const cappedAlgoStake = algoStake - algoStakeOverflow;
130
- const cappedMSolStake = mSolStake - mSolStakeOverflow;
131
- const cappedVeMndeStake = veMndeStake - veMndeStakeOverflow;
132
-
133
- if (cappedAlgoStake + cappedMSolStake + cappedVeMndeStake > 0) {
134
- someStakeIncreased = true;
135
- }
136
-
137
- algoStakeDistributed += cappedAlgoStake;
138
- mSolStakeDistributed += cappedMSolStake;
139
- veMndeStakeDistributed += cappedVeMndeStake;
140
-
141
- stakes[voteAccount]!.algoStake += cappedAlgoStake;
142
- stakes[voteAccount]!.mSolStake += cappedMSolStake;
143
- stakes[voteAccount]!.veMndeStake += cappedVeMndeStake;
144
- stakes[voteAccount]!.totalStake += cappedAlgoStake + cappedMSolStake + cappedVeMndeStake;
145
- if (round > 1) {
146
- stakes[voteAccount]!.algoStakeFromOverflow += cappedAlgoStake;
147
- stakes[voteAccount]!.mSolStakeFromOverflow += cappedMSolStake;
148
- stakes[voteAccount]!.veMndeStakeFromOverflow += cappedVeMndeStake;
149
- stakes[voteAccount]!.totalStakeFromOverlfow += cappedAlgoStake + cappedMSolStake + cappedVeMndeStake;
150
- }
151
- }
152
-
153
- round++;
154
-
155
- if (!someStakeIncreased) {
156
- break;
157
- }
158
-
159
- for (const voteAccount of eligibleValidators) {
160
- if (Math.round(stakes[voteAccount]!.totalStake) === Math.round(eligibilities[voteAccount]!.capFromExternalStake)) {
161
- veMndeVotes = this.removeFromVotes(veMndeVotes, voteAccount);
162
- mSolVotes = this.removeFromVotes(mSolVotes, voteAccount);
163
- }
164
- }
165
- updatedTotalVeMndeVotes = sum(Object.values(veMndeVotes));
166
- updatedTotalMSolVotes = sum(Object.values(mSolVotes));
167
- }
168
-
169
- const remainingAlgoTvl = Math.max(algoTvl - algoStakeDistributed, 0);
170
- const remainingMSolTvl = Math.max(mSolTvl - mSolStakeDistributed, 0);
171
- const remainingVeMndeTvl = Math.max(veMndeTvl - veMndeStakeDistributed, 0);
172
- for (const voteAccount of eligibleValidators) {
173
- if (!stakes[voteAccount]) {
174
- continue;
175
- }
176
- if (algoStakeDistributed) {
177
- const extraStake = Math.round((stakes[voteAccount]?.algoStake ?? 0) / algoStakeDistributed * remainingAlgoTvl);
178
- stakes[voteAccount]!.totalStake += extraStake;
179
- stakes[voteAccount]!.algoStake += extraStake;
180
- stakes[voteAccount]!.algoStakeFromOverflow += extraStake;
181
- }
182
- if (mSolStakeDistributed) {
183
- const extraStake = Math.round((stakes[voteAccount]?.algoStake ?? 0) / mSolStakeDistributed * remainingMSolTvl);
184
- stakes[voteAccount]!.totalStake += extraStake;
185
- stakes[voteAccount]!.mSolStake += extraStake;
186
- stakes[voteAccount]!.mSolStakeFromOverflow += extraStake;
187
- }
188
- if (veMndeStakeDistributed) {
189
- const extraStake = Math.round((stakes[voteAccount]?.algoStake ?? 0) / veMndeStakeDistributed * remainingVeMndeTvl);
190
- stakes[voteAccount]!.totalStake += extraStake;
191
- stakes[voteAccount]!.veMndeStake += extraStake;
192
- stakes[voteAccount]!.veMndeStakeFromOverflow += extraStake;
193
- }
194
- }
195
- return stakes;
196
- }
197
-
198
- computeValidatorsStakes (
199
- aggregatedValidators: AggregatedValidators,
200
- scores: Scores,
201
- eligibilities: ValidatorsEligibilities,
202
- stakesConfig: StakesConfig,
203
- mSolVotes: Votes,
204
- veMndeVotes: Votes
205
- ): Stakes {
206
- let updatedMSolVotes = { ...mSolVotes };
207
- let updatedVeMndeVotes = { ...veMndeVotes };
208
-
209
- const processedVotesAndTvls = this.processVotesAndCalculateTVLs(updatedMSolVotes, updatedVeMndeVotes, stakesConfig);
210
-
211
- updatedMSolVotes = processedVotesAndTvls.updatedMSolVotes;
212
- updatedVeMndeVotes = processedVotesAndTvls.updatedVeMndeVotes;
213
- const algoTvl = stakesConfig.tvl - processedVotesAndTvls.mSolTvl - processedVotesAndTvls.veMndeTvl;
214
-
215
- try {
216
- const eligibleValidators = this.filterEligibleValidators(aggregatedValidators, eligibilities);
217
- const stakingVariables: StakingVariables = { tvl: algoTvl };
218
- const blockSize = Parser.evaluate(stakesConfig.formulaStakeBlockSize, stakingVariables);
219
-
220
- Object.keys(aggregatedValidators).filter((v) => !eligibilities[v]?.basicEligibility).forEach((voteAccount) => {
221
- updatedVeMndeVotes = this.removeFromVotes(updatedVeMndeVotes, voteAccount);
222
- updatedMSolVotes = this.removeFromVotes(updatedMSolVotes, voteAccount);
223
- });
224
- const updatedTotalVeMndeVotes = sum(Object.values(updatedVeMndeVotes));
225
- const updatedTotalMSolVotes = sum(Object.values(updatedMSolVotes));
226
- eligibleValidators.sort((a, b) => {
227
- const scoreA = scores[a]?.score ?? 0;
228
- const scoreB = scores[b]?.score ?? 0;
229
- return scoreB - scoreA;
230
- });
231
-
232
- return this.computeStakesForValidators(stakingVariables, eligibleValidators, scores, eligibilities,
233
- stakesConfig, updatedMSolVotes, updatedVeMndeVotes,
234
- updatedTotalMSolVotes, updatedTotalVeMndeVotes,
235
- processedVotesAndTvls.mSolTvl, processedVotesAndTvls.veMndeTvl,
236
- algoTvl, blockSize);
237
-
238
- } catch (err) {
239
- return {};
240
- }
241
- }
242
- }
@@ -1,48 +0,0 @@
1
- import { Module } from '@nestjs/common';
2
- import { ValidatorsService } from './validators.service';
3
- import { ConfigService } from '../config/config.service';
4
- import { ApiDataProvider } from '../providers/api-data.provider';
5
- import { FileDataProvider } from '../providers/file-data.provider';
6
- import { ConfigModule } from '../config/config.module';
7
-
8
- @Module({
9
- imports: [ConfigModule],
10
- providers: [ValidatorsService, {
11
- provide: 'IDataProvider',
12
- useFactory: (configService: ConfigService) => {
13
- const dataSourceType = configService.getDataProviderMode();
14
- if (dataSourceType === 'api') {
15
- return new ApiDataProvider(
16
- {
17
- validatorsURL: configService.getValidatorsURL(),
18
- blacklistURL: configService.getBlacklistURL(),
19
- vemndeVotesURL: configService.getVemndeVotesURL(),
20
- msolVotesURL: configService.getMsolVotesURL(),
21
- rewardsURL: configService.getRewardsURL(),
22
- jitoMevURL: configService.getJitoMevURL(),
23
- bondsURL: configService.getBondsURL(),
24
- marinadeTvlURL: configService.getMarinadeTvlURL(),
25
- }
26
- );
27
- } else if (dataSourceType === 'file') {
28
- return new FileDataProvider(
29
- {
30
- validatorsPath: configService.getValidatorsPath(),
31
- blacklistPath: configService.getBlacklistPath(),
32
- vemndeVotesPath: configService.getVemndeVotesPath(),
33
- msolVotesPath: configService.getMsolVotesPath(),
34
- rewardsPath: configService.getRewardsPath(),
35
- jitoMevPath: configService.getJitoMevPath(),
36
- bondsPath: configService.getBondsPath(),
37
- marinadeTvlPath: configService.getMarinadeTvlPath(),
38
- }
39
- );
40
- } else {
41
- throw new Error('Invalid data provider type');
42
- }
43
- },
44
- inject: [ConfigService],
45
- }],
46
- exports: [ValidatorsService],
47
- })
48
- export class ValidatorsModule {}