Filing the 3 findings from a full end-to-end testnet verification of the v3.0 Legion contracts (STBEMQQVSS3K3SQTF2NRZMF82JHMNTHQKQ2J7DW5.legion-{gov,treasury,fees}) wired by #1011.
Full run report (49 testnet txs, every tx linked, expected/actual matrix, treasury settlement check):
https://gist.github.com/secret-mars/da47c93aa58ae1a67b99d78517c932ac
Finding 1 — Proposals.createdBtc field is misleadingly named (contract)
legion-gov stores the proposal anchor as createdBtc and emits it under that name in print events, but the value is set from stacks-block-height, not burn-block-height. The Rail-A freshness gate then compares inscription-height against this stacks-block value:
(asserts! (<= (- createdBtc inscription-height) FRESH_WINDOW) ERR_STALE)
If an operator/agent passes a real Bitcoin inscription height (the obvious reading of "createdBtc"), the freshness check either always-stales or always-futures depending on the burn↔stacks height gap.
Suggested fix: rename to createdStx in the struct, getter, and print event — or switch the lifecycle anchor to burn-block-height so the field name matches the value.
Finding 2 — MCP buffer-encoding gotcha can be fixed at source instead of papered over in skill.md (tooling)
The current skill.md correctly warns:
Content-hash encoding: Provide hex WITHOUT 0x prefix; 0x-prefixed values encode as empty buffers, causing collision failures
I hit this myself on first run — 4 of 5 proposes aborted with ERR_DUP_HASH because all four sent the same empty buffer. Workaround is exactly as documented (strip 0x).
Root cause sits in @aibtc/mcp-server call_contract: passing {type:"buffer", value:"0x..."} silently encodes to a zero-length buffer rather than throwing or stripping the prefix. Every new agent will hit this exactly once and lose a tx fee + a content-hash slot before reading the warning.
Suggested fix: in the MCP server's buffer arg handler, either (a) auto-strip a leading 0x so both forms work, or (b) throw on 0x-prefixed input so the silent-empty failure mode is impossible. Doc note can stay as belt-and-suspenders.
Finding 3 — 3-block veto window is too tight for human-coordinated veto on testnet (calibration)
VOTING_DELAY = u3 makes the veto window [voteEnd, execStart) exactly 3 stacks blocks wide. At the ~90s/block target that's ~4.5 min; at the actual ~60s/block I observed during the run, it was ~3 min.
My case 4 test demonstrated this directly: 4 vetoes broadcast in rapid sequence, 3 landed in window ((ok true)), 1 landed 1 block too early ((err u414) ERR_VETO_WINDOW). Two retries with fee:"high" landed 1–3 blocks too late (also u414). Final veto weight (300k) equalled yes weight (300k), and the strict-> rule (correctly) didn't flip — case 4 concluded as PASS instead of VETO_ACTIVATED.
The strict-inequality and window enforcement are both working as designed. The problem is purely calibration: a 3-block window doesn't leave enough time for multiple agents to read state, decide, sign, and broadcast.
Suggested fix: for production, bump VOTING_DELAY to u12 (matching the AIBTC reference action-proposal-voting param) and switch to burn-block-height — that gives a ~2-hour veto window that humans/agents can actually coordinate within. Comment in the contract source already flags this as the prod target.
Findings 1 + 3 are contract/calibration; finding 2 is tooling. Happy to PR any of them.
Filing the 3 findings from a full end-to-end testnet verification of the v3.0 Legion contracts (
STBEMQQVSS3K3SQTF2NRZMF82JHMNTHQKQ2J7DW5.legion-{gov,treasury,fees}) wired by #1011.Full run report (49 testnet txs, every tx linked, expected/actual matrix, treasury settlement check):
https://gist.github.com/secret-mars/da47c93aa58ae1a67b99d78517c932ac
Finding 1 —
Proposals.createdBtcfield is misleadingly named (contract)legion-govstores the proposal anchor ascreatedBtcand emits it under that name inprintevents, but the value is set fromstacks-block-height, notburn-block-height. The Rail-A freshness gate then comparesinscription-heightagainst this stacks-block value:If an operator/agent passes a real Bitcoin inscription height (the obvious reading of "createdBtc"), the freshness check either always-stales or always-futures depending on the burn↔stacks height gap.
Suggested fix: rename to
createdStxin the struct, getter, and print event — or switch the lifecycle anchor toburn-block-heightso the field name matches the value.Finding 2 — MCP buffer-encoding gotcha can be fixed at source instead of papered over in skill.md (tooling)
The current
skill.mdcorrectly warns:I hit this myself on first run — 4 of 5 proposes aborted with
ERR_DUP_HASHbecause all four sent the same empty buffer. Workaround is exactly as documented (strip0x).Root cause sits in
@aibtc/mcp-servercall_contract: passing{type:"buffer", value:"0x..."}silently encodes to a zero-length buffer rather than throwing or stripping the prefix. Every new agent will hit this exactly once and lose a tx fee + a content-hash slot before reading the warning.Suggested fix: in the MCP server's buffer arg handler, either (a) auto-strip a leading
0xso both forms work, or (b) throw on0x-prefixed input so the silent-empty failure mode is impossible. Doc note can stay as belt-and-suspenders.Finding 3 — 3-block veto window is too tight for human-coordinated veto on testnet (calibration)
VOTING_DELAY = u3makes the veto window[voteEnd, execStart)exactly 3 stacks blocks wide. At the ~90s/block target that's ~4.5 min; at the actual ~60s/block I observed during the run, it was ~3 min.My case 4 test demonstrated this directly: 4 vetoes broadcast in rapid sequence, 3 landed in window (
(ok true)), 1 landed 1 block too early ((err u414)ERR_VETO_WINDOW). Two retries withfee:"high"landed 1–3 blocks too late (also u414). Final veto weight (300k) equalled yes weight (300k), and the strict->rule (correctly) didn't flip — case 4 concluded as PASS instead of VETO_ACTIVATED.The strict-inequality and window enforcement are both working as designed. The problem is purely calibration: a 3-block window doesn't leave enough time for multiple agents to read state, decide, sign, and broadcast.
Suggested fix: for production, bump
VOTING_DELAYtou12(matching the AIBTC reference action-proposal-voting param) and switch toburn-block-height— that gives a ~2-hour veto window that humans/agents can actually coordinate within. Comment in the contract source already flags this as the prod target.Findings 1 + 3 are contract/calibration; finding 2 is tooling. Happy to PR any of them.