Wire-format codec IDs unchanged. CLI aliases deleted; chain is the command. No backwards-compat shims, no deprecation comments.
193 lines
8.4 KiB
TypeScript
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)
|
|
}
|
|
})
|
|
})
|