@substrate/api-sidecar
Version:
REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.
705 lines • 35.5 kB
JavaScript
;
/* eslint-disable @typescript-eslint/require-await */
// Copyright 2017-2025 Parity Technologies (UK) Ltd.
// This file is part of Substrate API Sidecar.
//
// Substrate API Sidecar is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http_errors_1 = require("http-errors");
const apiRegistry_1 = require("../../apiRegistry");
const sanitizeNumbers_1 = require("../../sanitize/sanitizeNumbers");
const registries_1 = require("../../test-helpers/registries");
const mock_1 = require("../test-helpers/mock");
const validators789629Hex_1 = require("../test-helpers/mock/data/validators789629Hex");
const stakingProgress789629_json_1 = __importDefault(require("../test-helpers/responses/pallets/stakingProgress789629.json"));
const stakingProgressPostAhm_json_1 = __importDefault(require("../test-helpers/responses/pallets/stakingProgressPostAhm.json"));
const stakingProgressUnappliedSlashes_json_1 = __importDefault(require("../test-helpers/responses/pallets/stakingProgressUnappliedSlashes.json"));
const stakingProgressUnappliedSlashesPostAHM_json_1 = __importDefault(require("../test-helpers/responses/pallets/stakingProgressUnappliedSlashesPostAHM.json"));
const PalletsStakingProgressService_1 = require("./PalletsStakingProgressService");
const epochIndexAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('u64', 330));
const genesisSlotAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('u64', 265084563));
const currentSlotAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('u64', 265876724));
const currentIndexAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('SessionIndex', 330));
const timestampNowAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('u64', 1703123456789));
const eraElectionStatusAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('ElectionStatus', { Close: null }));
const validatorsAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Vec<ValidatorId>', validators789629Hex_1.validators789629Hex));
const forceEraAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Forcing', 'NotForcing'));
const unappliedSlashesEntries = () => {
return Promise.resolve([['5640', []]]);
};
const validatorCountAt = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('u32', 197));
const mockHistoricApi = {
...mock_1.defaultMockApi,
consts: {
babe: {
epochDuration: registries_1.polkadotRegistry.createType('u64', 2400),
},
staking: {
electionLookAhead: registries_1.polkadotRegistry.createType('BlockNumber'),
sessionsPerEra: registries_1.polkadotRegistry.createType('SessionIndex', 6),
},
},
query: {
babe: {
currentSlot: currentSlotAt,
epochIndex: epochIndexAt,
genesisSlot: genesisSlotAt,
},
session: {
currentIndex: currentIndexAt,
validators: validatorsAt,
},
timestamp: {
now: timestampNowAt,
},
staking: {
activeEra: mock_1.activeEraAt,
eraElectionStatus: eraElectionStatusAt,
erasStartSessionIndex: mock_1.erasStartSessionIndexAt,
forceEra: forceEraAt,
bondedEras: () => Promise.resolve([
[40, 276],
[41, 282],
[42, 288],
[43, 294],
[44, 300],
[45, 306],
[46, 312],
[47, 318],
[48, 324],
[49, 330],
].map((el) => [registries_1.polkadotRegistry.createType('u32', el[0]), registries_1.polkadotRegistry.createType('u32', el[1])])),
unappliedSlashes: {
entries: unappliedSlashesEntries,
},
validatorCount: validatorCountAt,
},
},
};
const mockApi = {
...mock_1.defaultMockApi,
at: (_hash) => mockHistoricApi,
};
const unappliedSlashes = [
{
validator: '5CD2Q2EnKaKvjWza3ufMxaXizBTTDgm9kPB3DCZ4VA9j7Ud6',
own: '0',
others: [['5GxDBrTuFgCAN49xrpRFWJiA969R2Ny5NnTa8cSPBh8hWHY9', '6902377436592']],
reporters: [],
payout: '345118871829',
toJSON: function () {
return {
validator: this.validator,
own: this.own,
others: this.others.map(([account, amount]) => ({ account, amount })),
reporters: this.reporters,
payout: this.payout,
};
},
},
];
const unappliedSlashesEntriesUnappliedSlashes = () => {
return Promise.resolve([['5640', unappliedSlashes]]);
};
const mockHistoricApiUnappliedSlashes = {
...mock_1.defaultMockApi,
consts: {
babe: {
epochDuration: registries_1.polkadotRegistry.createType('u64', 2400),
},
staking: {
electionLookAhead: registries_1.polkadotRegistry.createType('BlockNumber'),
sessionsPerEra: registries_1.polkadotRegistry.createType('SessionIndex', 6),
},
},
query: {
babe: {
currentSlot: currentSlotAt,
epochIndex: epochIndexAt,
genesisSlot: genesisSlotAt,
},
session: {
currentIndex: currentIndexAt,
validators: validatorsAt,
},
timestamp: {
now: timestampNowAt,
},
staking: {
activeEra: mock_1.activeEraAt,
eraElectionStatus: eraElectionStatusAt,
erasStartSessionIndex: mock_1.erasStartSessionIndexAt,
forceEra: forceEraAt,
bondedEras: () => Promise.resolve([
[40, 276],
[41, 282],
[42, 288],
[43, 294],
[44, 300],
[45, 306],
[46, 312],
[47, 318],
[48, 324],
[49, 330],
].map((el) => [registries_1.polkadotRegistry.createType('u32', el[0]), registries_1.polkadotRegistry.createType('u32', el[1])])),
unappliedSlashes: {
entries: unappliedSlashesEntriesUnappliedSlashes,
},
validatorCount: validatorCountAt,
},
},
};
const mockApiUnappliedSlashes = {
...{
...mockHistoricApiUnappliedSlashes,
query: {
...mockHistoricApiUnappliedSlashes.query,
staking: {
...mockHistoricApiUnappliedSlashes.query.staking,
unappliedSlashes: {
entries: unappliedSlashesEntriesUnappliedSlashes,
},
},
},
},
at: (_hash) => ({
...mockHistoricApiUnappliedSlashes,
query: {
...mockHistoricApiUnappliedSlashes.query,
staking: {
...mockHistoricApiUnappliedSlashes.query.staking,
unappliedSlashes: {
entries: unappliedSlashesEntriesUnappliedSlashes,
},
},
},
}),
};
const mockRCNextApi = {
...mock_1.defaultMockApi,
consts: {
...mock_1.defaultMockApi.consts,
babe: {
epochDuration: registries_1.polkadotRegistry.createType('u64', 2400),
},
},
query: {
...mock_1.defaultMockApi.query,
babe: {
currentSlot: currentSlotAt,
epochIndex: epochIndexAt,
genesisSlot: genesisSlotAt,
skippedEpochs: () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Vec<(u64, u32)>', [])),
},
staking: undefined,
session: {
currentIndex: currentIndexAt,
validators: validatorsAt,
},
},
at: (_hash) => ({
...mockHistoricApi,
consts: {
...mockHistoricApi.consts,
babe: {
epochDuration: registries_1.polkadotRegistry.createType('u64', 2400),
},
},
query: {
...mockHistoricApi.query,
babe: {
currentSlot: currentSlotAt,
epochIndex: epochIndexAt,
genesisSlot: genesisSlotAt,
skippedEpochs: () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Vec<(u64, u32)>', [])),
},
staking: undefined,
session: {
currentIndex: currentIndexAt,
validators: validatorsAt,
},
},
}),
};
const mockAHHistoricApi = {
...mockHistoricApi,
consts: {
...mockHistoricApi.consts,
babe: null,
staking: {
electionLookAhead: registries_1.polkadotRegistry.createType('BlockNumber'),
sessionsPerEra: registries_1.polkadotRegistry.createType('SessionIndex', 6),
},
},
query: {
...mockHistoricApi.query,
session: null,
timestamp: {
now: timestampNowAt,
},
staking: {
activeEra: mock_1.activeEraAt,
eraElectionStatus: eraElectionStatusAt,
erasStartSessionIndex: mock_1.erasStartSessionIndexAt,
forceEra: forceEraAt,
bondedEras: () => Promise.resolve([
[40, 276],
[41, 282],
[42, 288],
[43, 294],
[44, 300],
[45, 306],
[46, 312],
[47, 318],
[48, 324],
[49, 330],
].map((el) => [registries_1.polkadotRegistry.createType('u32', el[0]), registries_1.polkadotRegistry.createType('u32', el[1])])),
unappliedSlashes: {
entries: unappliedSlashesEntries,
},
validatorCount: validatorCountAt,
},
},
};
const mockAHNextApi = {
...mock_1.defaultMockApi,
consts: {
...mockHistoricApi.consts,
babe: null,
},
query: {
...mock_1.defaultMockApi.query,
session: null,
timestamp: {
now: timestampNowAt,
},
staking: {
activeEra: mock_1.activeEraAt,
eraElectionStatus: eraElectionStatusAt,
erasStartSessionIndex: mock_1.erasStartSessionIndexAt,
forceEra: forceEraAt,
unappliedSlashes: {
entries: unappliedSlashesEntries,
},
validatorCount: validatorCountAt,
},
},
at: (_hash) => mockAHHistoricApi,
};
// Helper functions for creating mock data
function mockSkippedEpochs(epochs) {
return registries_1.polkadotRegistry.createType('Vec<(u64, u32)>', epochs.map(([epoch, session]) => [
registries_1.polkadotRegistry.createType('u64', epoch),
registries_1.polkadotRegistry.createType('u32', session),
]));
}
function createMockAssetHubHistoricApiWithNoneActiveEra() {
return {
...mockAHHistoricApi,
query: {
...mockAHHistoricApi.query,
staking: {
...mockAHHistoricApi.query.staking,
activeEra: () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Option<ActiveEraInfo>', null)),
},
},
};
}
function createMockAssetHubHistoricApiWithNoneEraStartSessionIndex() {
return {
...mockAHHistoricApi,
query: {
...mockAHHistoricApi.query,
staking: {
...mockAHHistoricApi.query.staking,
bondedEras: () => Promise.resolve().then(() => []),
},
},
};
}
function createMockRelayChainApiWithSkippedEpochs(skippedEpochs) {
return {
...mockRCNextApi,
query: {
...mockRCNextApi.query,
babe: {
...mockRCNextApi.query.babe,
skippedEpochs: () => Promise.resolve().then(() => mockSkippedEpochs(skippedEpochs)),
},
},
at: (_hash) => ({
...mockRCNextApi,
query: {
...mockRCNextApi.query,
babe: {
...mockRCNextApi.query.babe,
skippedEpochs: () => Promise.resolve().then(() => mockSkippedEpochs(skippedEpochs)),
},
},
}),
};
}
describe('PalletStakingProgressService', () => {
beforeAll(() => {
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApi);
});
beforeEach(() => {
// Reset all mocks before each test to prevent interference
jest.clearAllMocks();
// Reset ApiPromiseRegistry mocks to default state
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApi);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot']);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => []);
// Reset assetHubInfo to default state
apiRegistry_1.ApiPromiseRegistry.assetHubInfo = {
isAssetHub: false,
isAssetHubMigrated: false,
};
});
afterEach(() => {
// Clean up after each test
jest.restoreAllMocks();
});
describe('derivePalletStakingProgress before AHM', () => {
mockHistoricApi.query.session.validators = validatorsAt;
it('works when ApiPromise works (block 789629)', async () => {
/**
* Mock PalletStakingProgressService instance.
*/
const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot');
expect((0, sanitizeNumbers_1.sanitizeNumbers)(await palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629))).toStrictEqual(stakingProgress789629_json_1.default);
});
it('throws when ErasStartSessionIndex.isNone', async () => {
mockHistoricApi.query.staking.bondedEras = () => Promise.resolve([]);
mockHistoricApi.query.staking.erasStartSessionIndex = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Option<SessionIndex>', null));
/**
* Mock PalletStakingProgressService instance.
*/
const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot');
await expect(palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toStrictEqual(new http_errors_1.InternalServerError('EraStartSessionIndex is None when Some was expected.'));
mockHistoricApi.query.staking.erasStartSessionIndex = mock_1.erasStartSessionIndexAt;
});
it('throws when activeEra.isNone', async () => {
mockHistoricApi.query.staking.activeEra = () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Option<ActiveEraInfo>', null));
/**
* Mock PalletStakingProgressService instance.
*/
const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot');
await expect(palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toStrictEqual(new http_errors_1.InternalServerError('ActiveEra is None when Some was expected.'));
mockHistoricApi.query.staking.activeEra = mock_1.activeEraAt;
mockHistoricApi.query.session.validators = validatorsAt;
});
it('works with entries in unappliedSlashes', async () => {
/**
* Mock PalletStakingProgressService instance.
*/
const palletStakingProgressServiceUnappliedSlashes = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiUnappliedSlashes);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(await palletStakingProgressServiceUnappliedSlashes.derivePalletStakingProgress(mock_1.blockHash789629))).toStrictEqual(stakingProgressUnappliedSlashes_json_1.default);
});
});
describe('derivePalletStakingProgress after AHM', () => {
beforeEach(() => {
// Set up Asset Hub state for these tests
apiRegistry_1.ApiPromiseRegistry.assetHubInfo = {
isAssetHub: true,
isAssetHubMigrated: true,
};
});
it('it throws if historicApi does not have staking', async () => {
const PalletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockRCNextApi);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => {
return [
{
specName: 'polkadot',
api: mockRCNextApi,
},
];
});
await expect(PalletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toThrow('Staking pallet not found for queried runtime');
});
it('it throws if sidecar is connected to AH and querying historical block', async () => {
const PalletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
// Create a mock that throws an error for historical blocks
const mockApiThatThrowsForHistorical = {
...mockAHNextApi,
at: (hash) => {
if (hash.eq(mock_1.blockHash100000)) {
throw new Error('At is currently unsupported for pallet staking validators connected to assethub');
}
return mockAHNextApi.at(hash);
},
};
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiThatThrowsForHistorical);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => {
return [
{
specName: 'polkadot',
api: mockRCNextApi,
},
];
});
await expect(PalletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash100000)).rejects.toThrow('At is currently unsupported for pallet staking validators connected to assethub');
});
it('it throws if sidecar is connected to AH but no RC connection is available', async () => {
const PalletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockAHNextApi);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => {
return [];
});
await expect(PalletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toThrow('Relay chain API not found');
});
it('works when ApiPromise works (block 789629)', async () => {
const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
// needs both RC and AH connection
jest.spyOn(palletStakingProgressService, 'assetHubInfo', 'get').mockReturnValue({
isAssetHub: true,
isAssetHubMigrated: true,
});
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getTypeBySpecName').mockReturnValue('assethub');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockAHNextApi);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => {
return [
{
specName: 'polkadot',
api: mockRCNextApi,
},
];
});
expect((0, sanitizeNumbers_1.sanitizeNumbers)(await palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629))).toStrictEqual(stakingProgressPostAhm_json_1.default);
});
it('throws when ErasStartSessionIndex.isNone', async () => {
// Create a fresh mock for this specific test
const mockHistoricApiWithNoneEraStartSessionIndex = {
...mockAHHistoricApi,
query: {
...mockAHHistoricApi.query,
staking: {
...mockAHHistoricApi.query.staking,
bondedEras: () => Promise.resolve().then(() => []),
erasStartSessionIndex: () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Option<SessionIndex>', undefined)),
},
},
};
const mockApiWithNoneEraStartSessionIndex = {
...mockAHNextApi,
at: (_hash) => Promise.resolve(mockHistoricApiWithNoneEraStartSessionIndex),
};
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getTypeBySpecName').mockReturnValue('assethub');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiWithNoneEraStartSessionIndex);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => {
return [
{
specName: 'polkadot',
api: mockRCNextApi,
},
];
});
const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
jest.spyOn(palletStakingProgressService, 'assetHubInfo', 'get').mockReturnValue({
isAssetHub: true,
isAssetHubMigrated: true,
});
await expect(palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toStrictEqual(new http_errors_1.InternalServerError('EraStartSessionIndex is None when Some was expected.'));
});
it('throws when activeEra.isNone', async () => {
// Create a fresh mock for this specific test
const mockHistoricApiWithNoneActiveEra = {
...mockAHHistoricApi,
query: {
...mockAHHistoricApi.query,
staking: {
...mockAHHistoricApi.query.staking,
activeEra: () => Promise.resolve().then(() => registries_1.polkadotRegistry.createType('Option<ActiveEraInfo>', null)),
},
},
};
const mockApiWithNoneActiveEra = {
...mockAHNextApi,
at: (_hash) => Promise.resolve(mockHistoricApiWithNoneActiveEra),
};
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getTypeBySpecName').mockReturnValue('assethub');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiWithNoneActiveEra);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => {
return [
{
specName: 'polkadot',
api: mockRCNextApi,
},
];
});
const palletStakingProgressService = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
jest.spyOn(palletStakingProgressService, 'assetHubInfo', 'get').mockReturnValue({
isAssetHub: true,
isAssetHubMigrated: true,
});
await expect(palletStakingProgressService.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toStrictEqual(new http_errors_1.InternalServerError('ActiveEra is None when Some was expected.'));
});
it('works with entries in unappliedSlashes', async () => {
// Create a fresh mock for this specific test
const mockApiUnappliedSlashesForAH = {
...mockApiUnappliedSlashes,
query: {
...mockApiUnappliedSlashes.query,
session: null,
},
consts: {
...mockApiUnappliedSlashes.consts,
session: null,
},
};
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getTypeBySpecName').mockReturnValue('assethub');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiUnappliedSlashesForAH);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['polkadot', 'statemine']);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => {
return [
{
specName: 'polkadot',
api: mockRCNextApi,
},
];
});
const palletStakingProgressServiceUnappliedSlashes = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
jest.spyOn(palletStakingProgressServiceUnappliedSlashes, 'assetHubInfo', 'get').mockReturnValue({
isAssetHub: true,
isAssetHubMigrated: true,
});
expect((0, sanitizeNumbers_1.sanitizeNumbers)(await palletStakingProgressServiceUnappliedSlashes.derivePalletStakingProgress(mock_1.blockHash789629))).toStrictEqual(stakingProgressUnappliedSlashesPostAHM_json_1.default);
});
});
describe('Asset Hub BABE Calculations', () => {
describe('derivePalletStakingProgress with Asset Hub BABE calculations', () => {
beforeEach(() => {
apiRegistry_1.ApiPromiseRegistry.assetHubInfo = {
isAssetHub: true,
isAssetHubMigrated: true,
};
});
it('uses Asset Hub calculation when isAssetHub && isAssetHubMigrated', async () => {
const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getTypeBySpecName').mockReturnValue('assethub');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockAHNextApi);
jest
.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType')
.mockImplementation(() => [{ specName: 'polkadot', api: mockRCNextApi }]);
const result = await service.derivePalletStakingProgress(mock_1.blockHash789629);
// Verify the result has the expected structure
expect(result).toHaveProperty('at');
expect(result).toHaveProperty('activeEra');
expect(result).toHaveProperty('forceEra');
expect(result).toHaveProperty('nextSessionEstimate');
expect(result).toHaveProperty('unappliedSlashes');
});
it('throws when ActiveEra is None for Asset Hub', async () => {
const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
// Create a fresh mock for this specific test
const mockHistoricApiWithNoneActiveEra = createMockAssetHubHistoricApiWithNoneActiveEra();
const mockApiWithNoneActiveEra = {
...mockAHNextApi,
at: (_hash) => Promise.resolve(mockHistoricApiWithNoneActiveEra),
};
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getTypeBySpecName').mockReturnValue('assethub');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiWithNoneActiveEra);
jest
.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType')
.mockImplementation(() => [{ specName: 'polkadot', api: mockRCNextApi }]);
await expect(service.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toThrow('ActiveEra is None when Some was expected.');
});
it('throws when EraStartSessionIndex is None for Asset Hub', async () => {
const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
// Create a fresh mock for this specific test
const mockHistoricApiWithNoneEraStartSessionIndex = createMockAssetHubHistoricApiWithNoneEraStartSessionIndex();
const mockApiWithNoneEraStartSessionIndex = {
...mockAHNextApi,
at: (_hash) => Promise.resolve(mockHistoricApiWithNoneEraStartSessionIndex),
};
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getTypeBySpecName').mockReturnValue('assethub');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApiWithNoneEraStartSessionIndex);
jest
.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType')
.mockImplementation(() => [{ specName: 'polkadot', api: mockRCNextApi }]);
await expect(service.derivePalletStakingProgress(mock_1.blockHash789629)).rejects.toThrow('EraStartSessionIndex is None when Some was expected.');
});
it('calculates session index with skipped epochs for Asset Hub', async () => {
const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getTypeBySpecName').mockReturnValue('assethub');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockAHNextApi);
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => [{ specName: 'polkadot', api: createMockRelayChainApiWithSkippedEpochs([[16, 12]]) }]);
const result = await service.derivePalletStakingProgress(mock_1.blockHash789629);
// Verify the result has the expected structure
expect(result).toHaveProperty('at');
expect(result).toHaveProperty('activeEra');
expect(result).toHaveProperty('forceEra');
expect(result).toHaveProperty('nextSessionEstimate');
expect(result).toHaveProperty('unappliedSlashes');
});
it('uses hardcoded BABE values for time-based calculations in Asset Hub', async () => {
const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('statemine');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getTypeBySpecName').mockReturnValue('assethub');
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockAHNextApi);
jest
.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType')
.mockImplementation(() => [{ specName: 'polkadot', api: mockRCNextApi }]);
const result = await service.derivePalletStakingProgress(mock_1.blockHash789629);
// Verify the result has the expected structure and uses time-based calculations
expect(result).toHaveProperty('at');
expect(result).toHaveProperty('activeEra');
expect(result).toHaveProperty('forceEra');
expect(result).toHaveProperty('nextSessionEstimate');
expect(result).toHaveProperty('unappliedSlashes');
});
});
});
describe('Non-Asset Hub calculations', () => {
beforeEach(() => {
// Reset to non-Asset Hub state for these tests
apiRegistry_1.ApiPromiseRegistry.assetHubInfo = {
isAssetHub: false,
isAssetHubMigrated: false,
};
// Ensure the mock has the correct bondedEras return type
mockHistoricApi.query.staking.bondedEras = () => Promise.resolve([
[40, 276],
[41, 282],
[42, 288],
[43, 294],
[44, 300],
[45, 306],
[46, 312],
[47, 318],
[48, 324],
[49, 330],
].map((el) => [registries_1.polkadotRegistry.createType('u32', el[0]), registries_1.polkadotRegistry.createType('u32', el[1])]));
});
it('uses regular calculation when not Asset Hub', async () => {
const service = new PalletsStakingProgressService_1.PalletsStakingProgressService('polkadot');
// Use the regular mock (not Asset Hub mock) for this test
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => mockApi);
const result = await service.derivePalletStakingProgress(mock_1.blockHash789629);
// Verify the result has the expected structure and matches the original response
expect((0, sanitizeNumbers_1.sanitizeNumbers)(result)).toStrictEqual(stakingProgress789629_json_1.default);
});
});
});
//# sourceMappingURL=PalletsStakingProgressService.spec.js.map