UNPKG

@wuwei-labs/srsly

Version:
143 lines 6.8 kB
import { describe, expect, it, vi } from 'vitest'; vi.mock('../accounts/contract', () => ({ fetchContract: vi.fn(), })); vi.mock('../accounts/config', () => ({ fetchConfig: vi.fn(), })); vi.mock('../utils/rpc', () => ({ createRpc: vi.fn(() => ({ getMinimumBalanceForRentExemption: (size) => ({ // Solana rent formula constants: ACCOUNT_STORAGE_OVERHEAD=128, // lamports_per_byte_year=3_480, exemption_threshold=2 years. send: async () => { const bytes = Number(size) + 128; return BigInt(bytes * 3_480 * 2); }, }), })), })); vi.mock('../utils/config', () => ({ getRpcUrl: () => 'http://localhost:8899', })); import { fetchContract } from '../accounts/contract'; import { fetchConfig } from '../accounts/config'; import { estimateOwnerSetupCost, estimateBorrowerSetupCost, estimateRentalCost, tickBudget, } from './estimate'; import { MINIMUM_THREAD_FUNDING, DEFAULT_EXECUTION_BUDGET, TX_BASE_FEE_LAMPORTS, PER_TICK_LAMPORTS, } from './constants'; import { CONTRACT_STATE_SIZE, RENTAL_STATE_SIZE, BORROWER_STATE_SIZE } from './sizes'; import { SPL_ATA_SIZE } from './constants'; // Rent formula used by our mock — matches Solana mainnet rent-exemption math. const rentFor = (size) => BigInt((size + 128) * 3_480 * 2); describe('estimateOwnerSetupCost', () => { it('baseline matches COSTS.md breakdown', async () => { const quote = await estimateOwnerSetupCost(); const contractStateRent = rentFor(CONTRACT_STATE_SIZE); const rentalStateRent = rentFor(RENTAL_STATE_SIZE); const ataRent = rentFor(SPL_ATA_SIZE); expect(quote.breakdown.contractState).toBe(contractStateRent); expect(quote.breakdown.rentalStateActive).toBe(rentalStateRent); expect(quote.breakdown.rentalStateQueued).toBe(rentalStateRent); expect(quote.breakdown.contractAta).toBe(ataRent); expect(quote.breakdown.ownerAta).toBe(ataRent); expect(quote.breakdown.threadInfraBase).toBe(MINIMUM_THREAD_FUNDING); expect(quote.breakdown.executionBudget).toBe(DEFAULT_EXECUTION_BUDGET); expect(quote.rentLamports).toBe(contractStateRent + rentalStateRent * 2n + ataRent + ataRent); expect(quote.threadLamports).toBe(MINIMUM_THREAD_FUNDING + DEFAULT_EXECUTION_BUDGET); expect(quote.txFees).toBe(TX_BASE_FEE_LAMPORTS * 2n); expect(quote.total).toBe(quote.rentLamports + quote.threadLamports + quote.txFees); }); it('ownerAtaExists skips owner ATA rent', async () => { const with_ = await estimateOwnerSetupCost(); const without = await estimateOwnerSetupCost({ ownerAtaExists: true }); const ataRent = rentFor(SPL_ATA_SIZE); expect(with_.total - without.total).toBe(ataRent); expect(without.breakdown.ownerAta).toBe(0n); }); it('custom executionBudget flows through to thread funding', async () => { const quote = await estimateOwnerSetupCost({ executionBudget: { sol: 0.01 } }); expect(quote.breakdown.executionBudget).toBe(10000000n); expect(quote.threadLamports).toBe(MINIMUM_THREAD_FUNDING + 10000000n); }); }); describe('estimateBorrowerSetupCost', () => { it('sums BorrowerState + managed ATA rent + tx fee', async () => { const quote = await estimateBorrowerSetupCost(); const borrowerStateRent = rentFor(BORROWER_STATE_SIZE); const ataRent = rentFor(SPL_ATA_SIZE); expect(quote.breakdown.borrowerState).toBe(borrowerStateRent); expect(quote.breakdown.managedAta).toBe(ataRent); expect(quote.rentLamports).toBe(borrowerStateRent + ataRent); expect(quote.txFees).toBe(TX_BASE_FEE_LAMPORTS); expect(quote.total).toBe(borrowerStateRent + ataRent + TX_BASE_FEE_LAMPORTS); }); }); describe('estimateRentalCost', () => { const mockContract = (rate) => ({ data: { rate }, address: 'Contract11111111111111111111111111111111111', }); const mockConfig = (overrides = {}) => ({ data: { stardustToAtlas: 100000000n, serviceFeeBps: 1000n, baseBps: 10_000, ...overrides, }, address: 'Config1111111111111111111111111111111111111', }); it('1 ATLAS/day × 86400s yields 100M Stardust escrow, 10M service fee', async () => { vi.mocked(fetchContract).mockResolvedValue(mockContract(1n)); vi.mocked(fetchConfig).mockResolvedValue(mockConfig()); const quote = await estimateRentalCost({ contract: 'AnyContract', durationSeconds: 86400n, }); expect(quote.atlasEscrow).toBe(100000000n); expect(quote.atlasServiceFee).toBe(10000000n); expect(quote.atlasBid).toBe(0n); expect(quote.atlasTotal).toBe(100000000n); expect(quote.serviceFeeBps).toBe(1_000); expect(quote.solTxFees).toBe(TX_BASE_FEE_LAMPORTS); }); it('rate in Stardust units (rate >= stardustToAtlas) is not rescaled', async () => { // 5 ATLAS/day already in Stardust = 500M. vi.mocked(fetchContract).mockResolvedValue(mockContract(500000000n)); vi.mocked(fetchConfig).mockResolvedValue(mockConfig()); const quote = await estimateRentalCost({ contract: 'AnyContract', durationSeconds: 86400n * 3n, // 3 days }); expect(quote.atlasEscrow).toBe(1500000000n); }); it('pro-rates sub-day durations via integer division', async () => { vi.mocked(fetchContract).mockResolvedValue(mockContract(1n)); vi.mocked(fetchConfig).mockResolvedValue(mockConfig()); // Half a day -> 50M Stardust. const quote = await estimateRentalCost({ contract: 'AnyContract', durationSeconds: 43200n, }); expect(quote.atlasEscrow).toBe(50000000n); expect(quote.atlasServiceFee).toBe(5000000n); }); it('bidStardust flows into atlasTotal but not into atlasEscrow', async () => { vi.mocked(fetchContract).mockResolvedValue(mockContract(1n)); vi.mocked(fetchConfig).mockResolvedValue(mockConfig()); const quote = await estimateRentalCost({ contract: 'AnyContract', durationSeconds: 86400n, bidStardust: 25000000n, }); expect(quote.atlasEscrow).toBe(100000000n); expect(quote.atlasBid).toBe(25000000n); expect(quote.atlasTotal).toBe(125000000n); }); }); describe('tickBudget', () => { it('scales by PER_TICK_LAMPORTS', () => { expect(tickBudget(365)).toBe(365n * PER_TICK_LAMPORTS); expect(tickBudget(0)).toBe(0n); expect(tickBudget(1000000n)).toBe(1000000n * PER_TICK_LAMPORTS); }); }); //# sourceMappingURL=estimate.test.js.map