Files

343 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest'
import {
LUX_MAINNET_CONTRACTS,
LUX_TESTNET_CONTRACTS,
LUX_DEVNET_CONTRACTS,
LUX_DEV_CONTRACTS,
HANZO_MAINNET_CONTRACTS,
HANZO_TESTNET_CONTRACTS,
HANZO_DEVNET_CONTRACTS,
ZOO_MAINNET_CONTRACTS,
ZOO_TESTNET_CONTRACTS,
ZOO_DEVNET_CONTRACTS,
PARS_MAINNET_CONTRACTS,
PARS_TESTNET_CONTRACTS,
PARS_DEVNET_CONTRACTS,
SPC_MAINNET_CONTRACTS,
SPC_TESTNET_CONTRACTS,
SPC_DEVNET_CONTRACTS,
OSAGE_MAINNET_CONTRACTS,
OSAGE_TESTNET_CONTRACTS,
OSAGE_DEVNET_CONTRACTS,
DEX_PRECOMPILES,
PQ_CRYPTO_PRECOMPILES,
PENDING,
getContracts,
isContractDeployed,
} from '../src/contracts/addresses'
// ---- helpers ----
const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
function allAddresses(obj: Record<string, string>): string[] {
return Object.values(obj)
}
function addressEntries(obj: Record<string, string>): [string, string][] {
return Object.entries(obj)
}
// Known EMPTY legacy addresses from DEPLOYMENTS.md -- these should NOT appear
// in the current production contract set.
const KNOWN_EMPTY_LEGACY = [
'0xd9a95609DbB228A13568Bd9f9A285105E7596970',
'0x1F6cbC7d3bc7D803ee76D80F0eEE25767431e674',
'0xb732BD88F25EdD9C3456638671fB37685D4B4e3f',
]
const ALL_CHAIN_SETS: [string, Record<string, string>][] = [
// Lux primary C-Chain
['LUX_MAINNET_CONTRACTS', LUX_MAINNET_CONTRACTS],
['LUX_TESTNET_CONTRACTS', LUX_TESTNET_CONTRACTS],
['LUX_DEVNET_CONTRACTS', LUX_DEVNET_CONTRACTS],
['LUX_DEV_CONTRACTS', LUX_DEV_CONTRACTS],
// Hanzo brand L1
['HANZO_MAINNET_CONTRACTS', HANZO_MAINNET_CONTRACTS],
['HANZO_TESTNET_CONTRACTS', HANZO_TESTNET_CONTRACTS],
['HANZO_DEVNET_CONTRACTS', HANZO_DEVNET_CONTRACTS],
// Zoo brand L1
['ZOO_MAINNET_CONTRACTS', ZOO_MAINNET_CONTRACTS],
['ZOO_TESTNET_CONTRACTS', ZOO_TESTNET_CONTRACTS],
['ZOO_DEVNET_CONTRACTS', ZOO_DEVNET_CONTRACTS],
// Pars brand L1
['PARS_MAINNET_CONTRACTS', PARS_MAINNET_CONTRACTS],
['PARS_TESTNET_CONTRACTS', PARS_TESTNET_CONTRACTS],
['PARS_DEVNET_CONTRACTS', PARS_DEVNET_CONTRACTS],
// SPC brand L1
['SPC_MAINNET_CONTRACTS', SPC_MAINNET_CONTRACTS],
['SPC_TESTNET_CONTRACTS', SPC_TESTNET_CONTRACTS],
['SPC_DEVNET_CONTRACTS', SPC_DEVNET_CONTRACTS],
// Osage brand L1
['OSAGE_MAINNET_CONTRACTS', OSAGE_MAINNET_CONTRACTS],
['OSAGE_TESTNET_CONTRACTS', OSAGE_TESTNET_CONTRACTS],
['OSAGE_DEVNET_CONTRACTS', OSAGE_DEVNET_CONTRACTS],
]
// ---- 1. Every chain contract set is a non-empty object ----
describe('contract set exports', () => {
it.each([
...ALL_CHAIN_SETS,
['DEX_PRECOMPILES', DEX_PRECOMPILES],
['PQ_CRYPTO_PRECOMPILES', PQ_CRYPTO_PRECOMPILES],
])('%s is a non-empty object', (_name, contracts) => {
expect(contracts).toBeDefined()
expect(typeof contracts).toBe('object')
expect(Object.keys(contracts).length).toBeGreaterThan(0)
})
})
// ---- 2. getContracts() returns correct set for every supported chain ID ----
describe('getContracts()', () => {
it.each([
// Lux primary C-Chain
[96369, LUX_MAINNET_CONTRACTS],
[96368, LUX_TESTNET_CONTRACTS],
[96370, LUX_DEVNET_CONTRACTS],
[1337, LUX_DEV_CONTRACTS],
// Hanzo brand L1
[36963, HANZO_MAINNET_CONTRACTS],
[36964, HANZO_TESTNET_CONTRACTS],
// Zoo brand L1
[200200, ZOO_MAINNET_CONTRACTS],
[200201, ZOO_TESTNET_CONTRACTS],
[200202, ZOO_DEVNET_CONTRACTS],
// Pars brand L1 (post-2026-06-05 reset mainnet uses 7070)
[7070, PARS_MAINNET_CONTRACTS],
[494950, PARS_TESTNET_CONTRACTS],
[494951, PARS_DEVNET_CONTRACTS],
// SPC brand L1
[36911, SPC_MAINNET_CONTRACTS],
[36910, SPC_TESTNET_CONTRACTS],
[36912, SPC_DEVNET_CONTRACTS],
// Osage brand L1
[1872, OSAGE_MAINNET_CONTRACTS],
[1871, OSAGE_TESTNET_CONTRACTS],
[1873, OSAGE_DEVNET_CONTRACTS],
])('chain %i returns the correct contract set', (chainId, expected) => {
expect(getContracts(chainId)).toBe(expected)
})
// ---- 3. Unknown chain IDs fall back to mainnet (not undefined) ----
it.each([999, 0, -1, 12345, 1])('unknown chain %i returns LUX_MAINNET_CONTRACTS', (chainId) => {
const result = getContracts(chainId)
expect(result).toBe(LUX_MAINNET_CONTRACTS)
expect(result).toBeDefined()
})
})
// ---- 4. All addresses are valid hex (0x + 40 hex chars) ----
describe('address format validation', () => {
it.each([
...ALL_CHAIN_SETS,
['DEX_PRECOMPILES', DEX_PRECOMPILES],
['PQ_CRYPTO_PRECOMPILES', PQ_CRYPTO_PRECOMPILES],
])('%s has valid hex addresses (0x + 40 hex chars)', (_name, contracts) => {
for (const [key, addr] of addressEntries(contracts)) {
expect(addr, `${_name}.${key} = "${addr}"`).toMatch(ADDRESS_RE)
}
})
})
// ---- 5. No duplicate addresses within a single chain (excluding PENDING sentinels) ----
describe('no duplicate addresses within a chain', () => {
it.each([
...ALL_CHAIN_SETS,
['DEX_PRECOMPILES', DEX_PRECOMPILES],
])('%s has no duplicate non-PENDING addresses', (_name, contracts) => {
// PENDING (zero address) is a valid sentinel; multiple PENDING entries are
// expected on chains where contracts haven't been deployed yet.
const addrs = allAddresses(contracts)
.map((a) => a.toLowerCase())
.filter((a) => a !== ZERO_ADDRESS)
const unique = new Set(addrs)
const dupes = addrs.filter((a, i) => addrs.indexOf(a) !== i)
expect(dupes, `${_name} has duplicates: ${dupes.join(', ')}`).toHaveLength(0)
expect(unique.size).toBe(addrs.length)
})
})
// ---- 6. Mainnet addresses don't match known EMPTY legacy addresses ----
describe('mainnet addresses are not known-empty legacy', () => {
const legacyLower = KNOWN_EMPTY_LEGACY.map((a) => a.toLowerCase())
it('LUX_MAINNET_CONTRACTS contains no legacy EMPTY addresses', () => {
for (const [key, addr] of addressEntries(LUX_MAINNET_CONTRACTS)) {
expect(
legacyLower.includes(addr.toLowerCase()),
`LUX_MAINNET_CONTRACTS.${key} matches known-empty legacy address ${addr}`,
).toBe(false)
}
})
})
// ---- 7. DEX_PRECOMPILES addresses match canonical 0x04xx block ----
describe('DEX_PRECOMPILES canonical 0x04xx addresses', () => {
const EXPECTED_SUFFIXES: [string, string][] = [
['POOL_MANAGER', '0400'],
['SWAP_ROUTER', '0401'],
['HOOKS_REGISTRY', '0402'],
['FLASH_LOAN', '0403'],
['LENDING_POOL', '0410'],
['INTEREST_RATE', '0411'],
['LIQUIDATOR', '0412'],
['PERP_ENGINE', '0420'],
['FUNDING_RATE', '0421'],
['INSURANCE_FUND', '0422'],
['LIQUID_VAULT', '0430'],
['LIQUID_FX', '0431'],
['LIQUID_TOKEN', '0432'],
['YIELD_ROUTER', '0433'],
['TELEPORT_BRIDGE', '0440'],
['OMNICHAIN_ROUTER', '0441'],
]
it.each(EXPECTED_SUFFIXES)(
'DEX_PRECOMPILES.%s ends with 0x%s',
(name, suffix) => {
const addr = (DEX_PRECOMPILES as Record<string, string>)[name]
expect(addr).toBeDefined()
expect(addr.toLowerCase()).toBe(
`0x${'0'.repeat(40 - suffix.length)}${suffix}`,
)
},
)
it('all DEX precompile addresses have leading zeros (low address space)', () => {
for (const [key, addr] of addressEntries(DEX_PRECOMPILES)) {
const body = addr.slice(2, 32)
expect(body, `DEX_PRECOMPILES.${key} should have leading zeros`).toMatch(/^0+$/)
}
})
})
// ---- 7b. PQ_CRYPTO_PRECOMPILES match LP-4200 unified PQCrypto block ----
describe('PQ_CRYPTO_PRECOMPILES LP-4200 unified block', () => {
const EXPECTED: [string, string][] = [
['ML_KEM', '012201'],
['ML_DSA', '012202'],
['SLH_DSA', '012203'],
['PULSAR', '012204'],
['P3Q', '012205'],
['CORONA', '012206'],
['MAGNETAR', '012207'],
['HQC', '012208'],
]
it.each(EXPECTED)('PQ_CRYPTO_PRECOMPILES.%s = 0x..%s', (name, suffix) => {
const addr = (PQ_CRYPTO_PRECOMPILES as Record<string, string>)[name]
expect(addr).toBeDefined()
expect(addr.toLowerCase()).toBe(
`0x${'0'.repeat(40 - suffix.length)}${suffix}`,
)
})
it('PQ precompiles are non-empty, well-formed hex', () => {
for (const [key, addr] of addressEntries(PQ_CRYPTO_PRECOMPILES)) {
expect(addr, `PQ_CRYPTO_PRECOMPILES.${key} = "${addr}"`).toMatch(ADDRESS_RE)
}
})
})
// ---- 8. Chains with live AMM deployments have non-zero V2_FACTORY/V2_ROUTER ----
describe('chains with live AMM deployments', () => {
// Only chains where V2_FACTORY+V2_ROUTER are actually live. Post-reset brand
// chains are pending redeploy and use PENDING sentinels.
const SETS_WITH_LIVE_AMM: [string, Record<string, string>][] = [
['LUX_MAINNET_CONTRACTS', LUX_MAINNET_CONTRACTS],
['LUX_TESTNET_CONTRACTS', LUX_TESTNET_CONTRACTS],
['LUX_DEV_CONTRACTS', LUX_DEV_CONTRACTS],
['ZOO_MAINNET_CONTRACTS', ZOO_MAINNET_CONTRACTS],
]
it.each(SETS_WITH_LIVE_AMM)('%s V2_FACTORY is not the zero address', (_name, contracts) => {
expect(contracts.V2_FACTORY).toBeDefined()
expect(contracts.V2_FACTORY!.toLowerCase()).not.toBe(ZERO_ADDRESS)
})
it.each(SETS_WITH_LIVE_AMM)('%s V2_ROUTER is not the zero address', (_name, contracts) => {
expect(contracts.V2_ROUTER).toBeDefined()
expect(contracts.V2_ROUTER!.toLowerCase()).not.toBe(ZERO_ADDRESS)
})
})
// ---- 9. WLUX differs across networks where deployed ----
describe('WLUX differs between networks (where deployed)', () => {
it('mainnet WLUX is different from testnet WLUX', () => {
expect(LUX_MAINNET_CONTRACTS.WLUX.toLowerCase()).not.toBe(
LUX_TESTNET_CONTRACTS.WLUX.toLowerCase(),
)
})
it('mainnet WLUX is different from dev WLUX', () => {
expect(LUX_MAINNET_CONTRACTS.WLUX.toLowerCase()).not.toBe(
LUX_DEV_CONTRACTS.WLUX.toLowerCase(),
)
})
it('testnet WLUX is different from dev WLUX', () => {
expect(LUX_TESTNET_CONTRACTS.WLUX.toLowerCase()).not.toBe(
LUX_DEV_CONTRACTS.WLUX.toLowerCase(),
)
})
})
// ---- 10. PENDING sentinel + isContractDeployed predicate ----
describe('PENDING sentinel + isContractDeployed predicate', () => {
it('PENDING is the zero address', () => {
expect(PENDING).toBe(ZERO_ADDRESS)
})
it('isContractDeployed returns true for a live contract on Lux mainnet', () => {
expect(isContractDeployed(96369, 'WLUX')).toBe(true)
expect(isContractDeployed(96369, 'V2_ROUTER')).toBe(true)
})
it('isContractDeployed returns false for PENDING contracts', () => {
// Hanzo mainnet (36963) is post-reset; V2_ROUTER pending.
expect(isContractDeployed(36963, 'V2_ROUTER')).toBe(false)
expect(isContractDeployed(36963, 'WLUX')).toBe(false)
})
it('Hanzo mainnet MULTICALL is always live (canonical address)', () => {
expect(isContractDeployed(36963, 'MULTICALL')).toBe(true)
})
})
// ---- 11. Inventory coverage — every brand L1 × env is exported ----
describe('inventory coverage — 18 brand-L1 endpoints', () => {
it('exports 6 chain-contract sets per env (lux + 5 brands)', () => {
// Lux + Hanzo + Zoo + Pars + SPC + Osage = 6 per env
expect(getContracts(96369)).toBeDefined() // lux mainnet
expect(getContracts(36963)).toBeDefined() // hanzo mainnet
expect(getContracts(200200)).toBeDefined() // zoo mainnet
expect(getContracts(7070)).toBeDefined() // pars mainnet
expect(getContracts(36911)).toBeDefined() // spc mainnet
expect(getContracts(1872)).toBeDefined() // osage mainnet
expect(getContracts(96368)).toBeDefined() // lux testnet
expect(getContracts(36964)).toBeDefined() // hanzo testnet
expect(getContracts(200201)).toBeDefined() // zoo testnet
expect(getContracts(494950)).toBeDefined() // pars testnet
expect(getContracts(36910)).toBeDefined() // spc testnet
expect(getContracts(1871)).toBeDefined() // osage testnet
expect(getContracts(96370)).toBeDefined() // lux devnet
expect(getContracts(200202)).toBeDefined() // zoo devnet
expect(getContracts(494951)).toBeDefined() // pars devnet
expect(getContracts(36912)).toBeDefined() // spc devnet
expect(getContracts(1873)).toBeDefined() // osage devnet
})
})