76 lines
2.9 KiB
TypeScript
76 lines
2.9 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
import { calculateSnatchShortfall, isPositionDelinquent } from '../staking.js';
|
|
|
|
describe('calculateSnatchShortfall', () => {
|
|
test('returns zero when within cap', () => {
|
|
const outstanding = 100n;
|
|
const desired = 50n;
|
|
const total = 1000n;
|
|
|
|
const result = calculateSnatchShortfall(outstanding, desired, total, 2n, 10n);
|
|
expect(result).toBe(0n);
|
|
});
|
|
|
|
test('returns positive remainder when exceeding cap', () => {
|
|
const outstanding = 200n;
|
|
const desired = 200n;
|
|
const total = 1000n;
|
|
|
|
const result = calculateSnatchShortfall(outstanding, desired, total, 2n, 10n);
|
|
expect(result).toBe(200n);
|
|
});
|
|
|
|
test('returns exact overage when required equals cap + 1', () => {
|
|
// cap = (1000 * 2) / 10 = 200; required = 100 + 101 = 201; delta = 1
|
|
expect(calculateSnatchShortfall(100n, 101n, 1000n, 2n, 10n)).toBe(1n);
|
|
});
|
|
|
|
test('returns zero when required equals cap exactly', () => {
|
|
// cap = (1000 * 2) / 10 = 200; required = 100 + 100 = 200; delta = 0
|
|
expect(calculateSnatchShortfall(100n, 100n, 1000n, 2n, 10n)).toBe(0n);
|
|
});
|
|
|
|
test('uses default cap numerator/denominator when not provided', () => {
|
|
// defaults: capNumerator=2n, capDenominator=10n
|
|
// cap = (1000 * 2) / 10 = 200; outstanding=100, desired=50 -> required=150 <= 200 -> 0
|
|
expect(calculateSnatchShortfall(100n, 50n, 1000n)).toBe(0n);
|
|
});
|
|
|
|
test('throws when capDenominator is zero', () => {
|
|
expect(() => calculateSnatchShortfall(100n, 50n, 1000n, 2n, 0n)).toThrow('capDenominator must be greater than zero');
|
|
});
|
|
});
|
|
|
|
describe('isPositionDelinquent', () => {
|
|
test('respects tax rate windows', () => {
|
|
const now = 1_000_000;
|
|
const taxRate = 0.5; // 50%
|
|
const windowSeconds = (365 * 24 * 60 * 60) / taxRate;
|
|
|
|
expect(isPositionDelinquent(now - windowSeconds + 1, taxRate, now)).toBe(false);
|
|
expect(isPositionDelinquent(now - windowSeconds - 10, taxRate, now)).toBe(true);
|
|
expect(isPositionDelinquent(now, 0, now)).toBe(false);
|
|
});
|
|
|
|
test('returns false for zero tax rate', () => {
|
|
expect(isPositionDelinquent(0, 0, 1_000_000)).toBe(false);
|
|
});
|
|
|
|
test('returns false for negative tax rate', () => {
|
|
expect(isPositionDelinquent(0, -0.1, 1_000_000)).toBe(false);
|
|
});
|
|
|
|
test('returns true when far past the allowance window', () => {
|
|
// taxRate=1.0 -> allowance = 31_536_000s (1 year); use now > 31_536_000
|
|
const now = 100_000_000;
|
|
const lastTax = 0;
|
|
const taxRate = 1.0; // 100% per year = 1 year window
|
|
expect(isPositionDelinquent(lastTax, taxRate, now)).toBe(true);
|
|
});
|
|
|
|
test('uses current time as default when referenceTimestamp not provided', () => {
|
|
// taxRate=1.0 -> allowance = 1 year; 2 years ago is definitely delinquent
|
|
const twoYearsAgoSeconds = Math.floor(Date.now() / 1000) - 2 * 365 * 24 * 60 * 60;
|
|
expect(isPositionDelinquent(twoYearsAgoSeconds, 1.0)).toBe(true);
|
|
});
|
|
});
|