@marinade.finance/scoring 1.0.0 → 1.0.2
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.
- package/dist/computing/cluster.d.ts +10 -0
- package/dist/computing/cluster.js +95 -0
- package/dist/computing/cluster.js.map +1 -0
- package/dist/computing/eligibility.d.ts +19 -0
- package/dist/computing/eligibility.js +163 -0
- package/dist/computing/eligibility.js.map +1 -0
- package/dist/computing/score.d.ts +20 -0
- package/dist/computing/score.js +174 -0
- package/dist/computing/score.js.map +1 -0
- package/dist/computing/stake.d.ts +26 -0
- package/dist/computing/stake.js +197 -0
- package/dist/computing/stake.js.map +1 -0
- package/dist/computing/validators.d.ts +16 -0
- package/dist/computing/validators.js +123 -0
- package/dist/computing/validators.js.map +1 -0
- package/dist/constants/marinade.json +38 -0
- package/dist/dto/bonds.dto.d.ts +12 -0
- package/dist/dto/bonds.dto.js +3 -0
- package/dist/dto/bonds.dto.js.map +1 -0
- package/dist/dto/cluster.dto.d.ts +12 -0
- package/dist/dto/cluster.dto.js +3 -0
- package/dist/dto/cluster.dto.js.map +1 -0
- package/{src/dto/eligibility.dto.ts → dist/dto/eligibility.dto.d.ts} +15 -20
- package/dist/dto/eligibility.dto.js +3 -0
- package/dist/dto/eligibility.dto.js.map +1 -0
- package/dist/dto/jito.dto.d.ts +9 -0
- package/dist/dto/jito.dto.js +10 -0
- package/dist/dto/jito.dto.js.map +1 -0
- package/dist/dto/marinade.dto.d.ts +18 -0
- package/dist/dto/marinade.dto.js +3 -0
- package/dist/dto/marinade.dto.js.map +1 -0
- package/dist/dto/rewards.dto.d.ts +17 -0
- package/dist/dto/rewards.dto.js +18 -0
- package/dist/dto/rewards.dto.js.map +1 -0
- package/dist/dto/scoring.dto.d.ts +101 -0
- package/dist/dto/scoring.dto.js +3 -0
- package/dist/dto/scoring.dto.js.map +1 -0
- package/dist/dto/snapshots.dto.d.ts +15 -0
- package/dist/dto/snapshots.dto.js +13 -0
- package/dist/dto/snapshots.dto.js.map +1 -0
- package/dist/dto/stakes.dto.d.ts +30 -0
- package/dist/dto/stakes.dto.js +3 -0
- package/dist/dto/stakes.dto.js.map +1 -0
- package/dist/dto/validators.dto.d.ts +110 -0
- package/dist/dto/validators.dto.js +19 -0
- package/dist/dto/validators.dto.js.map +1 -0
- package/dist/errors/fetching.d.ts +22 -0
- package/dist/errors/fetching.js +30 -0
- package/dist/errors/fetching.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/data-provider.interface.d.ts +16 -0
- package/dist/interfaces/data-provider.interface.js +3 -0
- package/dist/interfaces/data-provider.interface.js.map +1 -0
- package/dist/logging/logger.d.ts +3 -0
- package/dist/logging/logger.js +72 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/providers/api-data.provider.d.ts +30 -0
- package/dist/providers/api-data.provider.js +227 -0
- package/dist/providers/api-data.provider.js.map +1 -0
- package/dist/providers/file-data.provider.d.ts +29 -0
- package/dist/providers/file-data.provider.js +96 -0
- package/dist/providers/file-data.provider.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/csv.d.ts +2 -0
- package/dist/utils/csv.js +53 -0
- package/dist/utils/csv.js.map +1 -0
- package/dist/utils/maths.d.ts +8 -0
- package/dist/utils/maths.js +56 -0
- package/dist/utils/maths.js.map +1 -0
- package/dist/utils/solana.d.ts +1 -0
- package/dist/utils/solana.js +9 -0
- package/dist/utils/solana.js.map +1 -0
- package/dist/utils/zip.d.ts +2 -0
- package/dist/utils/zip.js +15 -0
- package/dist/utils/zip.js.map +1 -0
- package/package.json +14 -16
- package/src/cluster/cluster.module.ts +0 -8
- package/src/cluster/cluster.service.ts +0 -108
- package/src/config/config.module.ts +0 -8
- package/src/config/config.service.ts +0 -136
- package/src/constants/marinade.json +0 -38
- package/src/dto/bonds.dto.ts +0 -14
- package/src/dto/cluster.dto.ts +0 -13
- package/src/dto/jito.dto.ts +0 -10
- package/src/dto/marinade.dto.ts +0 -18
- package/src/dto/rewards.dto.ts +0 -21
- package/src/dto/scoring.dto.ts +0 -109
- package/src/dto/snapshots.dto.ts +0 -19
- package/src/dto/stakes.dto.ts +0 -35
- package/src/dto/validators.dto.ts +0 -146
- package/src/eligibility/eligibility.module.ts +0 -11
- package/src/eligibility/eligibility.service.ts +0 -183
- package/src/errors/fetching.ts +0 -25
- package/src/interfaces/data-provider.interface.ts +0 -17
- package/src/providers/api-data.provider.ts +0 -261
- package/src/providers/file-data.provider.ts +0 -125
- package/src/scoring/scoring.module.ts +0 -11
- package/src/scoring/scoring.service.ts +0 -184
- package/src/stake/stake.module.ts +0 -10
- package/src/stake/stake.service.ts +0 -242
- package/src/utils/csv.ts +0 -30
- package/src/utils/maths.ts +0 -75
- package/src/utils/solana.ts +0 -9
- package/src/utils/zip.ts +0 -12
- package/src/validators/validators.module.ts +0 -48
- package/src/validators/validators.service.ts +0 -177
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
3
|
-
import { TvlStats } from '../dto/marinade.dto';
|
|
4
|
-
import { Bonds, BondsDto } from '../dto/bonds.dto';
|
|
5
|
-
import { JitoValidatorDto, JitoValidatorsResponseDto } from '../dto/jito.dto';
|
|
6
|
-
import { Rewards, RewardsResponseDto } from '../dto/rewards.dto';
|
|
7
|
-
import { EpochStatDto, ValidatorDto, ValidatorsResponseDto } from '../dto/validators.dto';
|
|
8
|
-
import { IDataProvider } from '../interfaces/data-provider.interface';
|
|
9
|
-
import { readFile } from 'fs/promises';
|
|
10
|
-
import { FETCHING_ERROR_MESSAGES } from '../errors/fetching';
|
|
11
|
-
import { sum } from '../utils/maths';
|
|
12
|
-
import { Votes, mSolSnapshotDto } from '../dto/snapshots.dto';
|
|
13
|
-
|
|
14
|
-
class Paths {
|
|
15
|
-
validatorsPath: string;
|
|
16
|
-
blacklistPath: string;
|
|
17
|
-
vemndeVotesPath: string;
|
|
18
|
-
msolVotesPath: string;
|
|
19
|
-
rewardsPath: string;
|
|
20
|
-
jitoMevPath: string;
|
|
21
|
-
bondsPath: string;
|
|
22
|
-
marinadeTvlPath: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class FileDataProvider implements IDataProvider {
|
|
26
|
-
constructor (private paths: Paths) { }
|
|
27
|
-
|
|
28
|
-
async fetchValidators (epochsToFetch: number): Promise<ValidatorDto[]> {
|
|
29
|
-
try {
|
|
30
|
-
const validatorsFile = await readFile(this.paths.validatorsPath, 'utf8');
|
|
31
|
-
const validatorsData: ValidatorsResponseDto = JSON.parse(validatorsFile);
|
|
32
|
-
|
|
33
|
-
validatorsData.validators.forEach(validator => {
|
|
34
|
-
validator.epochStats = validator.epoch_stats.reduce((acc, epochStat) => {
|
|
35
|
-
acc[epochStat.epoch] = epochStat;
|
|
36
|
-
return acc;
|
|
37
|
-
}, {} as Record<number, EpochStatDto>);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
return validatorsData.validators;
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error(FETCHING_ERROR_MESSAGES.generalValidatorsFetchFailed, error);
|
|
43
|
-
throw error;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async fetchTvl (withSnapshot: boolean): Promise<TvlStats> {
|
|
48
|
-
const tvlFile = await readFile(this.paths.marinadeTvlPath, 'utf8');
|
|
49
|
-
const tvlData: TvlStats = JSON.parse(tvlFile);
|
|
50
|
-
|
|
51
|
-
return tvlData;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async fetchBlacklist (): Promise<Set<string>> {
|
|
55
|
-
const blacklistFile = await readFile(this.paths.blacklistPath, 'utf8');
|
|
56
|
-
const blacklistSet = new Set(blacklistFile.split('\n').map((line) => line.split(',')[0]).filter((value): value is string => !!value));
|
|
57
|
-
return blacklistSet;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async fetchBonds (): Promise<Bonds> {
|
|
61
|
-
const bondsFile = await readFile(this.paths.bondsPath, 'utf8');
|
|
62
|
-
const bondsData: BondsDto = JSON.parse(bondsFile);
|
|
63
|
-
|
|
64
|
-
const bonds: Bonds = bondsData.bonds.reduce<Bonds>((acc, bond) => {
|
|
65
|
-
acc[bond.vote_account] = bond.cpmpe;
|
|
66
|
-
return acc;
|
|
67
|
-
}, {} as Bonds);
|
|
68
|
-
|
|
69
|
-
return bonds;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async fetchVeMndeVotes (): Promise<Record<string, number>> {
|
|
73
|
-
const vemndeVotesFile = await readFile(this.paths.vemndeVotesPath, 'utf8');
|
|
74
|
-
const vemndeVotesData = JSON.parse(vemndeVotesFile) as mSolSnapshotDto;
|
|
75
|
-
const result: Votes = {};
|
|
76
|
-
for (const { amount, validatorVoteAccount } of vemndeVotesData.records) {
|
|
77
|
-
const parsedAmount = Number(amount);
|
|
78
|
-
if (!isNaN(parsedAmount) && parsedAmount > 0) {
|
|
79
|
-
result[validatorVoteAccount] = (result[validatorVoteAccount] ?? 0) + parsedAmount;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async fetchMSolVotes (): Promise<Record<string, number>> {
|
|
87
|
-
const msolVotesFile = await readFile(this.paths.msolVotesPath, 'utf8');
|
|
88
|
-
const msolVotesData = JSON.parse(msolVotesFile) as mSolSnapshotDto;
|
|
89
|
-
const result: Votes = {};
|
|
90
|
-
for (const { amount, validatorVoteAccount } of msolVotesData.records) {
|
|
91
|
-
const parsedAmount = Number(amount);
|
|
92
|
-
if (!isNaN(parsedAmount) && parsedAmount > 0) {
|
|
93
|
-
result[validatorVoteAccount] = (result[validatorVoteAccount] ?? 0) + parsedAmount;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return result;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async fetchRewards (epochs: number): Promise<Rewards> {
|
|
101
|
-
const rewardsFile = await readFile(this.paths.rewardsPath, 'utf8');
|
|
102
|
-
const rewardsData = JSON.parse(rewardsFile) as { rewards_mev: [number, number][], rewards_inflation_est: [number, number][] };
|
|
103
|
-
|
|
104
|
-
const data = new RewardsResponseDto(rewardsData);
|
|
105
|
-
|
|
106
|
-
const mev = sum(data.rewards_mev.map(dto => dto.amount));
|
|
107
|
-
const inflation = sum(data.rewards_inflation_est.map(dto => dto.amount));
|
|
108
|
-
|
|
109
|
-
return { inflation, mev };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async fetchValidatorsJitoMEV (): Promise<Record<string, JitoValidatorDto>> {
|
|
113
|
-
try {
|
|
114
|
-
const jitoMevFile = await readFile(this.paths.jitoMevPath, 'utf8');
|
|
115
|
-
const jitoMevData: JitoValidatorsResponseDto = JSON.parse(jitoMevFile);
|
|
116
|
-
|
|
117
|
-
return Object.fromEntries(
|
|
118
|
-
jitoMevData.validators.map((validator) => [validator.vote_account, validator])
|
|
119
|
-
);
|
|
120
|
-
} catch (error) {
|
|
121
|
-
console.error(FETCHING_ERROR_MESSAGES.generalJitoMEVFetchFailed, error);
|
|
122
|
-
throw error;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
@@ -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
|
-
}
|
package/src/utils/csv.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
-
/* eslint-disable no-console */
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import { Score } from '../dto/scoring.dto';
|
|
5
|
-
|
|
6
|
-
export function writeScoresToCsv (filePath: string, data: Score[]): void {
|
|
7
|
-
if (data.length === 0) {
|
|
8
|
-
console.error('Data array is empty, no CSV will be written.');
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
const headers = Object.keys(data[0]!);
|
|
12
|
-
let csvString = headers.join(',') + '\n';
|
|
13
|
-
|
|
14
|
-
data.forEach(score => {
|
|
15
|
-
const rowData = headers.map(header => {
|
|
16
|
-
const value = score[header as keyof Score];
|
|
17
|
-
if (typeof value === 'string' && (value.includes(',') || value.includes('\n') || value.includes('"'))) {
|
|
18
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
19
|
-
}
|
|
20
|
-
return `"${value}"`;
|
|
21
|
-
}).join(',');
|
|
22
|
-
csvString += rowData + '\n';
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
fs.writeFile(filePath, csvString, (err) => {
|
|
26
|
-
if (err) {
|
|
27
|
-
console.error('Error writing CSV file:', err);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
}
|
package/src/utils/maths.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { Value } from 'expr-eval';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Calculates the sum of an array of numbers.
|
|
5
|
-
* @param {number[]} amounts - The array of numbers to sum.
|
|
6
|
-
* @return {number} The sum of the numbers in the array.
|
|
7
|
-
*/
|
|
8
|
-
export function sum (amounts: number[]): number {
|
|
9
|
-
if (!Array.isArray(amounts) || amounts.some(amount => typeof amount !== 'number')) {
|
|
10
|
-
throw new TypeError('Input must be an array of numbers');
|
|
11
|
-
}
|
|
12
|
-
return amounts.reduce((acc, amount) => acc + amount, 0);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Calculates the mean (average) of an array of numbers.
|
|
17
|
-
* @param {number[]} data - The array of numbers for which to calculate the mean.
|
|
18
|
-
* @return {number} The mean of the numbers in the array. Throws an error if the array is empty.
|
|
19
|
-
*/
|
|
20
|
-
export function mean (data: number[]): number {
|
|
21
|
-
if (data.length === 0) {
|
|
22
|
-
throw new Error('Cannot calculate mean for empty data');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return sum(data) / data.length;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Calculates the standard deviation of an array of numbers.
|
|
30
|
-
* Standard deviation is a measure of the amount of variation or dispersion of a set of values.
|
|
31
|
-
* @param {number[]} data - The array of numbers for which to calculate the standard deviation.
|
|
32
|
-
* @return {number} The standard deviation of the numbers in the array. Throws an error if the array is empty.
|
|
33
|
-
*/
|
|
34
|
-
export function stdDev (data: number[]): number {
|
|
35
|
-
if (data.length === 0) {
|
|
36
|
-
throw new Error('Cannot calculate std. dev. for empty data');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const dataMean = mean(data);
|
|
40
|
-
return Math.sqrt(data.reduce((sum, value) => sum + Math.pow(value - dataMean, 2), 0) / data.length);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function piecewise (...args: Value[]): Value {
|
|
44
|
-
if (args.length === 0) {
|
|
45
|
-
throw new Error('At least one argument must be provided!');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
for (let conditionIndex = 0, valueIndex = 1; valueIndex < args.length; conditionIndex += 2, valueIndex += 2) {
|
|
49
|
-
const condition = args[conditionIndex];
|
|
50
|
-
const value = args[valueIndex];
|
|
51
|
-
|
|
52
|
-
if (typeof condition !== 'boolean') {
|
|
53
|
-
throw new Error(`Argument at position ${conditionIndex} must be a boolean!`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (condition) {
|
|
57
|
-
if (value === undefined) {
|
|
58
|
-
throw new Error(`Expected a value corresponding to the condition at position ${conditionIndex}, but got undefined.`);
|
|
59
|
-
}
|
|
60
|
-
return value;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const defaultValue = args[args.length - 1];
|
|
65
|
-
if (defaultValue === undefined) {
|
|
66
|
-
throw new Error('Unexpected error: Default value is undefined.');
|
|
67
|
-
}
|
|
68
|
-
return defaultValue;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function mathUtilityFunctions () {
|
|
72
|
-
return {
|
|
73
|
-
piecewise
|
|
74
|
-
};
|
|
75
|
-
}
|