UNPKG

@substrate/api-sidecar

Version:

REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.

486 lines 24 kB
"use strict"; // 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/>. Object.defineProperty(exports, "__esModule", { value: true }); const apiRegistry_1 = require("../../apiRegistry"); const sanitize_1 = require("../../sanitize"); const registries_1 = require("../../test-helpers/registries"); const mock_1 = require("../test-helpers/mock"); const accounts_1 = require("../test-helpers/mock/accounts"); const AccountsStakingPayoutsService_1 = require("./AccountsStakingPayoutsService"); const eraIndex = registries_1.polkadotRegistryV1000001.createType('EraIndex', accounts_1.ERA); const historyDepthAt = registries_1.polkadotRegistryV1000001.createType('u32', 84); const blockHash = mock_1.blockHash789629; const validator = '16Divajwsc8nq8NLQUfVyDjbG18xp6GrAS4GSDVBTwm6eY27'; const nominator = '15j4dg5GzsL1bw2U2AWgeyAk6QTxq43V7ZPbXdAmbVLjvDCK'; const mockHistoricApi = { registry: registries_1.polkadotRegistryV1000001, consts: { staking: { historyDepth: historyDepthAt, }, }, query: { staking: { ledger: accounts_1.ledgerAt, erasRewardPoints: accounts_1.erasRewardPointsAt, erasValidatorReward: accounts_1.erasValidatorRewardAt, erasValidatorPrefs: accounts_1.erasValidatorPrefsAt, bonded: accounts_1.bondedAt, erasStakersClipped: { entries: accounts_1.erasStakersClippedAt, }, }, }, }; const mockApi = { ...mock_1.defaultMockApi, at: (_hash) => mockHistoricApi, }; const RCNextAHMApiPromise = { ...mock_1.defaultMockApi, consts: { ...mock_1.defaultMockApi.consts, staking: null, }, query: { ...mock_1.defaultMockApi.query, staking: null, }, at: (_hash) => ({ ...mockHistoricApi, consts: { ...mockHistoricApi.consts, staking: null, }, query: { ...mockHistoricApi.query, staking: null, }, }), }; const AHNextAHMApiPromise = { ...mock_1.defaultMockApi, consts: { ...mock_1.defaultMockApi.consts, session: null, babe: null, }, rpc: { ...mock_1.defaultMockApi.rpc, chain: { ...mock_1.defaultMockApi.rpc.chain, getFinalizedHead: () => Promise.resolve().then(() => blockHash), }, }, query: { ...mock_1.defaultMockApi.query, session: null, babe: null, }, at: (_hash) => ({ ...mockHistoricApi, consts: { ...mockHistoricApi.consts, session: null, babe: null, }, rpc: { ...mock_1.defaultMockApi.rpc, chain: { ...mock_1.defaultMockApi.rpc.chain, getFinalizedHead: () => Promise.resolve().then(() => blockHash), }, }, query: { ...mockHistoricApi.query, session: null, babe: null, }, }), }; const stakingPayoutsService = new AccountsStakingPayoutsService_1.AccountsStakingPayoutsService('mock'); describe('AccountsStakingPayoutsService', () => { beforeAll(() => { jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => { return mockApi; }); }); describe('fetchAccountStakingPayout before AHM', () => { it('Should work with a validator address', async () => { const res = await stakingPayoutsService.fetchAccountStakingPayout(blockHash, validator, 1, accounts_1.ERA, false, accounts_1.ERA + 1, mockHistoricApi); expect((0, sanitize_1.sanitizeNumbers)(res)).toStrictEqual({ at: { height: '789629', hash: '0x7b713de604a99857f6c25eacc115a4f28d2611a23d9ddff99ab0e4f1c17a8578', }, erasPayouts: [ { era: '1039', payouts: [ { claimed: true, nominatorExposure: '0', nominatorStakingPayout: '1043968334900993560134832959396203124', totalValidatorExposure: '17302617747768368', totalValidatorRewardPoints: '78920', validatorCommission: '1000000000', validatorId: '16Divajwsc8nq8NLQUfVyDjbG18xp6GrAS4GSDVBTwm6eY27', }, ], totalEraPayout: '308747987428782798114933729373649371136', totalEraRewardPoints: '23340160', }, ], }); }); it('Should work with a nominator address', async () => { const res = await stakingPayoutsService.fetchAccountStakingPayout(blockHash, nominator, 1, accounts_1.ERA, false, accounts_1.ERA + 1, mockHistoricApi); expect((0, sanitize_1.sanitizeNumbers)(res)).toStrictEqual({ at: { height: '789629', hash: '0x7b713de604a99857f6c25eacc115a4f28d2611a23d9ddff99ab0e4f1c17a8578', }, erasPayouts: [ { era: '1039', payouts: [ { claimed: true, nominatorExposure: '21133134966048676', nominatorStakingPayout: '0', totalValidatorExposure: '21133134966048676', totalValidatorRewardPoints: '97620', validatorCommission: '1000000000', validatorId: '16hzCDgyqnm1tskDccVWqxDVXYDLgdrrpC4Guxu3gPgLe5ib', }, ], totalEraPayout: '308747987428782798114933729373649371136', totalEraRewardPoints: '23340160', }, ], }); }); it('Should throw an error when the depth is greater than the historyDepth', () => { const serviceCall = async () => { await stakingPayoutsService.fetchAccountStakingPayout(blockHash, nominator, 85, accounts_1.ERA, true, accounts_1.ERA + 1, mockHistoricApi); }; // eslint-disable-next-line @typescript-eslint/no-floating-promises expect(serviceCall()).rejects.toThrow('Must specify a depth less than history_depth'); }); it('Should throw an error inputted era and historydepth is invalid', () => { const serviceCall = async () => { await stakingPayoutsService.fetchAccountStakingPayout(blockHash, nominator, 1, accounts_1.ERA, true, accounts_1.ERA + 134, mockHistoricApi); }; // eslint-disable-next-line @typescript-eslint/no-floating-promises expect(serviceCall()).rejects.toThrow('Must specify era and depth such that era - (depth - 1) is less ' + 'than or equal to current_era - history_depth.'); }); it('extractExposure Should work when the address is a nominator', () => { const nom = '15j4dg5GzsL1bw2U2AWgeyAk6QTxq43V7ZPbXdAmbVLjvDCK'; const val = '16hzCDgyqnm1tskDccVWqxDVXYDLgdrrpC4Guxu3gPgLe5ib'; const res = stakingPayoutsService['extractExposure'](nom, val, accounts_1.deriveEraExposureParam, 0); expect((0, sanitize_1.sanitizeNumbers)(res)).toStrictEqual({ nominatorExposure: '21133134966048676', totalExposure: '21133134966048676', }); }); it('extractExposure Should work when the address is a validator', () => { const val = '16hzCDgyqnm1tskDccVWqxDVXYDLgdrrpC4Guxu3gPgLe5ib'; const res = stakingPayoutsService['extractExposure'](val, val, accounts_1.deriveEraExposureParam, 0); expect((0, sanitize_1.sanitizeNumbers)(res)).toStrictEqual({ nominatorExposure: '0', totalExposure: '21133134966048676', }); }); it('extractTotalValidatorRewardPoints Should return the correct rewards', async () => { const rewards = await (0, accounts_1.erasRewardPointsAt)(eraIndex); const res = stakingPayoutsService['extractTotalValidatorRewardPoints'](rewards, validator); expect((0, sanitize_1.sanitizeNumbers)(res)).toBe('78920'); }); it('deriveNominatedExposures', () => { const res = stakingPayoutsService['deriveNominatedExposures'](nominator, accounts_1.deriveEraExposureParam); expect((0, sanitize_1.sanitizeNumbers)(res)).toStrictEqual([ { validatorId: '16hzCDgyqnm1tskDccVWqxDVXYDLgdrrpC4Guxu3gPgLe5ib', validatorIndex: '0', }, ]); }); it('deriveEraExposure Should return the correct derived value', async () => { const res = await stakingPayoutsService[`deriveEraExposure`](mockHistoricApi, eraIndex); // We check the length of the values since the data is so large. expect(res.era.toString()).toEqual('1039'); expect(Object.keys(res.nominators).length).toEqual(201); expect(Object.keys(res.validators).length).toEqual(3); }); }); describe('fetchAccountStakingPayout after AHM', () => { it('Should work with a validator address', async () => { apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; const stakingPayoutsService = new AccountsStakingPayoutsService_1.AccountsStakingPayoutsService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => { return AHNextAHMApiPromise; }); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['kusama', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'kusama', api: RCNextAHMApiPromise, }, ]; }); const res = await stakingPayoutsService.fetchAccountStakingPayout(blockHash, validator, 1, accounts_1.ERA, false, accounts_1.ERA + 1, await AHNextAHMApiPromise.at(blockHash)); expect((0, sanitize_1.sanitizeNumbers)(res)).toStrictEqual({ at: { height: '789629', hash: '0x7b713de604a99857f6c25eacc115a4f28d2611a23d9ddff99ab0e4f1c17a8578', }, erasPayouts: [ { era: '1039', payouts: [ { claimed: true, nominatorExposure: '0', nominatorStakingPayout: '1043968334900993560134832959396203124', totalValidatorExposure: '17302617747768368', totalValidatorRewardPoints: '78920', validatorCommission: '1000000000', validatorId: '16Divajwsc8nq8NLQUfVyDjbG18xp6GrAS4GSDVBTwm6eY27', }, ], totalEraPayout: '308747987428782798114933729373649371136', totalEraRewardPoints: '23340160', }, ], }); }); it('Should work with a nominator address', async () => { apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; const stakingPayoutsService = new AccountsStakingPayoutsService_1.AccountsStakingPayoutsService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => { return AHNextAHMApiPromise; }); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['kusama', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'kusama', api: RCNextAHMApiPromise, }, ]; }); const res = await stakingPayoutsService.fetchAccountStakingPayout(blockHash, nominator, 1, accounts_1.ERA, false, accounts_1.ERA + 1, mockHistoricApi); expect((0, sanitize_1.sanitizeNumbers)(res)).toStrictEqual({ at: { height: '789629', hash: '0x7b713de604a99857f6c25eacc115a4f28d2611a23d9ddff99ab0e4f1c17a8578', }, erasPayouts: [ { era: '1039', payouts: [ { claimed: true, nominatorExposure: '21133134966048676', nominatorStakingPayout: '0', totalValidatorExposure: '21133134966048676', totalValidatorRewardPoints: '97620', validatorCommission: '1000000000', validatorId: '16hzCDgyqnm1tskDccVWqxDVXYDLgdrrpC4Guxu3gPgLe5ib', }, ], totalEraPayout: '308747987428782798114933729373649371136', totalEraRewardPoints: '23340160', }, ], }); }); it('Should throw an error when the depth is greater than the historyDepth', async () => { apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; const stakingPayoutsService = new AccountsStakingPayoutsService_1.AccountsStakingPayoutsService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => { return AHNextAHMApiPromise; }); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['kusama', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'kusama', api: RCNextAHMApiPromise, }, ]; }); await expect(stakingPayoutsService.fetchAccountStakingPayout(blockHash, nominator, 85, accounts_1.ERA, true, accounts_1.ERA + 1, mockHistoricApi)).rejects.toThrow('Must specify a depth less than history_depth'); }); it('Should throw an error inputted era and historydepth is invalid', async () => { apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; const stakingPayoutsService = new AccountsStakingPayoutsService_1.AccountsStakingPayoutsService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => { return AHNextAHMApiPromise; }); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['kusama', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'kusama', api: RCNextAHMApiPromise, }, ]; }); await expect(stakingPayoutsService.fetchAccountStakingPayout(blockHash, nominator, 1, accounts_1.ERA, true, accounts_1.ERA + 134, mockHistoricApi)).rejects.toThrow('Must specify era and depth such that era - (depth - 1) is less ' + 'than or equal to current_era - history_depth.'); }); it('extractExposure Should work when the address is a nominator', () => { const nom = '15j4dg5GzsL1bw2U2AWgeyAk6QTxq43V7ZPbXdAmbVLjvDCK'; const val = '16hzCDgyqnm1tskDccVWqxDVXYDLgdrrpC4Guxu3gPgLe5ib'; apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; const stakingPayoutsService = new AccountsStakingPayoutsService_1.AccountsStakingPayoutsService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => { return AHNextAHMApiPromise; }); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['kusama', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'kusama', api: RCNextAHMApiPromise, }, ]; }); const res = stakingPayoutsService['extractExposure'](nom, val, accounts_1.deriveEraExposureParam, 0); expect((0, sanitize_1.sanitizeNumbers)(res)).toStrictEqual({ nominatorExposure: '21133134966048676', totalExposure: '21133134966048676', }); }); it('extractExposure Should work when the address is a validator', () => { apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; const stakingPayoutsService = new AccountsStakingPayoutsService_1.AccountsStakingPayoutsService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => { return AHNextAHMApiPromise; }); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['kusama', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'kusama', api: RCNextAHMApiPromise, }, ]; }); const val = '16hzCDgyqnm1tskDccVWqxDVXYDLgdrrpC4Guxu3gPgLe5ib'; const res = stakingPayoutsService['extractExposure'](val, val, accounts_1.deriveEraExposureParam, 0); expect((0, sanitize_1.sanitizeNumbers)(res)).toStrictEqual({ nominatorExposure: '0', totalExposure: '21133134966048676', }); }); it('extractTotalValidatorRewardPoints Should return the correct rewards', async () => { apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; const stakingPayoutsService = new AccountsStakingPayoutsService_1.AccountsStakingPayoutsService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => { return AHNextAHMApiPromise; }); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['kusama', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'kusama', api: RCNextAHMApiPromise, }, ]; }); const rewards = await (0, accounts_1.erasRewardPointsAt)(eraIndex); const res = stakingPayoutsService['extractTotalValidatorRewardPoints'](rewards, validator); expect((0, sanitize_1.sanitizeNumbers)(res)).toBe('78920'); }); it('deriveNominatedExposures', () => { apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; const stakingPayoutsService = new AccountsStakingPayoutsService_1.AccountsStakingPayoutsService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => { return AHNextAHMApiPromise; }); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['kusama', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'kusama', api: RCNextAHMApiPromise, }, ]; }); const res = stakingPayoutsService['deriveNominatedExposures'](nominator, accounts_1.deriveEraExposureParam); expect((0, sanitize_1.sanitizeNumbers)(res)).toStrictEqual([ { validatorId: '16hzCDgyqnm1tskDccVWqxDVXYDLgdrrpC4Guxu3gPgLe5ib', validatorIndex: '0', }, ]); }); it('deriveEraExposure Should return the correct derived value', async () => { apiRegistry_1.ApiPromiseRegistry.assetHubInfo = { isAssetHub: true, isAssetHubMigrated: true, }; const stakingPayoutsService = new AccountsStakingPayoutsService_1.AccountsStakingPayoutsService('statemine'); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => { return AHNextAHMApiPromise; }); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getAllAvailableSpecNames').mockReturnValue(['kusama', 'statemine']); jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApiByType').mockImplementation(() => { return [ { specName: 'kusama', api: RCNextAHMApiPromise, }, ]; }); const res = await stakingPayoutsService[`deriveEraExposure`](mockHistoricApi, eraIndex); // We check the length of the values since the data is so large. expect(res.era.toString()).toEqual('1039'); expect(Object.keys(res.nominators).length).toEqual(201); expect(Object.keys(res.validators).length).toEqual(3); }); }); }); //# sourceMappingURL=AccountsStakingPayoutsService.spec.js.map