Files
universe/e2e/12-staking-rpc.spec.ts
Hanzo AI 87f523f69b scrub: subnet/l2 → chain (canonical vocabulary, forward-only)
Wire-format codec IDs unchanged. CLI aliases deleted; chain is the
command. No backwards-compat shims, no deprecation comments.
2026-05-31 15:16:45 -07:00

193 lines
8.4 KiB
TypeScript

import { test, expect } from '@playwright/test'
import {
getUrls,
rpcCall,
getCurrentValidators,
} from './helpers'
const urls = getUrls()
// ---------------------------------------------------------------------------
// Staking Operations — P-Chain RPC queries beyond basic validator listing
// ---------------------------------------------------------------------------
// ── Reward UTXOs ───────────────────────────────────────────────────
test.describe('Staking RPC — Reward UTXOs', () => {
test('platform.getRewardUTXOs returns valid response', async ({ request }) => {
try {
const result = await rpcCall(
request, urls.rpcPlatform, 'platform.getRewardUTXOs', {
txID: '0x0000000000000000000000000000000000000000000000000000000000000000',
encoding: 'hex',
},
) as { utxos: unknown[]; numFetched: string; encoding: string }
console.log(` Reward UTXOs fetched: ${result.numFetched || 0}`)
expect(result).toBeDefined()
// Zero txID should return empty set — confirms endpoint works
expect(Array.isArray(result.utxos)).toBe(true)
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err)
console.log(` getRewardUTXOs: ${msg}`)
// Method may not be publicly exposed — acceptable
expect(msg.length).toBeGreaterThan(0)
}
})
})
// ── Current Supply ─────────────────────────────────────────────────
test.describe('Staking RPC — Current Supply', () => {
test('platform.getCurrentSupply returns supply > 0', async ({ request }) => {
const result = await rpcCall(
request, urls.rpcPlatform, 'platform.getCurrentSupply', {},
) as { supply: string }
const supply = BigInt(result.supply)
console.log(` Current supply: ${supply.toLocaleString()}`)
expect(supply).toBeGreaterThan(0n)
})
})
// ── Validators At Height ───────────────────────────────────────────
test.describe('Staking RPC — Validators At Height', () => {
test('platform.getValidatorsAt returns validators at current height', async ({ request }) => {
// First get current height
const heightResult = await rpcCall(
request, urls.rpcPlatform, 'platform.getHeight', {},
) as { height: string }
const height = Number(heightResult.height)
console.log(` P-Chain height: ${height}`)
try {
const result = await rpcCall(
request, urls.rpcPlatform, 'platform.getValidatorsAt', {
height: heightResult.height,
netID: '',
},
) as { validators: Record<string, unknown> }
const validatorCount = Object.keys(result.validators || {}).length
console.log(` Validators at height ${height}: ${validatorCount}`)
expect(validatorCount).toBeGreaterThanOrEqual(5)
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err)
console.log(` getValidatorsAt not available: ${msg}`)
// Method may not be exposed on public endpoint
}
})
})
// ── Delegation Fee and Parameters ──────────────────────────────────
test.describe('Staking RPC — Delegation Parameters', () => {
test('min delegation stake < min validator stake', async ({ request }) => {
const result = await rpcCall(
request, urls.rpcPlatform, 'platform.getMinStake', {},
) as { minValidatorStake: string; minDelegatorStake: string }
const valStake = BigInt(result.minValidatorStake)
const delStake = BigInt(result.minDelegatorStake)
console.log(` Min validator stake: ${valStake}`)
console.log(` Min delegator stake: ${delStake}`)
expect(delStake).toBeLessThanOrEqual(valStake)
expect(delStake).toBeGreaterThan(0n)
})
test('all validators have valid delegation fee percentage', async ({ request }) => {
const validators = await getCurrentValidators(request, urls.rpcPlatform) as {
nodeID: string
delegationFee: string
}[]
expect(validators.length).toBeGreaterThan(0)
for (const v of validators) {
const fee = parseFloat(v.delegationFee)
console.log(` ${v.nodeID} delegationFee=${fee}%`)
// Delegation fee must be between 0% and 100%
expect(fee).toBeGreaterThanOrEqual(0)
expect(fee).toBeLessThanOrEqual(100)
}
})
test('max validator count is reasonable', async ({ request }) => {
const validators = await getCurrentValidators(request, urls.rpcPlatform) as { nodeID: string }[]
console.log(` Current validator count: ${validators.length}`)
// Should have at least genesis validators and not exceed unreasonable limits
expect(validators.length).toBeGreaterThanOrEqual(5)
expect(validators.length).toBeLessThan(10_000)
})
})
// ── Staking Period ─────────────────────────────────────────────────
test.describe('Staking RPC — Staking Period Validation', () => {
test('all validators have valid start/end times', async ({ request }) => {
const validators = await getCurrentValidators(request, urls.rpcPlatform) as {
nodeID: string
startTime: string
endTime: string
}[]
expect(validators.length).toBeGreaterThan(0)
const now = Math.floor(Date.now() / 1000)
for (const v of validators) {
const start = Number(v.startTime)
const end = Number(v.endTime)
expect(start).toBeGreaterThan(0)
expect(end).toBeGreaterThan(start)
// End time should be in the future for active validators
expect(end).toBeGreaterThan(now)
console.log(` ${v.nodeID} start=${new Date(start * 1000).toISOString()} end=${new Date(end * 1000).toISOString()}`)
}
})
test('no validator has already expired', async ({ request }) => {
const validators = await getCurrentValidators(request, urls.rpcPlatform) as {
nodeID: string
endTime: string
}[]
const now = Math.floor(Date.now() / 1000)
const expired = validators.filter(v => Number(v.endTime) < now)
console.log(` Expired validators: ${expired.length}/${validators.length}`)
expect(expired.length).toBe(0)
})
})
// ── Chain Validators ──────────────────────────────────────────────
test.describe('Staking RPC — Chain Validators', () => {
test('can query chain validators (if chains exist)', async ({ request }) => {
// Get list of nets
try {
const result = await rpcCall(
request, urls.rpcPlatform, 'platform.getNets', { ids: [] },
) as { nets: { id: string; controlKeys: string[]; threshold: string }[] }
console.log(` Nets: ${result.nets?.length ?? 0}`)
if (result.nets && result.nets.length > 0) {
for (const net of result.nets.slice(0, 3)) {
console.log(` Net ${net.id}: threshold=${net.threshold}`)
}
}
expect(result.nets).toBeDefined()
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err)
console.log(` getNets: ${msg}`)
}
})
})
// ── Total Stake ────────────────────────────────────────────────────
test.describe('Staking RPC — Total Stake', () => {
test('platform.getTotalStake returns > 0', async ({ request }) => {
try {
const result = await rpcCall(
request, urls.rpcPlatform, 'platform.getTotalStake', { netID: '' },
) as { stake: string; weight: string }
const stake = BigInt(result.stake || result.weight || '0')
console.log(` Total stake: ${stake}`)
expect(stake).toBeGreaterThan(0n)
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err)
console.log(` getTotalStake: ${msg}`)
// Fallback: sum validator weights
const validators = await getCurrentValidators(request, urls.rpcPlatform) as { weight: string }[]
const totalWeight = validators.reduce((sum, v) => sum + BigInt(v.weight), 0n)
console.log(` Total weight (from validators): ${totalWeight}`)
expect(totalWeight).toBeGreaterThan(0n)
}
})
})