Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ts/config/protocols/dex/hybra.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 5 additions & 4 deletions ts/config/protocols/dex/nest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -83,15 +84,15 @@ 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"
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"
Expand Down Expand Up @@ -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).
88 changes: 88 additions & 0 deletions ts/packages/defi-cli/src/qa/address-shape.test.ts
Original file line number Diff line number Diff line change
@@ -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<hex>"` 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);
});
});