diff --git a/ts/config/protocols/dex/hybra.toml b/ts/config/protocols/dex/hybra.toml index c449168..6dcdc5a 100644 --- a/ts/config/protocols/dex/hybra.toml +++ b/ts/config/protocols/dex/hybra.toml @@ -22,7 +22,9 @@ v2_factory = "0x9c7397c9C5ecC400992843408D3A283fE9108009" voter = "0x5623F012d15EB828C12fe32e46d40AdC2A9e4FA3" ve_token = "0xAE6D5FcE541216BDA471D311425B5412D9f1DEb9" minter = "0xA8265e40e4Cdf6DB345861F4FCb75F9CC63E149b" -rewards_distributor = "0x04FCae9aF38e79B7bb82C6491BDACC2431F0e" +# rewards_distributor: removed 2026-05-07 — the previously-listed address +# was malformed (37 hex chars after 0x, should be 40) and no adapter code +# referenced it. Re-add with the correct address when it's actually wired in. gauge_factory_cl = "0xeB60888176d0c6AF4C539D64b2E83E470a63e4F9" gauge_manager = "0x742CAA5bA7C92ca6CFeBfD0e73c21739b3b65d5e" bribe_factory = "0x2555F79AC6e8096c755096e3A8d175a4Bf5fC82F" diff --git a/ts/config/protocols/dex/nest.toml b/ts/config/protocols/dex/nest.toml index bb79f2e..c345abf 100644 --- a/ts/config/protocols/dex/nest.toml +++ b/ts/config/protocols/dex/nest.toml @@ -67,7 +67,8 @@ address = "0x45FbF9786cDBDE9E940620F4Af0eb42B76848d17" token0 = "USDH" token1 = "WHYPE" tick_spacing = 10 -gauge = "0xDb57bF01A22D11F2c5eE6B7b4E5e2d36f6B34E6" +# gauge: removed 2026-05-07 — previously listed address was malformed +# (39 hex chars after 0x, should be 40); nest is is_active=false anyway. [[protocol.pools]] name = "WHYPE/kHYPE" @@ -83,7 +84,7 @@ address = "0x36De5262ADe55357A15226B85e2f20622D6eB203" token0 = "WHYPE" token1 = "PURR" tick_spacing = 50 -gauge = "0xb744C8Aa4F2f5E6B70c7Ab7A6A4F67a8C7B270F" +# gauge: removed 2026-05-07 — malformed address (39 hex chars). [[protocol.pools]] name = "WHYPE/wstHYPE" @@ -91,7 +92,7 @@ address = "0x728C8E4E89472B96Bd2B830438E0CB458F141db9" token0 = "WHYPE" token1 = "wstHYPE" tick_spacing = 1 -gauge = "0xcF5848f2D4e1a5cF2c8Ee8B72cB3E0fA1D7c1d9" +# gauge: removed 2026-05-07 — malformed address (39 hex chars). [[protocol.pools]] name = "WHYPE/LHYPE" @@ -123,4 +124,4 @@ address = "0xb09a299E9f7D333420d347EEBE0456Cb0f8545d5" token0 = "USDH" token1 = "USDT0" tick_spacing = 1 -gauge = "0x5A6D3E43c7F2F5C5d4EbA2b8A7cF8e6D1D1B2c1" +# gauge: removed 2026-05-07 — malformed address (39 hex chars). diff --git a/ts/packages/defi-cli/src/qa/address-shape.test.ts b/ts/packages/defi-cli/src/qa/address-shape.test.ts new file mode 100644 index 0000000..e7c1200 --- /dev/null +++ b/ts/packages/defi-cli/src/qa/address-shape.test.ts @@ -0,0 +1,88 @@ +// Address shape guard. +// +// Every "0x..." literal in the bundled config (chains.toml, tokens/*.toml, +// protocols/**/*.toml) must be exactly `0x` + 40 hex chars (20 bytes). +// On 2026-05-07 a sweep found five malformed entries that had been live +// for weeks: +// - protocols/dex/hybra.toml: rewards_distributor (37 hex chars) +// - protocols/dex/nest.toml: 4× pool gauge fields (39 hex chars each) +// `defi --chain hyperevm status --verify` flagged the hybra one as +// `NO_CODE` because it slipped past the 42-length check that gates +// `isPlaceholder()`. The four nest entries flew under the radar +// because nest is `is_active = false` and its protocol is filtered +// out by `getProtocolsForChain()` before status verifies anything. +// +// This test scans the raw TOML bytes directly so we catch shape +// regressions even on inactive entries. +import { readFileSync, readdirSync, statSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PKG_ROOT = resolve(__dirname, "../.."); +const TS_ROOT = resolve(PKG_ROOT, "../.."); +const CONFIG_DIR = resolve(TS_ROOT, "config"); + +function walkToml(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir)) { + const p = join(dir, entry); + const st = statSync(p); + if (st.isDirectory()) out.push(...walkToml(p)); + else if (p.endsWith(".toml")) out.push(p); + } + return out; +} + +// Match `"0x"` literals. Capturing group 1 is the address. +const ADDR_RE = /"(0x[0-9a-fA-F]+)"/g; + +interface Offender { + file: string; + line: number; + address: string; +} + +function findMalformedAddresses(): Offender[] { + const tomls = walkToml(CONFIG_DIR); + const out: Offender[] = []; + for (const file of tomls) { + const lines = readFileSync(file, "utf8").split("\n"); + for (let i = 0; i < lines.length; i++) { + ADDR_RE.lastIndex = 0; + let m: RegExpExecArray | null; + while ((m = ADDR_RE.exec(lines[i]))) { + const addr = m[1]; + if (addr.length !== 42) { + out.push({ file: file.replace(CONFIG_DIR + "/", ""), line: i + 1, address: addr }); + } + } + } + } + return out; +} + +describe("config TOML address shape", () => { + it("every \"0x...\" literal in config/ is exactly 42 chars (0x + 40 hex)", () => { + const offenders = findMalformedAddresses(); + const lines = offenders.map( + (o) => `${o.file}:${o.line} ${o.address} (length=${o.address.length}, expected=42)`, + ); + expect( + offenders, + "Malformed addresses found in config TOML — fix or remove:\n" + lines.join("\n"), + ).toEqual([]); + }); + + it("the regex itself catches the canonical bad shape (sanity)", () => { + // Self-test: feed the regex an artificial 41-char address and confirm + // the test would have rejected it. Guards against the regex silently + // becoming permissive after future refactors. + const sample = '"0x1234567890abcdef1234567890abcdef1234567"'; // 41 hex chars + ADDR_RE.lastIndex = 0; + const m = ADDR_RE.exec(sample); + expect(m).not.toBeNull(); + expect(m![1].length).not.toBe(42); + }); +});