Skip to content

Commit

Permalink
Merge pull request #84 from kryzasada/master
Browse files Browse the repository at this point in the history
Update to v14
  • Loading branch information
Marcin-Radecki authored Dec 6, 2024
2 parents 764e12f + 658dd4f commit f370745
Show file tree
Hide file tree
Showing 29 changed files with 895 additions and 428 deletions.
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@
"@polkadot-cloud/core": "^0.1.20",
"@polkadot-cloud/react": "^0.1.39",
"@polkadot-cloud/utils": "^0.0.11",
"@polkadot/api": "^10.9.1",
"@polkadot/keyring": "^12.1.1",
"@polkadot/rpc-provider": "^10.9.1",
"@polkadot/util": "^12.4.2",
"@polkadot/util-crypto": "12.4.2",
"@polkadot/api": "^14.2.1",
"@polkadot/keyring": "^13.2.2",
"@polkadot/rpc-provider": "^14.2.1",
"@polkadot/types": "^15.0.1",
"@polkadot/util": "^13.2.2",
"@polkadot/util-crypto": "13.2.2",
"@substrate/connect": "^0.7.31",
"@zondax/ledger-substrate": "^0.41.2",
"bignumber.js": "^9.1.2",
Expand Down
6 changes: 6 additions & 0 deletions src/config/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ReactComponent as AzeroIconSVG } from 'img/a0_icon.svg';
import { ReactComponent as AzeroInlineSVG } from 'img/a0_inline.svg';
import { ReactComponent as AzeroLogoSVG } from 'img/a0_logo.svg';
import type { Networks } from 'types';
import BigNumber from 'bignumber.js';

export const NetworkList: Networks = {};

Expand Down Expand Up @@ -63,6 +64,7 @@ if (import.meta.env.VITE_DISABLE_MAINNET !== '1') {
stakeTarget: 0.5,
},
defaultFeeReserve: 0.1,
maxExposurePageSize: new BigNumber(1024),
} as const;

NetworkList[alephZero.name] = alephZero;
Expand Down Expand Up @@ -122,6 +124,7 @@ if (import.meta.env.VITE_DISABLE_TESTNET !== '1') {
stakeTarget: 0.5,
},
defaultFeeReserve: 0.1,
maxExposurePageSize: new BigNumber(1024),
} as const;

NetworkList[alephZeroTestnet.name] = alephZeroTestnet;
Expand Down Expand Up @@ -180,6 +183,7 @@ if (import.meta.env.VITE_ENABLE_CUSTOM_NETWORK === '1') {
stakeTarget: 0.5,
},
defaultFeeReserve: 0.1,
maxExposurePageSize: new BigNumber(1024),
} as const;

NetworkList[azeroCustom.name] = azeroCustom;
Expand Down Expand Up @@ -239,6 +243,7 @@ if (import.meta.env.VITE_DISABLE_DEVNET !== '1') {
stakeTarget: 0.5,
},
defaultFeeReserve: 0.1,
maxExposurePageSize: new BigNumber(1024),
} as const;

NetworkList[azeroDevnet.name] = azeroDevnet;
Expand Down Expand Up @@ -298,6 +303,7 @@ if (import.meta.env.MODE === 'development') {
stakeTarget: 0.5,
},
defaultFeeReserve: 0.1,
maxExposurePageSize: new BigNumber(1024),
} as const;

NetworkList[azeroLocal.name] = azeroLocal;
Expand Down
1 change: 1 addition & 0 deletions src/contexts/Api/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const consts: APIConstants = {
bondDuration: new BigNumber(0),
maxNominations: new BigNumber(0),
sessionsPerEra: new BigNumber(0),
maxExposurePageSize: new BigNumber(0),
maxNominatorRewardedPerValidator: new BigNumber(0),
historyDepth: new BigNumber(0),
maxElectingVoters: new BigNumber(0),
Expand Down
6 changes: 6 additions & 0 deletions src/contexts/Api/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export const APIProvider = ({ children }: { children: React.ReactNode }) => {
newApi.consts.staking.historyDepth,
null, // newApi.consts.fastUnstake.deposit, // we don't use fastUnstake
newApi.consts.nominationPools.palletId,
newApi.consts.staking.maxExposurePageSize,
]);

// format constants.
Expand Down Expand Up @@ -274,6 +275,10 @@ export const APIProvider = ({ children }: { children: React.ReactNode }) => {

const expectedEraTime = FallbackExpectedEraTime;

const maxExposurePageSize = result[11]
? new BigNumber(result[11].toString())
: NetworkList[network.name].maxExposurePageSize;

setConsts({
chainDecimals: newApi.registry.chainDecimals[0],
bondDuration,
Expand All @@ -288,6 +293,7 @@ export const APIProvider = ({ children }: { children: React.ReactNode }) => {
poolsPalletId,
existentialDeposit,
fastUnstakeDeposit,
maxExposurePageSize,
});
setApi(newApi);
};
Expand Down
1 change: 1 addition & 0 deletions src/contexts/Api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface APIConstants {
bondDuration: BigNumber;
maxNominations: BigNumber;
sessionsPerEra: BigNumber;
maxExposurePageSize: BigNumber;
maxNominatorRewardedPerValidator: BigNumber;
historyDepth: BigNumber;
maxElectingVoters: BigNumber;
Expand Down
3 changes: 2 additions & 1 deletion src/contexts/FastUnstake/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const FastUnstakeProvider = ({
const { metrics, activeEra } = useNetworkMetrics();
const { getNominationStatus } = useNominationStatus();
const { fastUnstakeErasToCheckPerBlock } = metrics;
const { bondDuration } = consts;
const { bondDuration, maxExposurePageSize } = consts;
const { nominees } = getNominationStatus(activeAccount, 'nominator');

// store whether a fast unstake check is in progress.
Expand Down Expand Up @@ -242,6 +242,7 @@ export const FastUnstakeProvider = ({
who: activeAccount,
networkName: network.name,
exitOnExposed: true,
maxExposurePageSize: maxExposurePageSize.toString(),
exposures,
});
};
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/Payouts/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const setLocalUnclaimedPayouts = (
network: NetworkName,
era: string,
who: string,
unclaimdPayouts: Record<string, string>,
unclaimdPayouts: Record<string, [number, string]>,
endEra: string
) => {
const current = JSON.parse(
Expand Down
131 changes: 107 additions & 24 deletions src/contexts/Payouts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ export const PayoutsProvider = ({
}: {
children: React.ReactNode;
}) => {
const { api, network } = useApi();
const { api, consts, network } = useApi();
const { activeAccount } = useConnect();
const { activeEra } = useNetworkMetrics();
const { isNominating, fetchEraStakers } = useStaking();
const { maxExposurePageSize } = consts;

// Store active accont's payout state.
const [unclaimedPayouts, setUnclaimedPayouts] =
Expand Down Expand Up @@ -87,6 +88,7 @@ export const PayoutsProvider = ({
era: String(era),
who: activeAccount,
networkName: network.name,
maxExposurePageSize: maxExposurePageSize.toString(),
exposures,
});
}
Expand Down Expand Up @@ -151,8 +153,8 @@ export const PayoutsProvider = ({
const exposedEras: string[] = [];
for (const era of erasToCheck)
if (
Object.values(
Object.keys(getLocalEraExposure(network.name, era, activeAccount))
Object.keys(
getLocalEraExposure(network.name, era, activeAccount)
)?.[0] === validator
)
exposedEras.push(era);
Expand All @@ -165,49 +167,126 @@ export const PayoutsProvider = ({
const validatorControllers: Record<string, string> = {};
for (let i = 0; i < bondedResults.length; i++) {
const ctlr = bondedResults[i].unwrapOr(null);
if (ctlr) validatorControllers[uniqueValidators[i]] = ctlr;
if (ctlr) {
validatorControllers[uniqueValidators[i]] = ctlr;
}
}

// Fetch ledgers to determine which eras have not yet been claimed per validator. Only includes
// eras that are in `erasToCheck`.
// Start old unclaimed payouts logic
const ledgerResults = await api.query.staking.ledger.multi<AnyApi>(
Object.values(validatorControllers)
);
const unclaimedRewards: Record<string, string[]> = {};

const oldClaimedRewards: Record<string, string[]> = {};
for (const ledgerResult of ledgerResults) {
const ledger = ledgerResult.unwrapOr(null)?.toHuman();
if (ledger) {
if (ledger && ledger.legacyClaimedRewards.length > 0) {
// get claimed eras within `erasToCheck`.
const erasClaimed = ledger.claimedRewards
const erasClaimed = ledger.legacyClaimedRewards
.map((e: string) => rmCommas(e))
.filter(
(e: string) =>
new BigNumber(e).isLessThanOrEqualTo(startEra) &&
new BigNumber(e).isGreaterThanOrEqualTo(endEra)
);

// filter eras yet to be claimed
unclaimedRewards[ledger.stash] = erasToCheck
oldClaimedRewards[ledger.stash] = erasToCheck
.map((e) => e.toString())
.filter((r: string) => validatorExposedEras(ledger.stash).includes(r))
.filter((r: string) => !erasClaimed.includes(r));
.filter((r: string) => erasClaimed.includes(r));
}
}

// Reformat old unclaimed rewards to match new format.
const oldClaimedByEra: Record<string, string[]> = {};
erasToCheck.forEach((era) => {
const eraValidators: string[] = [];
Object.entries(oldClaimedRewards).forEach(([validator, eras]) => {
if (eras.includes(era)) {
eraValidators.push(validator);
}
});
if (eraValidators.length > 0) {
oldClaimedByEra[era] = eraValidators;
}
});

const unclaimedRewards: Record<string, string[]> = {};

// Accumulate calls to fetch unclaimed rewards for each era for all validators.
const unclaimedRewardsEntries = erasToCheck
.map((era) => uniqueValidators.map((v) => [era, v]))
.flat();

const results = await Promise.all(
unclaimedRewardsEntries.map(([era, v]) =>
api.query.staking.claimedRewards<AnyApi>(era, v)
)
);

for (let i = 0; i < results.length; i++) {
const pages = results[i].toHuman() || [];
const era = unclaimedRewardsEntries[i][0];
const validator = unclaimedRewardsEntries[i][1];
const exposure = getLocalEraExposure(network.name, era, activeAccount);
const exposedPage =
exposure?.[validator]?.exposedPage !== undefined
? String(exposure[validator].exposedPage)
: '0';

// Add to `unclaimedRewards` if payout page has not yet been claimed.
if (!pages.includes(exposedPage)) {
if (unclaimedRewards?.[validator]) {
unclaimedRewards[validator].push(era);
} else {
unclaimedRewards[validator] = [era];
}
}
}

// Reformat unclaimed rewards to be { era: validators[] }.
const unclaimedByEra: Record<string, string[]> = {};
let unclaimedRewardsByEra: Record<string, string[]> = {};
erasToCheck.forEach((era) => {
const eraValidators: string[] = [];
Object.entries(unclaimedRewards).forEach(([validator, eras]) => {
if (eras.includes(era)) eraValidators.push(validator);
if (eras.includes(era)) {
eraValidators.push(validator);
}
});
if (eraValidators.length > 0) unclaimedByEra[era] = eraValidators;
if (eraValidators.length > 0) {
unclaimedRewardsByEra[era] = eraValidators;
}
});

function mergeClaimedData(
unclaimedByEra: Record<string, string[]>,
claimedByEra: Record<string, string[]>
): Record<string, string[]> {
const result = { ...unclaimedByEra };

for (const era in claimedByEra) {
if (era in result) {
result[era] = result[era].filter(
(validator) => !claimedByEra[era].includes(validator)
);

if (result[era].length === 0) {
delete result[era];
}
}
}

return result;
}
unclaimedRewardsByEra = mergeClaimedData(
unclaimedRewardsByEra,
oldClaimedByEra
);

// Accumulate calls needed to fetch data to calculate rewards.
const calls: AnyApi[] = [];
Object.entries(unclaimedByEra).forEach(([era, validators]) => {
if (validators.length > 0)
Object.entries(unclaimedRewardsByEra).forEach(([era, validators]) => {
if (validators.length > 0) {
calls.push(
Promise.all([
api.query.staking.erasValidatorReward<AnyApi>(era),
Expand All @@ -217,16 +296,17 @@ export const PayoutsProvider = ({
),
])
);
}
});

// Iterate calls and determine unclaimed payouts.
const unclaimed: UnclaimedPayouts = {};
let i = 0;
for (const [reward, points, ...prefs] of await Promise.all(calls)) {
const era = Object.keys(unclaimedByEra)[i];
const era = Object.keys(unclaimedRewardsByEra)[i];
const eraTotalPayout = new BigNumber(rmCommas(reward.toHuman()));
const eraRewardPoints = points.toHuman();
const unclaimedValidators = unclaimedByEra[era];
const unclaimedValidators = unclaimedRewardsByEra[era];

let j = 0;
for (const pref of prefs) {
Expand All @@ -247,6 +327,7 @@ export const PayoutsProvider = ({
const staked = new BigNumber(localExposed?.staked || '0');
const total = new BigNumber(localExposed?.total || '0');
const isValidator = localExposed?.isValidator || false;
const exposedPage = localExposed?.exposedPage || 0;

// Calculate the validator's share of total era payout.
const totalRewardPoints = new BigNumber(
Expand All @@ -269,11 +350,13 @@ export const PayoutsProvider = ({
.dividedBy(total)
.plus(isValidator ? valCut : 0);

unclaimed[era] = {
...unclaimed[era],
[validator]: unclaimedPayout.toString(),
};
j++;
if (!unclaimedPayout.isZero()) {
unclaimed[era] = {
...unclaimed[era],
[validator]: [exposedPage, unclaimedPayout.toString()],
};
j++;
}
}

// This is not currently useful for preventing re-syncing. Need to know the eras that have
Expand Down
3 changes: 2 additions & 1 deletion src/contexts/Payouts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ export type PayoutsContextInterface = {

export type UnclaimedPayouts = Record<string, EraUnclaimedPayouts> | null;

export type EraUnclaimedPayouts = Record<string, string>;
export type EraUnclaimedPayouts = Record<string, [number, string]>;

export interface LocalValidatorExposure {
staked: string;
total: string;
share: string;
isValidator: boolean;
exposedPage: number;
}
Loading

0 comments on commit f370745

Please sign in to comment.