@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
JavaScript
"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