@substrate/api-sidecar
Version:
REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.
335 lines • 18 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 });
const bn_js_1 = __importDefault(require("bn.js"));
const vestingCalculations_1 = require("./vestingCalculations");
describe('Vesting Calculations', () => {
// Sample vesting schedule based on real data from tests
const sampleSchedule = {
locked: new bn_js_1.default('1749990000000000'),
perBlock: new bn_js_1.default('166475460'),
startingBlock: new bn_js_1.default('4961000'),
};
describe('calculateVested', () => {
it('should return 0 when current block is before starting block', () => {
const currentBlock = new bn_js_1.default('4960000'); // Before startingBlock
const result = (0, vestingCalculations_1.calculateVested)(currentBlock, sampleSchedule);
expect(result.toString()).toBe('0');
});
it('should return 0 when current block equals starting block', () => {
const currentBlock = new bn_js_1.default('4961000'); // Equal to startingBlock
const result = (0, vestingCalculations_1.calculateVested)(currentBlock, sampleSchedule);
expect(result.toString()).toBe('0');
});
it('should return partial amount when vesting is in progress', () => {
// 1000 blocks after start
const currentBlock = new bn_js_1.default('4962000');
const result = (0, vestingCalculations_1.calculateVested)(currentBlock, sampleSchedule);
// Expected: 1000 * 166475460 = 166475460000
const expected = new bn_js_1.default('166475460000');
expect(result.toString()).toBe(expected.toString());
});
it('should return locked amount when fully vested', () => {
// Far in the future, well past end block
const currentBlock = new bn_js_1.default('100000000');
const result = (0, vestingCalculations_1.calculateVested)(currentBlock, sampleSchedule);
// Should be capped at locked amount
expect(result.toString()).toBe(sampleSchedule.locked.toString());
});
it('should not exceed locked amount even with large block numbers', () => {
const currentBlock = new bn_js_1.default('999999999999');
const result = (0, vestingCalculations_1.calculateVested)(currentBlock, sampleSchedule);
expect(result.lte(sampleSchedule.locked)).toBe(true);
expect(result.toString()).toBe(sampleSchedule.locked.toString());
});
it('should handle schedule with small values', () => {
const smallSchedule = {
locked: new bn_js_1.default('1000'),
perBlock: new bn_js_1.default('10'),
startingBlock: new bn_js_1.default('100'),
};
// 50 blocks after start: 50 * 10 = 500
const currentBlock = new bn_js_1.default('150');
const result = (0, vestingCalculations_1.calculateVested)(currentBlock, smallSchedule);
expect(result.toString()).toBe('500');
});
it('should handle schedule where perBlock equals locked', () => {
const quickVestSchedule = {
locked: new bn_js_1.default('1000'),
perBlock: new bn_js_1.default('1000'),
startingBlock: new bn_js_1.default('100'),
};
// 1 block after start should fully vest
const currentBlock = new bn_js_1.default('101');
const result = (0, vestingCalculations_1.calculateVested)(currentBlock, quickVestSchedule);
expect(result.toString()).toBe('1000');
});
});
describe('calculateEndBlock', () => {
it('should calculate correct end block for sample schedule', () => {
const result = (0, vestingCalculations_1.calculateEndBlock)(sampleSchedule);
// endBlock = startingBlock + ceil(locked / perBlock)
// locked / perBlock = 1749990000000000 / 166475460 = 10511999 remainder 130955460
// Since there's a remainder, we round up: 10511999 + 1 = 10512000
// endBlock = 4961000 + 10512000 = 15473000
const expectedEndBlock = new bn_js_1.default('15473000');
expect(result.toString()).toBe(expectedEndBlock.toString());
});
it('should handle exact division (no remainder)', () => {
const exactSchedule = {
locked: new bn_js_1.default('1000'),
perBlock: new bn_js_1.default('100'),
startingBlock: new bn_js_1.default('500'),
};
// endBlock = 500 + (1000 / 100) = 500 + 10 = 510
const result = (0, vestingCalculations_1.calculateEndBlock)(exactSchedule);
expect(result.toString()).toBe('510');
});
it('should handle division with remainder (rounds up)', () => {
const remainderSchedule = {
locked: new bn_js_1.default('1001'),
perBlock: new bn_js_1.default('100'),
startingBlock: new bn_js_1.default('500'),
};
// endBlock = 500 + ceil(1001 / 100) = 500 + 11 = 511
const result = (0, vestingCalculations_1.calculateEndBlock)(remainderSchedule);
expect(result.toString()).toBe('511');
});
it('should handle zero perBlock gracefully', () => {
const zeroPerBlockSchedule = {
locked: new bn_js_1.default('1000'),
perBlock: new bn_js_1.default('0'),
startingBlock: new bn_js_1.default('500'),
};
const result = (0, vestingCalculations_1.calculateEndBlock)(zeroPerBlockSchedule);
// Should return MAX_SAFE_INTEGER to indicate "never ends"
expect(result.toString()).toBe(Number.MAX_SAFE_INTEGER.toString());
});
});
describe('calculateTotalVested', () => {
it('should return 0 for empty schedules array', () => {
const currentBlock = new bn_js_1.default('5000000');
const result = (0, vestingCalculations_1.calculateTotalVested)(currentBlock, []);
expect(result.toString()).toBe('0');
});
it('should return correct total for single schedule', () => {
const currentBlock = new bn_js_1.default('4962000'); // 1000 blocks after start
const result = (0, vestingCalculations_1.calculateTotalVested)(currentBlock, [sampleSchedule]);
// Expected: 1000 * 166475460 = 166475460000
expect(result.toString()).toBe('166475460000');
});
it('should sum vested amounts from multiple schedules', () => {
const schedules = [
{
locked: new bn_js_1.default('1000'),
perBlock: new bn_js_1.default('10'),
startingBlock: new bn_js_1.default('100'),
},
{
locked: new bn_js_1.default('2000'),
perBlock: new bn_js_1.default('20'),
startingBlock: new bn_js_1.default('100'),
},
];
// At block 150 (50 blocks after start):
// Schedule 1: 50 * 10 = 500
// Schedule 2: 50 * 20 = 1000
// Total: 1500
const currentBlock = new bn_js_1.default('150');
const result = (0, vestingCalculations_1.calculateTotalVested)(currentBlock, schedules);
expect(result.toString()).toBe('1500');
});
it('should handle schedules with different starting blocks', () => {
const schedules = [
{
locked: new bn_js_1.default('1000'),
perBlock: new bn_js_1.default('10'),
startingBlock: new bn_js_1.default('100'),
},
{
locked: new bn_js_1.default('2000'),
perBlock: new bn_js_1.default('20'),
startingBlock: new bn_js_1.default('200'), // Starts later
},
];
// At block 150:
// Schedule 1: 50 * 10 = 500 (started at 100)
// Schedule 2: 0 (hasn't started, starts at 200)
// Total: 500
const currentBlock = new bn_js_1.default('150');
const result = (0, vestingCalculations_1.calculateTotalVested)(currentBlock, schedules);
expect(result.toString()).toBe('500');
});
it('should handle mix of fully vested and in-progress schedules', () => {
const schedules = [
{
locked: new bn_js_1.default('100'),
perBlock: new bn_js_1.default('100'), // Fully vests in 1 block
startingBlock: new bn_js_1.default('100'),
},
{
locked: new bn_js_1.default('1000'),
perBlock: new bn_js_1.default('10'),
startingBlock: new bn_js_1.default('100'),
},
];
// At block 150:
// Schedule 1: min(50 * 100, 100) = 100 (fully vested)
// Schedule 2: 50 * 10 = 500
// Total: 600
const currentBlock = new bn_js_1.default('150');
const result = (0, vestingCalculations_1.calculateTotalVested)(currentBlock, schedules);
expect(result.toString()).toBe('600');
});
});
describe('calculateVestingTotal', () => {
it('should return 0 for empty schedules array', () => {
const result = (0, vestingCalculations_1.calculateVestingTotal)([]);
expect(result.toString()).toBe('0');
});
it('should return locked amount for single schedule', () => {
const result = (0, vestingCalculations_1.calculateVestingTotal)([sampleSchedule]);
expect(result.toString()).toBe(sampleSchedule.locked.toString());
});
it('should sum locked amounts from multiple schedules', () => {
const schedules = [
{
locked: new bn_js_1.default('1000'),
perBlock: new bn_js_1.default('10'),
startingBlock: new bn_js_1.default('100'),
},
{
locked: new bn_js_1.default('2000'),
perBlock: new bn_js_1.default('20'),
startingBlock: new bn_js_1.default('200'),
},
];
const result = (0, vestingCalculations_1.calculateVestingTotal)(schedules);
expect(result.toString()).toBe('3000');
});
});
describe('calculateVestedClaimable', () => {
it('should return 0 when nothing has vested', () => {
const vestingLocked = new bn_js_1.default('1000');
const vestingTotal = new bn_js_1.default('1000');
const vestedBalance = new bn_js_1.default('0');
const result = (0, vestingCalculations_1.calculateVestedClaimable)(vestingLocked, vestingTotal, vestedBalance);
// claimable = 1000 - (1000 - 0) = 0
expect(result.toString()).toBe('0');
});
it('should return correct claimable when partially vested', () => {
const vestingLocked = new bn_js_1.default('1000');
const vestingTotal = new bn_js_1.default('1000');
const vestedBalance = new bn_js_1.default('400');
const result = (0, vestingCalculations_1.calculateVestedClaimable)(vestingLocked, vestingTotal, vestedBalance);
// claimable = 1000 - (1000 - 400) = 1000 - 600 = 400
expect(result.toString()).toBe('400');
});
it('should return correct claimable when some has already been claimed', () => {
// Example: 1000 locked, 400 vested, user already claimed 100
const vestingLocked = new bn_js_1.default('900'); // 1000 - 100 already claimed
const vestingTotal = new bn_js_1.default('1000');
const vestedBalance = new bn_js_1.default('400');
const result = (0, vestingCalculations_1.calculateVestedClaimable)(vestingLocked, vestingTotal, vestedBalance);
// claimable = 900 - (1000 - 400) = 900 - 600 = 300
expect(result.toString()).toBe('300');
});
it('should return 0 when fully claimed', () => {
// All vested amount already claimed
const vestingLocked = new bn_js_1.default('600'); // Original 1000, vested 400, all 400 claimed
const vestingTotal = new bn_js_1.default('1000');
const vestedBalance = new bn_js_1.default('400');
const result = (0, vestingCalculations_1.calculateVestedClaimable)(vestingLocked, vestingTotal, vestedBalance);
// claimable = 600 - (1000 - 400) = 600 - 600 = 0
expect(result.toString()).toBe('0');
});
it('should return full amount when fully vested and nothing claimed', () => {
const vestingLocked = new bn_js_1.default('1000');
const vestingTotal = new bn_js_1.default('1000');
const vestedBalance = new bn_js_1.default('1000'); // Fully vested
const result = (0, vestingCalculations_1.calculateVestedClaimable)(vestingLocked, vestingTotal, vestedBalance);
// claimable = 1000 - (1000 - 1000) = 1000 - 0 = 1000
expect(result.toString()).toBe('1000');
});
it('should handle multiple schedules (aggregate values)', () => {
// Two schedules: 1000 + 2000 = 3000 total locked
// 400 + 800 = 1200 total vested
// User claimed 200
const vestingLocked = new bn_js_1.default('2800'); // 3000 - 200 claimed
const vestingTotal = new bn_js_1.default('3000');
const vestedBalance = new bn_js_1.default('1200');
const result = (0, vestingCalculations_1.calculateVestedClaimable)(vestingLocked, vestingTotal, vestedBalance);
// claimable = 2800 - (3000 - 1200) = 2800 - 1800 = 1000
expect(result.toString()).toBe('1000');
});
it('should return 0 for negative result (edge case)', () => {
// Edge case: vestingLocked is less than stillLocked (shouldn't happen normally)
const vestingLocked = new bn_js_1.default('500');
const vestingTotal = new bn_js_1.default('1000');
const vestedBalance = new bn_js_1.default('400');
const result = (0, vestingCalculations_1.calculateVestedClaimable)(vestingLocked, vestingTotal, vestedBalance);
// claimable = 500 - (1000 - 400) = 500 - 600 = -100, capped at 0
expect(result.toString()).toBe('0');
});
});
describe('calculateVestingDetails', () => {
it('should return empty array for empty schedules', () => {
const currentBlock = new bn_js_1.default('5000000');
const result = (0, vestingCalculations_1.calculateVestingDetails)(currentBlock, []);
expect(result).toEqual([]);
});
it('should return details for single schedule', () => {
const schedule = {
locked: new bn_js_1.default('1000'),
perBlock: new bn_js_1.default('100'),
startingBlock: new bn_js_1.default('500'),
};
const currentBlock = new bn_js_1.default('505'); // 5 blocks after start
const result = (0, vestingCalculations_1.calculateVestingDetails)(currentBlock, [schedule]);
expect(result.length).toBe(1);
expect(result[0].vested.toString()).toBe('500'); // 5 * 100
expect(result[0].endBlock.toString()).toBe('510'); // 500 + (1000/100)
});
it('should return details for multiple schedules', () => {
const schedules = [
{
locked: new bn_js_1.default('1000'),
perBlock: new bn_js_1.default('100'),
startingBlock: new bn_js_1.default('500'),
},
{
locked: new bn_js_1.default('2000'),
perBlock: new bn_js_1.default('200'),
startingBlock: new bn_js_1.default('600'),
},
];
const currentBlock = new bn_js_1.default('605'); // 105 blocks after schedule 1, 5 blocks after schedule 2
const result = (0, vestingCalculations_1.calculateVestingDetails)(currentBlock, schedules);
expect(result.length).toBe(2);
// Schedule 1: fully vested (105 * 100 = 10500 > 1000)
expect(result[0].vested.toString()).toBe('1000');
expect(result[0].endBlock.toString()).toBe('510');
// Schedule 2: 5 * 200 = 1000
expect(result[1].vested.toString()).toBe('1000');
expect(result[1].endBlock.toString()).toBe('610'); // 600 + (2000/200)
});
});
});
//# sourceMappingURL=vestingCalculations.spec.js.map