Skip to content

fix(harness): CollateralPosition PDA on both deposit paths + flow coverage (SOL-375/379/380) - #57

Merged
valthon merged 3 commits into
masterfrom
fix/SOL-375-379-380-opp-collateral
Aug 20, 2026
Merged

fix(harness): CollateralPosition PDA on both deposit paths + flow coverage (SOL-375/379/380)#57
valthon merged 3 commits into
masterfrom
fix/SOL-375-379-380-opp-collateral

Conversation

@valthon

@valthon valthon commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Important

Part of a three-repo set — all must merge together:

The harness deposit paths must pass the CollateralPosition PDA the program now
requires; without wire-solana#409 these calls have nowhere to send it, and without this
PR every harness collateral deposit fails against the new program.

Note

The port-lock changes were split out to #76 per review. This PR no longer touches
BindConfigProvider or fsUtils. It does still carry the jest testTimeout bump —
see "The timeout lives here" below for why that half came back. #76 now depends on this
PR
, not the other way round.

Harness side of the SOL-375/379/380 collateral work. Three commits.

Track collateral position PDAs in SOL flows

  • SolanaCollateralTool derives and passes the per-(operator, token_code)
    CollateralPosition PDA on both deposit paths.
  • TerminationScenario snapshots the position's rent, which is refunded on the
    full-drain close — so the SOL remit assertion is exact rather than off by the rent.
  • The per-token little-endian seed encoder that three call sites had each re-spelled is
    consolidated into one helper in slugUtils.
  • The outpost bootstrap pre-creates reserve_aggregate's ATA for every registered SPL
    collateral mint
    (new idempotent SolanaFundingTool.ensureAssociatedTokenAccount). An
    SPL SLASH settles into that ATA; the program never creates ATAs and a dropped seizure
    has no return attestation to re-drive it, so the destination must exist first (SOL-380).
    A real outpost deploy owes the same one-time step when it registers an SPL collateral
    token — the sole remaining production follow-up.

Log validator output; unblock commits on untrusted mise config

  • solana-test-validator's RUST_LOG defaults to an additive filter (agave's own
    solana=info,agave=info plus the program-log target), so on-chain msg!() reaches
    validator.log WITHOUT silencing agave's startup diagnostics — a bare program-log
    directive would replace agave's default and hide the panic/bind lines
    validatorLogTail() surfaces.
  • .husky/pre-commit falls through to a direct pnpm run when mise is present but cannot
    execute (a fresh clone whose mise config has not been trusted yet).

OMC style, lint, rules AND dedupe review

A four-reviewer pass (STYLE.md/eslint, manifest rules, duplication, plus two independent
second opinions); every finding was verified against the source before being applied.

start.sh no longer freezes build-host state. DaemonConfig carried
SolanaValidatorProcess.resolveEnv(), which reads the build host's RUST_LOG, and the
renderer emitted that frozen answer as an unconditional export. Both branches were
wrong: a build host with RUST_LOG set stripped the program-log target from the
published script entirely — the very defect the commit above exists to fix — and one
without it pinned a value the operator could not override. DaemonConfig now carries the
unconditional DefaultEnv, and the renderer emits
[ -n "${NAME:-}" ] || export NAME=<value> so the run-time environment still wins. (The
guarded form rather than ${NAME:-…}: bash re-parses quotes inside an expansion word, so
a value containing ' would break the script.) This restores the renderer's own
documented invariant — build-time conditionals render as SHELL, never frozen.

The ensureAssociatedTokenAccount test asserted nothing. It stubbed sendTransaction
while sendAndPoll submits via sendRawTransaction, so its write-count flag could never
flip — and the ATA-absent branch, the one SOL-380's argument rests on, had no coverage
at all. The stub now implements the methods the send path actually calls; both branches
are asserted.

Seeds and PDA derivation stop forking. reserve_aggregate was re-spelled in
SolanaCollateralTool while the same change consumed
SolanaOutpostBootstrapper.PdaSeed.ReserveAggregate two files over. CollateralPosition
and CollateralVault join that registry and the tool derives all three from it. The
findProgramAddressSync plumbing was triplicated behind three private one-liners across
two files — now one SolanaOutpostProgramTool.derivePda.

tokenCodeToLittleEndianBufferslugNameToLittleEndianBuffer, widened to
number | bigint. Five of its twelve call sites encode a reserve code, so the old
name misdescribed them, and the deleted predecessor already used the slugName stem. The
widening drops the BigInt(...) wrapper the bigint-only signature forced on callers
holding the number that SlugName.from returns.

Also: a dead Anchor Program load built solely to interpolate a failure message is gone,
along with the divergent program-id source it introduced; mintMockSplToUser and
ensureAssociatedTokenAccount share an instruction-returning helper so the former keeps
batching ATA creation with its mint in one transaction; the renderer walks
daemon.env once via the existing isNotEmpty predicate; tests added for the
DaemonConfig env wiring, both start.sh branches, and the number carrier; the
validator tests drop process.env mutation for resolveEnv's parameter; every touched
file passes prettier.

The timeout lives here

The jest ceiling (30s → 120s, root + cluster-tool) briefly moved to #76 and has come back,
because it is what a green suite actually needs and it is not part of the port-lock
change the reviewer asked to split out.

An earlier revision of this description claimed the ENOENT … wire-cluster-ports.lock.lock
failures were a second defect requiring #76. That was wrong. They are a consequence
of the undershot ceiling: a test killed at 30s dies inside withFileLock, jest tears the
suite down, the fixture removes its temp registry dir while proper-lockfile's refresh
timer still holds the lock, and onCompromised throws. One defect, two symptoms.
Measured on the same tree:

Tree Result
this PR (timeout, lock files untouched) 220/220 suites, 1726/1726 tests — green
lock fix only, 30s ceiling red (~86 Exceeded timeout of 30000 ms)

Verification

Rebased on latest origin/master (78285a80); each commit builds independently.

  • pnpm build
  • pnpm lint ✅ (no suppressions added; every touched file prettier-clean)
  • pnpm test ✅ — 220/220 suites, 1726/1726 tests, all 8 projects

The cross-repo verification run must override all three repos (BRANCH_WIRE_SYSIO,
BRANCH_WIRE_SOLANA, BRANCH_WIRE_TOOLS_TS = fix/SOL-375-379-380-opp-collateral) — the
program, the relay manifest, and the harness deposit paths move together.

Prior full-platform e2e evidence (pre-split, same harness surfaces):
14/14 flows green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VqAoaLdCBqgV2vXgDCGhgY

@valthon

valthon commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@valthon
valthon force-pushed the fix/SOL-375-379-380-opp-collateral branch from 1d24df4 to 1dacdaa Compare August 15, 2026 00:13
@valthon

valthon commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

14/14 e2e flows ✅

Cross-repo E2E integration run with all three branches pinned to fix/SOL-375-379-380-opp-collateral (wire-sysio, wire-solana, wire-tools-ts; other repos on manifest defaults, Release build): 14/14 flows passed — full platform build + every discovered flow green, no epoch stalls, OPP circulation clean.

Directly exercising this branch's surfaces:

  • operator-collateral-deposit ✅ / reserve-lifecycle ✅ — F1 per-position custody read + F3 SPL reserve-ATA provisioning
  • underwriter-slashing ✅ / batch-operator-slashing ✅ — SPL slash prechecks-before-debit
  • full swap suite ✅ (incl. swap-private-reserves, swap-non-native-tokens) — SPL terminal-settlement paths (SOL-380)

Run: https://github.com/Wire-Network/wire-platform-build-system/actions/runs/31853124095

@valthon
valthon force-pushed the fix/SOL-375-379-380-opp-collateral branch 2 times, most recently from f1b1022 to 0cce106 Compare August 19, 2026 14:35
@valthon valthon closed this Aug 19, 2026
@valthon valthon reopened this Aug 19, 2026
@valthon

valthon commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

flow tests

@valthon
valthon requested a review from jglanz August 19, 2026 21:02

@jglanz jglanz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split out the port lock changes into another PR, and just validate the timeout changes and should be good to go

Comment thread packages/cluster-tool/src/config/BindConfigProvider.ts Outdated
Comment thread packages/cluster-tool/src/utils/fsUtils.ts Outdated
Comment thread jest.config.ts
valthon and others added 2 commits August 20, 2026 11:36
solana-test-validator's RUST_LOG now defaults to an ADDITIVE filter —
agave's own `solana=info,agave=info` plus the program-log target — so
on-chain msg!() output reaches <ledger>/validator.log WITHOUT silencing
agave's startup diagnostics. A bare program-log directive REPLACES
agave's default and hides exactly the panic/bind lines
validatorLogTail() exists to surface.

A daemon's resolved env is threaded through DaemonConfig into the
emitted start.sh, so a validator relaunched from that script produces
the same logs the harness run would.

The pre-commit hook falls through to a direct pnpm run when mise is
present but cannot execute — notably a fresh clone whose mise config has
not been trusted yet — rather than hard-blocking every commit. The
mise-pinned Node is still used whenever mise works; it is just no longer
a hard dependency on local configuration state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The SOL outpost program moved operator collateral from a bounded global
ledger to per-(operator, token_code) CollateralPosition PDAs, and its
dispatch handlers settle SPL collateral out of a per-token
collateral_vault. The harness now models both.

Both deposit paths in SolanaCollateralTool derive and carry the
CollateralPosition PDA, and the batch-operator-termination flow
snapshots the position's rent so a full-drain close is asserted. The
per-token little-endian seed encoder that three call sites had each
re-spelled is consolidated into one tokenCodeToLittleEndianBuffer in
slugUtils, and every consumer points at it.

The outpost bootstrap pre-creates reserve_aggregate's associated token
account for each registered SPL collateral mint, via a new idempotent
SolanaFundingTool.ensureAssociatedTokenAccount. An OPERATOR_ACTION(SLASH)
of SPL collateral settles into that ATA, the program never creates ATAs,
and a dropped seizure carries no return attestation to re-drive it — so
the destination must exist before the slash lands (SOL-380). A real
outpost deploy owes the same one-time step when it registers an SPL
collateral token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VqAoaLdCBqgV2vXgDCGhgY
@valthon
valthon force-pushed the fix/SOL-375-379-380-opp-collateral branch from 0cce106 to 398d0f9 Compare August 20, 2026 15:36
@valthon
valthon requested a review from jglanz August 20, 2026 16:48
Findings from a four-reviewer pass (STYLE.md/eslint, manifest rules,
duplication, plus two independent second opinions), each verified
against the source before being applied.

start.sh no longer freezes build-host state. DaemonConfig carried
`SolanaValidatorProcess.resolveEnv()`, which reads the BUILD host's
RUST_LOG, and the renderer emitted the frozen answer as an
unconditional export. Both branches were wrong: a build host WITH
RUST_LOG set stripped the program-log target from the published script
entirely — the exact defect this branch exists to fix — and a build
host without it pinned a value the operator could not override.
DaemonConfig now carries the unconditional `DefaultEnv`, and
environmentLines renders `[ -n "${NAME:-}" ] || export NAME=<value>`
so the run-time environment still wins. The guarded form is used
rather than `${NAME:-…}` because bash re-parses quotes inside an
expansion word, so a value containing a single quote would break the
script; the guard keeps shellQuote's fully-inert single-quoted form.
This restores the renderer's own documented invariant — build-time
conditionals render as SHELL, never frozen.

The ensureAssociatedTokenAccount test asserted nothing. It stubbed
`sendTransaction` while sendAndPoll submits via `sendRawTransaction`,
so its write-count flag could never flip and the ATA-absent branch —
the one SOL-380's argument rests on — had no coverage at all. The stub
now implements the methods the send path actually calls and counts raw
submissions; both branches are asserted.

Seeds and PDA derivation stop forking. `reserve_aggregate` was
re-spelled in SolanaCollateralTool while the same change consumed
`SolanaOutpostBootstrapper.PdaSeed.ReserveAggregate` two files over;
CollateralPosition and CollateralVault join that registry and the tool
derives all three from it. The `findProgramAddressSync` plumbing was
triplicated across two files behind three private one-liners — now one
`SolanaOutpostProgramTool.derivePda`.

tokenCodeToLittleEndianBuffer is renamed slugNameToLittleEndianBuffer
and widened to `number | bigint`. Five of its twelve call sites encode
a RESERVE code, so the old name misdescribed them, and the deleted
predecessor already used the slugName stem. The widening removes the
`BigInt(...)` wrapper the bigint-only signature forced on callers that
hold the `number` SlugName.from returns.

Also: the jest ceiling is raised to the loaded-host worst case, which
is what a green suite actually needs — an undershot ceiling kills a
test inside withFileLock and the teardown then throws
`ENOENT … wire-cluster-ports.lock.lock`, one defect presenting as two.
A dead Anchor Program load built solely for a failure-message
interpolation is gone, along with the divergent program-id source it
introduced. mintMockSplToUser and ensureAssociatedTokenAccount share
an instruction-returning helper, so the former keeps batching ATA
creation with its mint in one transaction. The renderer walks
daemon.env once, via the existing isNotEmpty predicate. Tests were
added for the DaemonConfig env wiring, both start.sh branches, and the
number carrier; the validator tests drop process.env mutation for
resolveEnv's parameter. Every touched file now passes prettier.

pnpm build, pnpm lint, and pnpm test all green: 220/220 suites,
1726/1726 tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@valthon
valthon requested a review from jglanz August 20, 2026 19:24
@valthon
valthon merged commit 38f0154 into master Aug 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants