238 lines
9.1 KiB
TypeScript
238 lines
9.1 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
import { calculateActivePositionProfit, calculateClosedPositionProfit } from '../position.js';
|
|
|
|
describe('position profit calculations', () => {
|
|
describe('calculateActivePositionProfit', () => {
|
|
test('calculates profit correctly for active position with 10% share', () => {
|
|
const totalSupplyInit = 1000000n * 10n ** 18n; // 1M tokens
|
|
const currentTotalSupply = 1100000n * 10n ** 18n; // 1.1M tokens (100k new)
|
|
const positionShare = 0.1; // 10%
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
// Expected: (1,100,000 - 1,000,000) * 0.1 = 10,000 tokens
|
|
expect(profit).toBeCloseTo(10000, 2);
|
|
});
|
|
|
|
test('returns zero when no new issuance', () => {
|
|
const totalSupplyInit = 1000000n * 10n ** 18n;
|
|
const currentTotalSupply = 1000000n * 10n ** 18n; // Same supply
|
|
const positionShare = 0.5;
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
expect(profit).toBe(0);
|
|
});
|
|
|
|
test('returns zero when current supply is less than initial (should not happen)', () => {
|
|
const totalSupplyInit = 1000000n * 10n ** 18n;
|
|
const currentTotalSupply = 900000n * 10n ** 18n; // Decreased supply
|
|
const positionShare = 0.2;
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
expect(profit).toBe(0);
|
|
});
|
|
|
|
test('calculates profit correctly for small share', () => {
|
|
const totalSupplyInit = 1000000n * 10n ** 18n;
|
|
const currentTotalSupply = 1500000n * 10n ** 18n; // 500k new tokens
|
|
const positionShare = 0.001; // 0.1%
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
// Expected: 500,000 * 0.001 = 500 tokens
|
|
expect(profit).toBeCloseTo(500, 2);
|
|
});
|
|
|
|
test('calculates profit correctly for large share', () => {
|
|
const totalSupplyInit = 500000n * 10n ** 18n;
|
|
const currentTotalSupply = 750000n * 10n ** 18n; // 250k new tokens
|
|
const positionShare = 0.8; // 80%
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
// Expected: 250,000 * 0.8 = 200,000 tokens
|
|
expect(profit).toBeCloseTo(200000, 2);
|
|
});
|
|
|
|
test('handles very large supply values', () => {
|
|
const totalSupplyInit = 1000000000n * 10n ** 18n; // 1 billion tokens
|
|
const currentTotalSupply = 1100000000n * 10n ** 18n; // 100M new
|
|
const positionShare = 0.05; // 5%
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
// Expected: 100,000,000 * 0.05 = 5,000,000 tokens
|
|
expect(profit).toBeCloseTo(5000000, 2);
|
|
});
|
|
|
|
test('throws error for negative supply values', () => {
|
|
expect(() => {
|
|
calculateActivePositionProfit(-100n, 200n, 0.5);
|
|
}).toThrow('Supply values must be non-negative');
|
|
|
|
expect(() => {
|
|
calculateActivePositionProfit(100n, -200n, 0.5);
|
|
}).toThrow('Supply values must be non-negative');
|
|
});
|
|
|
|
test('throws error for invalid position share', () => {
|
|
const supply = 1000n * 10n ** 18n;
|
|
|
|
expect(() => {
|
|
calculateActivePositionProfit(supply, supply, -0.1);
|
|
}).toThrow('Position share must be between 0 and 1');
|
|
|
|
expect(() => {
|
|
calculateActivePositionProfit(supply, supply, 1.5);
|
|
}).toThrow('Position share must be between 0 and 1');
|
|
});
|
|
|
|
test('handles edge case of 100% share', () => {
|
|
const totalSupplyInit = 100000n * 10n ** 18n;
|
|
const currentTotalSupply = 150000n * 10n ** 18n; // 50k new tokens
|
|
const positionShare = 1.0; // 100%
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
// Expected: all new issuance goes to this position
|
|
expect(profit).toBeCloseTo(50000, 2);
|
|
});
|
|
|
|
test('handles edge case of 0% share', () => {
|
|
const totalSupplyInit = 100000n * 10n ** 18n;
|
|
const currentTotalSupply = 150000n * 10n ** 18n;
|
|
const positionShare = 0; // 0%
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
expect(profit).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('calculateClosedPositionProfit', () => {
|
|
test('calculates profit correctly for closed position with 15% share', () => {
|
|
const totalSupplyInit = 800000n * 10n ** 18n; // 800k tokens
|
|
const totalSupplyEnd = 1000000n * 10n ** 18n; // 1M tokens (200k new)
|
|
const positionShare = 0.15; // 15%
|
|
|
|
const profit = calculateClosedPositionProfit(totalSupplyInit, totalSupplyEnd, positionShare);
|
|
|
|
// Expected: (1,000,000 - 800,000) * 0.15 = 30,000 tokens
|
|
expect(profit).toBeCloseTo(30000, 2);
|
|
});
|
|
|
|
test('returns zero when no issuance during position lifetime', () => {
|
|
const totalSupplyInit = 500000n * 10n ** 18n;
|
|
const totalSupplyEnd = 500000n * 10n ** 18n; // Same supply
|
|
const positionShare = 0.3;
|
|
|
|
const profit = calculateClosedPositionProfit(totalSupplyInit, totalSupplyEnd, positionShare);
|
|
|
|
expect(profit).toBe(0);
|
|
});
|
|
|
|
test('returns zero when end supply is less than initial (should not happen)', () => {
|
|
const totalSupplyInit = 1000000n * 10n ** 18n;
|
|
const totalSupplyEnd = 900000n * 10n ** 18n; // Decreased supply
|
|
const positionShare = 0.25;
|
|
|
|
const profit = calculateClosedPositionProfit(totalSupplyInit, totalSupplyEnd, positionShare);
|
|
|
|
expect(profit).toBe(0);
|
|
});
|
|
|
|
test('calculates profit for short-lived position with rapid issuance', () => {
|
|
const totalSupplyInit = 1000000n * 10n ** 18n;
|
|
const totalSupplyEnd = 2000000n * 10n ** 18n; // Doubled during lifetime
|
|
const positionShare = 0.05; // 5%
|
|
|
|
const profit = calculateClosedPositionProfit(totalSupplyInit, totalSupplyEnd, positionShare);
|
|
|
|
// Expected: 1,000,000 * 0.05 = 50,000 tokens
|
|
expect(profit).toBeCloseTo(50000, 2);
|
|
});
|
|
|
|
test('handles very small issuance amounts', () => {
|
|
const totalSupplyInit = 1000000n * 10n ** 18n;
|
|
const totalSupplyEnd = 1000100n * 10n ** 18n; // Only 100 new tokens
|
|
const positionShare = 0.1; // 10%
|
|
|
|
const profit = calculateClosedPositionProfit(totalSupplyInit, totalSupplyEnd, positionShare);
|
|
|
|
// Expected: 100 * 0.1 = 10 tokens
|
|
expect(profit).toBeCloseTo(10, 2);
|
|
});
|
|
|
|
test('throws error for negative supply values', () => {
|
|
expect(() => {
|
|
calculateClosedPositionProfit(-100n, 200n, 0.5);
|
|
}).toThrow('Supply values must be non-negative');
|
|
|
|
expect(() => {
|
|
calculateClosedPositionProfit(100n, -200n, 0.5);
|
|
}).toThrow('Supply values must be non-negative');
|
|
});
|
|
|
|
test('throws error for invalid position share', () => {
|
|
const supply = 1000n * 10n ** 18n;
|
|
|
|
expect(() => {
|
|
calculateClosedPositionProfit(supply, supply, -0.1);
|
|
}).toThrow('Position share must be between 0 and 1');
|
|
|
|
expect(() => {
|
|
calculateClosedPositionProfit(supply, supply, 1.5);
|
|
}).toThrow('Position share must be between 0 and 1');
|
|
});
|
|
|
|
test('matches active position calculation for same parameters', () => {
|
|
const totalSupplyInit = 600000n * 10n ** 18n;
|
|
const totalSupplyEnd = 900000n * 10n ** 18n;
|
|
const positionShare = 0.2;
|
|
|
|
const closedProfit = calculateClosedPositionProfit(totalSupplyInit, totalSupplyEnd, positionShare);
|
|
const activeProfit = calculateActivePositionProfit(totalSupplyInit, totalSupplyEnd, positionShare);
|
|
|
|
// Both should calculate the same profit for the same supply range
|
|
expect(closedProfit).toBeCloseTo(activeProfit, 2);
|
|
});
|
|
});
|
|
|
|
describe('real-world scenarios', () => {
|
|
test('example from CollapseActive component (seed data)', () => {
|
|
// Based on typical seed data: ~3M initial supply, position share ~0.03
|
|
const totalSupplyInit = 3000000n * 10n ** 18n;
|
|
const currentTotalSupply = 3150000n * 10n ** 18n; // 150k new tokens
|
|
const positionShare = 0.03; // 3%
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
// Expected: 150,000 * 0.03 = 4,500 tokens
|
|
expect(profit).toBeCloseTo(4500, 2);
|
|
});
|
|
|
|
test('high tax rate position with small share', () => {
|
|
const totalSupplyInit = 2500000n * 10n ** 18n;
|
|
const currentTotalSupply = 3000000n * 10n ** 18n; // 500k new
|
|
const positionShare = 0.005; // 0.5% (high tax rate = small share)
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
// Expected: 500,000 * 0.005 = 2,500 tokens
|
|
expect(profit).toBeCloseTo(2500, 2);
|
|
});
|
|
|
|
test('low tax rate position with large share', () => {
|
|
const totalSupplyInit = 1500000n * 10n ** 18n;
|
|
const currentTotalSupply = 1800000n * 10n ** 18n; // 300k new
|
|
const positionShare = 0.4; // 40% (low tax rate = large share)
|
|
|
|
const profit = calculateActivePositionProfit(totalSupplyInit, currentTotalSupply, positionShare);
|
|
|
|
// Expected: 300,000 * 0.4 = 120,000 tokens
|
|
expect(profit).toBeCloseTo(120000, 2);
|
|
});
|
|
});
|
|
});
|