F-2026-0014·test-harness-signature-mismatch

Invariant suite handler uses legacy EIP-191 signatures, silently reverts every position creation, yields zero security signal

Fixedvaultleveragedprediction-marketgithub.com/bloom-art/dripster-lend
TL;DR

Foundry invariant handler signs payloads with packed EIP-191 while the contract verifies EIP-712, so every position-creation handler call silently reverts and the suite never exercises the position state space.

Severity
INFO
Impact
LOW
Likelihood
LOW
Method
FFuzzing
CAT.
Complexity
MEDIUM
Exploitability
LOW
02Section · Description

Description

The Foundry invariant suite at evm_protocol/test/invariant/VaultInvariant.t.sol declares ten invariant_* properties exercised through PositionHandler.sol. The suite reports green on every campaign run, but produces no security signal because its handler builds signatures with legacy EIP-191 packed format while the contract requires EIP-712 typed data:

solidity
// PositionHandler._signCreatePosition (lines 470-491), legacy EIP-191 packed:
bytes32 messageHash = keccak256(abi.encodePacked("CREATE_POSITION", positionSeed, user, marketId, ...));
bytes32 ethSignedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
// SignatureLib.verifyCreatePositionSignature requires EIP-712:
bytes32 structHash = keccak256(abi.encode(CREATE_POSITION_TYPEHASH, ...));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));

Different digests result in recovered signer not equal to appAdmin1 / appAdmin2, triggering an InvalidSignature revert. The revert is swallowed by the try/catch block in PositionHandler.createPosition (line 130), so the fuzzer treats the call as non-reverting and continues. createdPositionKeys stays empty across every run, and every position-iterating invariant is vacuously satisfied. Only invariants that check immutable state (AdminAddressesNeverZero, ExternalContractsNeverZero, VersionIsOne) or pause-toggle ghost state carry real signal.

The correct EIP-712 signing helpers (_signCreatePositionWithKey, _signFinalizeSettleWithKey) already exist in test/helpers/SignatureHelpers.sol and are used by every passing unit test. The handler simply does not inherit them.

03Section · Recommendation

Recommendation

Have PositionHandler inherit SignatureHelpers (which extends Test) instead of importing Test directly, override _getVaultAddress() to return address(vault), delete the local packed-signature helpers, and route signing through the inherited EIP-712 functions. Add a campaign-level liveness assertion (afterInvariant: positionCount > 0) so a regression that re-introduces silent signature failures fails a hard check instead of being absorbed by the catch block.

04Section · Resolution

Resolution

Fixed. Invariant handler now inherits SignatureHelpers and signs via the EIP-712 helpers; campaign-level liveness assertion guards against silent regression.

Status
Fixed
F-2026-0014

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx