@substrate/api-sidecar
Version:
REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.
444 lines • 21 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/>.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.noneAuctionsInfoAt = exports.auctionsInfoAt = exports.slotsLeasesAt = exports.emptyVectorLeases = void 0;
const bn_js_1 = __importDefault(require("bn.js"));
const apiRegistry_1 = require("../../apiRegistry");
const sanitizeNumbers_1 = require("../../sanitize/sanitizeNumbers");
const polkadotV1000001Metadata_1 = require("../../test-helpers/metadata/polkadotV1000001Metadata");
const registries_1 = require("../../test-helpers/registries");
const typeFactory_1 = require("../../test-helpers/typeFactory");
const mock_1 = require("../test-helpers/mock");
const eventsHex_1 = require("../test-helpers/mock/paras/eventsHex");
const parasHeadBackedCandidates_json_1 = __importDefault(require("../test-helpers/responses/paras/parasHeadBackedCandidates.json"));
const parasHeadIncludedCandidates_json_1 = __importDefault(require("../test-helpers/responses/paras/parasHeadIncludedCandidates.json"));
const ParasService_1 = require("./ParasService");
/**
* ParasService specific constants
* The below types and constants use the Polkadot registry in order to properly
* test the ParasService with accurate metadata
*/
const polkadotApi = (0, typeFactory_1.createApiWithAugmentations)(polkadotV1000001Metadata_1.polkadotMetadataRpcV1000001);
const polkadotTypeFactory = new typeFactory_1.TypeFactory(polkadotApi);
/**
* Used for parachain crowdloans
*/
const funds = {
depositor: registries_1.polkadotRegistryV1000001.createType('AccountId', '12rgGkphjoZ25FubPoxywaNm3oVhSHnzExnT6hsLnicuLaaj'),
verifier: null,
deposit: registries_1.polkadotRegistryV1000001.createType('Balance', 5000000000000),
raised: registries_1.polkadotRegistryV1000001.createType('Balance', 2999970000000000),
end: registries_1.polkadotRegistryV1000001.createType('BlockNumber', 14164600),
cap: registries_1.polkadotRegistryV1000001.createType('Balance', 3000000000000000),
lastContribution: registries_1.polkadotRegistryV1000001.createType('LastContribution', {
preEnding: 34,
}),
firstPeriod: registries_1.polkadotRegistryV1000001.createType('LeasePeriod', 11),
lastPeriod: registries_1.polkadotRegistryV1000001.createType('LeasePeriod', 15),
trieIndex: registries_1.polkadotRegistryV1000001.createType('TrieIndex', 55),
};
const paraLifecycleObjectOne = {
onboarding: true,
parachain: true,
};
const paraLifecycleObjectTwo = {
parathread: true,
parachain: false,
};
const crowdloanParaId1 = polkadotTypeFactory.storageKey(1000, 'ParaId', polkadotApi.query.crowdloan.funds);
const crowdloanParaId2 = polkadotTypeFactory.storageKey(200, 'ParaId', polkadotApi.query.crowdloan.funds);
const paraLifecycleOne = registries_1.polkadotRegistryV1000001.createType('ParaLifecycle', paraLifecycleObjectOne);
const paraLifecycleTwo = registries_1.polkadotRegistryV1000001.createType('ParaLifecycle', paraLifecycleObjectTwo);
const accountIdOne = registries_1.polkadotRegistryV1000001.createType('AccountId', '13UVJyLnbVp77Z2t6qjFmcyzTXYQJjyb6Hww7ZHPumd81iht');
const accountIdTwo = registries_1.polkadotRegistryV1000001.createType('AccountId', '13UVJyLnbVp77Z2t6qpQs8LAavPA1FuD53ocaULGhTEy8s6n');
const balanceOfOne = registries_1.polkadotRegistryV1000001.createType('BalanceOf', 1000000);
const balanceOfTwo = registries_1.polkadotRegistryV1000001.createType('BalanceOf', 2999970000000000);
const fundsEntries = () => Promise.resolve().then(() => {
const optionFundInfo = registries_1.polkadotRegistryV1000001.createType('Option<FundInfo>', funds);
const entries = [
[crowdloanParaId1, optionFundInfo],
[crowdloanParaId2, optionFundInfo],
];
return entries;
});
const fundsAt = () => Promise.resolve().then(() => {
return registries_1.polkadotRegistryV1000001.createType('Option<FundInfo>', funds);
});
const fundsKeys = () => Promise.resolve().then(() => {
return [crowdloanParaId1, crowdloanParaId2];
});
/**
* Used for parachain paras
*/
const paraLifeCycleParaId1 = polkadotTypeFactory.storageKey(199, 'ParaId', polkadotApi.query.paras.paraLifecycles);
const paraLifeCycleParaId2 = polkadotTypeFactory.storageKey(200, 'ParaId', polkadotApi.query.paras.paraLifecycles);
const parasLifecyclesEntriesAt = () => Promise.resolve().then(() => {
return [
[paraLifeCycleParaId1, paraLifecycleOne],
[paraLifeCycleParaId2, paraLifecycleTwo],
];
});
const parasGenesisArgsAt = () => Promise.resolve().then(() => {
return registries_1.polkadotRegistryV1000001.createType('ParaGenesisArgs', { parachain: true });
});
const upcomingParasGenesisAt = () => Promise.resolve().then(() => {
return registries_1.polkadotRegistryV1000001.createType('Option<ParaGenesisArgs>', {
parachain: true,
});
});
const parasLifecyclesAt = () => Promise.resolve().then(() => {
return polkadotTypeFactory.optionOf(paraLifecycleOne);
});
/**
* Used for parachain leases
*/
const leasesParaId1 = polkadotTypeFactory.storageKey(2094, 'ParaId', polkadotApi.query.slots.leases);
const leasesParaId2 = polkadotTypeFactory.storageKey(2097, 'ParaId', polkadotApi.query.slots.leases);
const leasesTupleTwo = polkadotTypeFactory.tupleOf([accountIdTwo, balanceOfTwo], ['AccountId', 'BalanceOf']);
const parasOptionsTwo = polkadotTypeFactory.optionOf(leasesTupleTwo);
const vectorLeases = polkadotTypeFactory.vecOf([parasOptionsTwo]);
exports.emptyVectorLeases = registries_1.polkadotRegistryV1000001.createType('Vec<Raw>', []);
const slotsLeasesAt = () => Promise.resolve().then(() => {
return vectorLeases;
});
exports.slotsLeasesAt = slotsLeasesAt;
const slotsLeasesEntriesAt = () => Promise.resolve().then(() => {
return [
[leasesParaId1, vectorLeases],
[leasesParaId2, vectorLeases],
];
});
/**
* Used for parachain Auctions
*/
const auctionsInfoAt = () => Promise.resolve().then(() => {
const beginEnd = registries_1.polkadotRegistryV1000001.createType('BlockNumber', 18001400);
const leasePeriodIndex = registries_1.polkadotRegistryV1000001.createType('BlockNumber', 15);
const vectorAuctions = polkadotTypeFactory.vecOf([leasePeriodIndex, beginEnd]);
const optionAuctions = polkadotTypeFactory.optionOf(vectorAuctions);
return optionAuctions;
});
exports.auctionsInfoAt = auctionsInfoAt;
const noneAuctionsInfoAt = () => Promise.resolve().then(() => registries_1.polkadotRegistryV1000001.createType('Option<Raw>', null));
exports.noneAuctionsInfoAt = noneAuctionsInfoAt;
const auctionCounterAt = () => Promise.resolve().then(() => {
const counter = new bn_js_1.default(4);
return counter;
});
const auctionsWinningsAt = () => Promise.resolve().then(() => {
const paraId1 = registries_1.polkadotRegistryV1000001.createType('ParaId', 1000);
const paraId2 = registries_1.polkadotRegistryV1000001.createType('ParaId', 200);
const tupleOne = polkadotTypeFactory.tupleOf([accountIdOne, paraId1, balanceOfOne], ['AccountId', 'ParaId', 'BalanceOf']);
const tupleTwo = polkadotTypeFactory.tupleOf([accountIdTwo, paraId2, balanceOfTwo], ['AccountId', 'ParaId', 'BalanceOf']);
const parasOptionsOne = polkadotTypeFactory.optionOf(tupleOne);
const parasOptionsTwo = polkadotTypeFactory.optionOf(tupleTwo);
// No bids for the remaining slot ranges
const mockWinningOptions = new Array(8).fill(registries_1.polkadotRegistryV1000001.createType('Option<Raw>', null));
// Total of 10 winning object, 2 `Some(..)`, 8 `None`
const vectorWinnings = polkadotTypeFactory.vecOf([parasOptionsOne, parasOptionsTwo, ...mockWinningOptions]);
const optionWinnings = polkadotTypeFactory.optionOf(vectorWinnings);
return optionWinnings;
});
/**
* Used for parachain ParasHeads
*/
const eventsAt = () => Promise.resolve().then(() => registries_1.polkadotRegistryV1000001.createType('Vec<FrameSystemEventRecord>', eventsHex_1.eventsHex));
const historicApi = {
consts: {
auctions: {
endingPeriod: new bn_js_1.default(53400),
sampleLength: new bn_js_1.default(2),
leasePeriodsPerSlot: new bn_js_1.default(8),
},
slots: {
leasePeriod: new bn_js_1.default(20000),
},
},
query: {
auctions: {
auctionInfo: exports.auctionsInfoAt,
auctionCounter: auctionCounterAt,
winning: auctionsWinningsAt,
},
crowdloan: {
funds: fundsAt,
},
paras: {
paraLifecycles: parasLifecyclesAt,
paraGenesisArgs: parasGenesisArgsAt,
upcomingParasGenesis: upcomingParasGenesisAt,
},
slots: {
leases: exports.slotsLeasesAt,
},
system: {
events: eventsAt,
},
},
};
/**
* Assign necessary keys to crowdloan.funds
*/
Object.assign(historicApi.query.crowdloan.funds, {
entries: fundsEntries,
keys: fundsKeys,
});
/**
* Assign necessary keys to paras.paraLifecycles
*/
Object.assign(historicApi.query.paras.paraLifecycles, {
entries: parasLifecyclesEntriesAt,
});
/**
* Assign necessary keys to slots.leases
*/
Object.assign(historicApi.query.slots.leases, {
entries: slotsLeasesEntriesAt,
});
const mockApi = {
...mock_1.mockApiBlock18468942,
at: (_hash) => historicApi,
};
const parasService = new ParasService_1.ParasService('mcok');
describe('ParasService', () => {
beforeAll(() => {
jest.spyOn(apiRegistry_1.ApiPromiseRegistry, 'getApi').mockImplementation(() => {
return mockApi;
});
});
const paraId = 2094;
const expectedAt = {
hash: '0x1ffece02b91e52c4923827843774f705911905c0a66980f7037bed643b746d1d',
height: '18468942',
};
const expectedFund = {
depositor: '12rgGkphjoZ25FubPoxywaNm3oVhSHnzExnT6hsLnicuLaaj',
verifier: null,
deposit: '5000000000000',
raised: '2999970000000000',
end: '14164600',
cap: '3000000000000000',
lastContribution: { preEnding: '34' },
firstPeriod: '11',
lastPeriod: '15',
trieIndex: '55',
};
describe('ParasService.crowdloansInfo', () => {
it('Should return correct crowdloans info for a queried `paraId`', async () => {
const expectedResponse = {
at: expectedAt,
fundInfo: expectedFund,
leasePeriods: ['11', '12', '13', '14', '15'],
};
const response = await parasService.crowdloansInfo(mock_1.blockHash18468942, paraId);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toMatchObject(expectedResponse);
});
});
describe('ParasService.crowdloans', () => {
it('Should return correct crowdloans response', async () => {
const expectedResponse = {
at: expectedAt,
funds: [
{
fundInfo: expectedFund,
paraId: '1000',
},
{
fundInfo: expectedFund,
paraId: '200',
},
],
};
const response = await parasService.crowdloans(mock_1.blockHash18468942);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toMatchObject(expectedResponse);
});
});
describe('ParasService.leaseInfo', () => {
it('Should return the correct leasing information for a queried `paraId`', async () => {
const expectedResponse = {
at: expectedAt,
leases: [
{
account: '13UVJyLnbVp77Z2t6qpQs8LAavPA1FuD53ocaULGhTEy8s6n',
deposit: '2999970000000000',
},
],
paraLifecycle: 'Onboarding',
onboardingAs: 'parachain',
};
const response = await parasService.leaseInfo(mock_1.blockHash18468942, paraId);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toMatchObject(expectedResponse);
});
it('Should have `null` `leases` when its length is equal to 0', async () => {
const emptyLeasesAt = () => Promise.resolve().then(() => exports.emptyVectorLeases);
historicApi.query.slots.leases = emptyLeasesAt;
const expectedResponse = {
at: expectedAt,
leases: null,
paraLifecycle: 'Onboarding',
onboardingAs: 'parachain',
};
const response = await parasService.leaseInfo(mock_1.blockHash18468942, paraId);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toMatchObject(expectedResponse);
historicApi.query.slots.leases = exports.slotsLeasesAt;
});
});
describe('ParasService.leasesCurrent', () => {
it('Should return the correct entries for `leasesCurrent`', async () => {
const expectedResponse = {
at: expectedAt,
leasePeriodIndex: '923',
endOfLeasePeriod: '18480000',
currentLeaseHolders: ['2094', '2097'],
};
const response = await parasService.leasesCurrent(mock_1.blockHash18468942, true);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toMatchObject(expectedResponse);
});
it('Should return the correct response excluding `currentLeaseHolders`', async () => {
const expectedResponse = {
at: expectedAt,
leasePeriodIndex: '923',
endOfLeasePeriod: '18480000',
currentLeaseHolders: undefined,
};
const response = await parasService.leasesCurrent(mock_1.blockHash18468942, false);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toMatchObject(expectedResponse);
});
});
describe('ParasService.paras', () => {
it('Should return correct ParaLifecycles response', async () => {
const expectedResponse = {
at: expectedAt,
paras: [
{
paraId: '199',
paraLifecycle: 'Onboarding',
onboardingAs: 'parachain',
},
{
paraId: '200',
paraLifecycle: 'Parathread',
},
],
};
const response = await parasService.paras(mock_1.blockHash18468942);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toMatchObject(expectedResponse);
});
});
describe('ParasService.auctionsCurrent', () => {
/**
* Helper function to generate a a header to use to override the current one.
* This allows us to change the expected block we are using here as our head
* to test for a specific `phase` in an auction.
*
* @param blockNumber Current block head returned by header
* @returns
*/
const generateOverrideHeader = (blockNumber) => {
return {
parentHash: '0xa59330a7420132ea9429939ab5e5b695c5100985af507c6feb29a6fcacfb572e',
number: blockNumber,
stateRoot: '0x6ad2141fb995ec4076449fc512169e033747a90adfbd1d2120aed1addf534d58',
extrinsicsRoot: '0xc58ba0e38feed447870398e0f45cd234e00dc4cd40200b1248e341ab9ea058e2',
digest: {},
};
};
it('Should return the correct data during an ongoing endPeriod phase', async () => {
const expectedResponse = {
at: expectedAt,
beginEnd: '18001400',
finishEnd: '18054800',
winning: null,
};
const response = await parasService.auctionsCurrent(mock_1.blockHash18468942);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toMatchObject(expectedResponse);
});
it('Should return the correct data during a startPeriod phase', async () => {
const overrideHeader = generateOverrideHeader(770000);
const header = registries_1.polkadotRegistryV1000001.createType('Header', overrideHeader);
// Override the mockApi
mockApi.rpc.chain.getHeader = () => Promise.resolve().then(() => header);
const expectedResponse = {
at: {
hash: '0x1ffece02b91e52c4923827843774f705911905c0a66980f7037bed643b746d1d',
height: '770000',
},
beginEnd: '18001400',
finishEnd: '18054800',
phase: 'startPeriod',
auctionIndex: '4',
leasePeriods: ['15', '16', '17', '18', '19', '20', '21', '22'],
winning: null,
};
const response = await parasService.auctionsCurrent(mock_1.blockHash18468942);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toStrictEqual(expectedResponse);
// Set the MockApi back to its original self
mockApi.rpc.chain.getHeader = () => Promise.resolve().then(() => mock_1.mockBlock18468942.header);
});
it('Should return the correct data during a vrfDelay phase', async () => {
const overrideHeader = generateOverrideHeader(18468942);
const header = registries_1.polkadotRegistryV1000001.createType('Header', overrideHeader);
// Override the mockApi
mockApi.rpc.chain.getHeader = () => Promise.resolve().then(() => header);
const expectedResponse = {
at: {
hash: '0x1ffece02b91e52c4923827843774f705911905c0a66980f7037bed643b746d1d',
height: '18468942',
},
beginEnd: '18001400',
finishEnd: '18054800',
phase: 'vrfDelay',
auctionIndex: '4',
leasePeriods: ['15', '16', '17', '18', '19', '20', '21', '22'],
winning: null,
};
const response = await parasService.auctionsCurrent(mock_1.blockHash18468942);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toStrictEqual(expectedResponse);
// Set the MockApi back to its original self
mockApi.rpc.chain.getHeader = () => Promise.resolve().then(() => mock_1.mockBlock18468942.header);
});
it('Should return the correct null values when `auctionInfo` is `None`', async () => {
historicApi.query.auctions.auctionInfo = exports.noneAuctionsInfoAt;
const expectedResponse = {
at: expectedAt,
beginEnd: null,
finishEnd: null,
phase: null,
auctionIndex: '4',
leasePeriods: null,
winning: null,
};
const response = await parasService.auctionsCurrent(mock_1.blockHash18468942);
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toMatchObject(expectedResponse);
historicApi.query.auctions.auctionInfo = exports.auctionsInfoAt;
});
});
describe('ParasService.parasHead', () => {
it('Should return the correct response for CandidateIncluded methods', async () => {
const response = await parasService.parasHead(mock_1.blockHash18468942, 'CandidateIncluded');
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toStrictEqual(parasHeadIncludedCandidates_json_1.default);
});
it('Should return the correct response for CandidateBacked methods', async () => {
const response = await parasService.parasHead(mock_1.blockHash18468942, 'CandidateBacked');
expect((0, sanitizeNumbers_1.sanitizeNumbers)(response)).toStrictEqual(parasHeadBackedCandidates_json_1.default);
});
});
});
//# sourceMappingURL=ParasService.spec.js.map