UNPKG

filecoin-pin

Version:

Bridge IPFS content to Filecoin Onchain Cloud using familiar tools

183 lines (161 loc) 7.58 kB
import { TIME_CONSTANTS } from '@filoz/synapse-sdk' import { describe, expect, it } from 'vitest' import { computeAdjustmentForExactDays, computeAdjustmentForExactDaysWithPiece, computeAdjustmentForExactDeposit, computeTopUpForDuration, DEFAULT_LOCKUP_DAYS, type PaymentStatus, type ServiceApprovalStatus, } from '../../core/payments/index.js' function makeStatus(params: { filecoinPayBalance: bigint; lockupUsed?: bigint; rateUsed?: bigint }): PaymentStatus { const currentAllowances: ServiceApprovalStatus = { rateAllowance: 0n, lockupAllowance: 0n, lockupUsed: params.lockupUsed ?? 0n, rateUsed: params.rateUsed ?? 0n, maxLockupPeriod: BigInt(DEFAULT_LOCKUP_DAYS) * TIME_CONSTANTS.EPOCHS_PER_DAY, } return { network: 'calibration', address: '0x0000000000000000000000000000000000000000', filBalance: 0n, walletUsdfcBalance: 0n, filecoinPayBalance: params.filecoinPayBalance, currentAllowances, } } describe('computeTopUpForDuration', () => { it('returns 0 topUp when days <= 0', () => { const rateUsed = 1_000_000_000_000_000_000n // 1 USDFC/epoch const status = makeStatus({ filecoinPayBalance: 0n, rateUsed }) const res = computeTopUpForDuration(status, 0) expect(res.topUp).toBe(0n) expect(res.perDay).toBe(rateUsed * TIME_CONSTANTS.EPOCHS_PER_DAY) }) it('returns 0 topUp when rateUsed = 0', () => { const status = makeStatus({ filecoinPayBalance: 1_000n, rateUsed: 0n }) const res = computeTopUpForDuration(status, 10) expect(res.topUp).toBe(0n) expect(res.perDay).toBe(0n) }) it('returns 0 topUp when available already covers the period', () => { const rateUsed = 1_000_000_000_000_000_000n // 1 USDFC/epoch const perDay = rateUsed * TIME_CONSTANTS.EPOCHS_PER_DAY const days = 10 const filecoinPayBalance = perDay * BigInt(days) const status = makeStatus({ filecoinPayBalance, lockupUsed: 0n, rateUsed }) const res = computeTopUpForDuration(status, days) expect(res.topUp).toBe(0n) expect(res.available).toBe(filecoinPayBalance) }) it('returns required topUp when available is insufficient', () => { const rateUsed = 1_000_000_000_000_000_000n // 1 USDFC/epoch const perDay = rateUsed * TIME_CONSTANTS.EPOCHS_PER_DAY const days = 10 const available = perDay * 5n // only 5 days funded const lockupUsed = 0n const filecoinPayBalance = available + lockupUsed const status = makeStatus({ filecoinPayBalance, lockupUsed, rateUsed }) const res = computeTopUpForDuration(status, days) expect(res.topUp).toBe(perDay * 5n) expect(res.available).toBe(available) }) }) describe('computeAdjustmentForExactDays', () => { it('throws on negative days', () => { const status = makeStatus({ filecoinPayBalance: 0n, lockupUsed: 0n, rateUsed: 1n }) expect(() => computeAdjustmentForExactDays(status, -1)).toThrow('days must be non-negative') }) it('returns zeros when rateUsed is 0', () => { const status = makeStatus({ filecoinPayBalance: 1_000n, lockupUsed: 100n, rateUsed: 0n }) const res = computeAdjustmentForExactDays(status, 10) expect(res.delta).toBe(0n) expect(res.targetAvailable).toBe(0n) expect(res.available).toBe(900n) }) it('returns positive delta when more deposit needed (includes 1-hour safety)', () => { const rateUsed = 1_000_000_000_000_000_000n // 1 USDFC/epoch const perDay = rateUsed * TIME_CONSTANTS.EPOCHS_PER_DAY const days = 30 const available = perDay * 30n // exactly 30 days const status = makeStatus({ filecoinPayBalance: available, lockupUsed: 0n, rateUsed }) const res = computeAdjustmentForExactDays(status, days) const perHour = perDay / 24n const safety = perHour > 0n ? perHour : 1n expect(res.delta).toBe(safety) expect(res.targetAvailable).toBe(perDay * 30n + safety) }) it('returns negative delta when withdrawal possible', () => { const rateUsed = 1_000_000_000_000_000_000n // 1 USDFC/epoch const perDay = rateUsed * TIME_CONSTANTS.EPOCHS_PER_DAY const days = 5 const perHour = perDay / 24n const safety = perHour > 0n ? perHour : 1n const targetAvailable = perDay * BigInt(days) + safety const available = targetAvailable + 1_000n // a bit more than target const status = makeStatus({ filecoinPayBalance: available, lockupUsed: 0n, rateUsed }) const res = computeAdjustmentForExactDays(status, days) expect(res.delta).toBe(-1_000n) }) }) describe('computeAdjustmentForExactDeposit', () => { it('throws on negative target', () => { const status = makeStatus({ filecoinPayBalance: 0n, lockupUsed: 0n, rateUsed: 0n }) expect(() => computeAdjustmentForExactDeposit(status, -1n)).toThrow('target deposit cannot be negative') }) it('clamps target to lockup when target below locked funds', () => { const filecoinPayBalance = 1_000n const lockupUsed = 800n const status = makeStatus({ filecoinPayBalance, lockupUsed, rateUsed: 1n }) const res = computeAdjustmentForExactDeposit(status, 500n) expect(res.clampedTarget).toBe(lockupUsed) // Need to withdraw down to locked amount expect(res.delta).toBe(lockupUsed - filecoinPayBalance) // negative }) it('returns zero delta when already at target', () => { const filecoinPayBalance = 2_000n const lockupUsed = 500n const status = makeStatus({ filecoinPayBalance, lockupUsed, rateUsed: 1n }) const res = computeAdjustmentForExactDeposit(status, filecoinPayBalance) expect(res.delta).toBe(0n) expect(res.clampedTarget).toBe(filecoinPayBalance) }) it('returns positive delta when more deposit needed', () => { const filecoinPayBalance = 1_000n const lockupUsed = 100n const status = makeStatus({ filecoinPayBalance, lockupUsed, rateUsed: 1n }) const res = computeAdjustmentForExactDeposit(status, 1_500n) expect(res.delta).toBe(500n) expect(res.clampedTarget).toBe(1_500n) }) }) describe('computeAdjustmentForExactDaysWithPiece', () => { it('calculates deposit for new file when rateUsed is 0', () => { // Scenario: No existing storage, uploading first file const status = makeStatus({ filecoinPayBalance: 0n, lockupUsed: 0n, rateUsed: 0n }) const pieceSizeBytes = 1024 * 1024 * 1024 // 1 GiB const pricePerTiBPerEpoch = 1_000_000_000_000_000n // 0.001 USDFC per TiB per epoch const days = 30 const res = computeAdjustmentForExactDaysWithPiece(status, days, pieceSizeBytes, pricePerTiBPerEpoch) // Should require deposit for both lockup and runway expect(res.delta).toBeGreaterThan(0n) expect(res.newRateUsed).toBeGreaterThan(0n) expect(res.newLockupUsed).toBeGreaterThan(0n) }) it('adds file requirements to existing usage', () => { // Scenario: Existing storage, adding another file const rateUsed = 1_000_000_000_000_000_000n // 1 USDFC/epoch const lockupUsed = rateUsed * BigInt(30) * TIME_CONSTANTS.EPOCHS_PER_DAY // 30 days worth const filecoinPayBalance = (lockupUsed * 12n) / 10n // 20% buffer const status = makeStatus({ filecoinPayBalance, lockupUsed, rateUsed }) const pieceSizeBytes = 1024 * 1024 * 1024 // 1 GiB const pricePerTiBPerEpoch = 1_000_000_000_000_000n // 0.001 USDFC per TiB per epoch const days = 30 const res = computeAdjustmentForExactDaysWithPiece(status, days, pieceSizeBytes, pricePerTiBPerEpoch) // New rate should be higher than existing expect(res.newRateUsed).toBeGreaterThan(rateUsed) expect(res.newLockupUsed).toBeGreaterThan(lockupUsed) }) })