Skip to content

Commit c3a270a

Browse files
committed
fix: correct MND rewards calculation
1 parent 7aa9247 commit c3a270a

File tree

3 files changed

+112
-23
lines changed

3 files changed

+112
-23
lines changed

app/node/[nodeAddr]/page.tsx

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ const fetchLicenseDetailsAndNodeAvailability = async (
7676
nodeResponse: types.OraclesAvailabilityResult & types.OraclesDefaultResult;
7777
}> => {
7878
let nodeAddress: types.EthAddress,
79-
totalAssignedAmount: bigint,
80-
totalClaimedAmount: bigint,
81-
lastClaimEpoch: bigint,
82-
assignTimestamp: bigint,
83-
lastClaimOracle: types.EthAddress,
79+
totalAssignedAmount: bigint,
80+
totalClaimedAmount: bigint,
81+
firstMiningEpoch: bigint | undefined,
82+
lastClaimEpoch: bigint,
83+
assignTimestamp: bigint,
84+
lastClaimOracle: types.EthAddress,
8485
isBanned: boolean,
8586
licenseId: bigint,
8687
licenseType: 'ND' | 'MND' | 'GND' | undefined,
@@ -89,12 +90,13 @@ const fetchLicenseDetailsAndNodeAvailability = async (
8990

9091
try {
9192
({
92-
nodeAddress,
93-
totalAssignedAmount,
94-
totalClaimedAmount,
95-
lastClaimEpoch,
96-
assignTimestamp,
97-
lastClaimOracle,
93+
nodeAddress,
94+
totalAssignedAmount,
95+
totalClaimedAmount,
96+
firstMiningEpoch,
97+
lastClaimEpoch,
98+
assignTimestamp,
99+
lastClaimOracle,
98100
isBanned,
99101
licenseId,
100102
licenseType,
@@ -112,12 +114,13 @@ const fetchLicenseDetailsAndNodeAvailability = async (
112114
}
113115

114116
const license: types.License = {
115-
nodeAddress,
116-
totalAssignedAmount,
117-
totalClaimedAmount,
118-
lastClaimEpoch,
119-
assignTimestamp,
120-
lastClaimOracle,
117+
nodeAddress,
118+
totalAssignedAmount,
119+
totalClaimedAmount,
120+
firstMiningEpoch,
121+
lastClaimEpoch,
122+
assignTimestamp,
123+
lastClaimOracle,
121124
isBanned,
122125
owner,
123126
r1PoaiRewards,

lib/api/blockchain.ts

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use server';
22

33
import { ERC20Abi } from '@/blockchain/ERC20';
4+
import { MNDContractAbi } from '@/blockchain/MNDContract';
45
import { NDContractAbi } from '@/blockchain/NDContract';
56
import { ReaderAbi } from '@/blockchain/Reader';
67
import config, { getCurrentEpoch, getEpochStartTimestamp, getNextEpochTimestamp } from '@/config';
@@ -66,10 +67,26 @@ export async function getNodeLicenseDetails(nodeAddress: types.EthAddress): Prom
6667
functionName: 'getNodeLicenseDetails',
6768
args: [nodeAddress],
6869
})
69-
.then((result) => ({
70-
...result,
71-
licenseType: [undefined, 'ND', 'MND', 'GND'][result.licenseType] as 'ND' | 'MND' | 'GND' | undefined,
72-
}));
70+
.then(async (result) => {
71+
const licenseType = [undefined, 'ND', 'MND', 'GND'][result.licenseType] as 'ND' | 'MND' | 'GND' | undefined;
72+
let firstMiningEpoch: bigint | undefined;
73+
if (licenseType === 'MND') {
74+
firstMiningEpoch = (
75+
await publicClient.readContract({
76+
address: config.mndContractAddress,
77+
abi: MNDContractAbi,
78+
functionName: 'licenses',
79+
args: [result.licenseId],
80+
})
81+
)[3];
82+
}
83+
84+
return {
85+
...result,
86+
licenseType,
87+
firstMiningEpoch,
88+
};
89+
});
7390
}
7491

7592
export async function getLicense(licenseType: 'ND' | 'MND' | 'GND', licenseId: number | string): Promise<types.License> {
@@ -109,7 +126,7 @@ export async function getLicense(licenseType: 'ND' | 'MND' | 'GND', licenseId: n
109126
functionName: 'getMndLicenseDetails',
110127
args: [BigInt(licenseId)],
111128
})
112-
.then((license) => {
129+
.then(async (license) => {
113130
const isLinked = !isZeroAddress(license.nodeAddress);
114131
const licenseType = [undefined, 'ND', 'MND', 'GND'][license.licenseType] as 'ND' | 'MND' | 'GND' | undefined;
115132
if (licenseType === undefined) {
@@ -118,11 +135,20 @@ export async function getLicense(licenseType: 'ND' | 'MND' | 'GND', licenseId: n
118135
if (licenseType !== 'MND' && licenseType !== 'GND') {
119136
throw new Error('Invalid license type');
120137
}
138+
const firstMiningEpoch = (
139+
await publicClient.readContract({
140+
address: config.mndContractAddress,
141+
abi: MNDContractAbi,
142+
functionName: 'licenses',
143+
args: [BigInt(licenseId)],
144+
})
145+
)[3];
121146

122147
return {
123148
...license,
124149
licenseType,
125150
isLinked,
151+
firstMiningEpoch,
126152
};
127153
});
128154
}
@@ -333,7 +359,7 @@ const getNdLicenseRewards = async (license: types.License, epochs: number[], epo
333359
};
334360

335361
const getMndLicenseRewards = async (license: types.License, epochs: number[], epochs_vals: number[]): Promise<bigint> => {
336-
return calculateLicenseRewards(license, epochs, epochs_vals, config.mndVestingEpochs, config.mndCliffEpochs);
362+
return calculateMndLicenseRewards(license, epochs, epochs_vals);
337363
};
338364

339365
const getGndLicenseRewards = async (license: types.License, epochs: number[], epochs_vals: number[]): Promise<bigint> => {
@@ -387,3 +413,62 @@ const calculateLicenseRewards = async (
387413
const maxRemainingClaimAmount = license.totalAssignedAmount - license.totalClaimedAmount;
388414
return rewards_amount < maxRemainingClaimAmount ? rewards_amount : maxRemainingClaimAmount;
389415
};
416+
417+
const calculateMndLicenseRewards = async (license: types.License, epochs: number[], epochs_vals: number[]): Promise<bigint> => {
418+
const currentEpoch = getCurrentEpoch();
419+
const firstMiningEpoch = license.firstMiningEpoch;
420+
421+
if (firstMiningEpoch === undefined) {
422+
throw new Error('First mining epoch is undefined for MND license');
423+
}
424+
425+
const firstEpochToClaim =
426+
Number(license.lastClaimEpoch) >= Number(firstMiningEpoch) ? Number(license.lastClaimEpoch) : Number(firstMiningEpoch);
427+
const epochsToClaim = currentEpoch - firstEpochToClaim;
428+
429+
if (currentEpoch < Number(firstMiningEpoch) || !epochsToClaim) {
430+
return 0n;
431+
}
432+
433+
if (epochs.length && epochs[0] < firstEpochToClaim) {
434+
const start = firstEpochToClaim - epochs[0];
435+
epochs = epochs.slice(start);
436+
epochs_vals = epochs_vals.slice(start);
437+
}
438+
439+
if (epochsToClaim !== epochs.length || epochsToClaim !== epochs_vals.length) {
440+
console.error(
441+
`Invalid epochs array length. Received ${epochs.length} epochs, but there are ${epochsToClaim} epochs to claim.`,
442+
);
443+
return 0n;
444+
}
445+
446+
let rewards_amount = 0n;
447+
const logisticPlateau = 300_505239501691000000n; // 300.50
448+
const licensePlateau = (license.totalAssignedAmount * BigInt(1e18)) / logisticPlateau;
449+
450+
for (let i = 0; i < epochsToClaim; i++) {
451+
const maxRewardsPerEpoch = calculateMndMaxEpochRelease(epochs[i], firstMiningEpoch, licensePlateau);
452+
rewards_amount += (maxRewardsPerEpoch * BigInt(epochs_vals[i])) / 255n;
453+
}
454+
455+
const maxRemainingClaimAmount = license.totalAssignedAmount - license.totalClaimedAmount;
456+
return rewards_amount > maxRemainingClaimAmount ? maxRemainingClaimAmount : rewards_amount;
457+
};
458+
459+
const calculateMndMaxEpochRelease = (epoch: number, firstMiningEpoch: bigint, licensePlateau: bigint): bigint => {
460+
let x = epoch - Number(firstMiningEpoch);
461+
if (x > config.mndVestingEpochs) {
462+
x = config.mndVestingEpochs;
463+
}
464+
const frac = logisticFraction(x);
465+
return (licensePlateau * BigInt(frac * 1e18)) / BigInt(1e18);
466+
};
467+
468+
const logisticFraction = (x: number): number => {
469+
const length = config.mndVestingEpochs;
470+
const k = 5.0;
471+
const midPrc = 0.7;
472+
const midpoint = length * midPrc;
473+
return 1.0 / (1.0 + Math.exp((-k * (x - midpoint)) / length));
474+
};

typedefs/blockchain.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type License = {
66
totalAssignedAmount: bigint;
77
totalClaimedAmount: bigint;
88
lastClaimEpoch: bigint;
9+
firstMiningEpoch?: bigint;
910
assignTimestamp: bigint;
1011
lastClaimOracle: EthAddress;
1112
isBanned: boolean;

0 commit comments

Comments
 (0)