Morpho

Morpho: Midnight whitelist enter gate audit

Cantina Security Report

Organization

@morpho

Engagement Type

Spearbit Web3

Period

-


Findings

Low Risk

1 findings

0 fixed

1 acknowledged

Informational

3 findings

1 fixed

2 acknowledged


Low Risk1 finding

  1. Front-running signed whitelist toggles blocks settlement batches

    State

    Acknowledged

    Severity

    Severity: Low

    Description

    Let nn be the current nonce for a fixed (creditSide, whitelister, account), with credit entry restricted. Consider a pending atomic batch containing two valid signed whitelist updates:

    enable(n)takedisable(n+1).\operatorname{enable}(n) \to \operatorname{take} \to \operatorname{disable}(n+1).

    The signed payload binds neither the caller nor the settlement. An observer can extract both signatures and execute enable(n) followed by disable(n+1) before the batch. The nonce becomes n+2n+2 and the account is disabled.

    The batch's first call then reaches the stale-nonce branch: n<n+2n<n+2 holds, but the current status is false rather than the signed true. It reverts with WhitelistedStatusChanged(), preventing settlement. The attacker needs no whitelister role; both updates use the exposed signatures. Retrying with fresh signatures remains susceptible to the same ordering.

    Recommendation

    Bind temporary whitelist authorization to an executor that enforces the complete authorized settlement sequence atomically. Binding only to a permissionless bundler address is insufficient if arbitrary callers can use it to execute the extracted updates.

    Keep the stale-status check: removing it leaves the account disabled, while reapplying stale grants would allow consumed signatures to override subsequent revocations.

Informational3 findings

  1. Optional omission of whitelister from the EOA-only signed digest

    Severity

    Severity: Informational

    Description

    setIsWhitelistedWithSig includes whitelister in the signed struct, then requires ecrecover(digest, v, r, s) == whitelister, a nonzero recovered address, and the whitelister role. Signature recovery already binds the authorization to the signer; nonces are scoped to that verified address.

    For the current EOA verification scheme, omitting whitelister saves one word of struct encoding and hashing while retaining the signer and role checks. This is an optional optimization, not a correctness issue; retaining the field explicitly commits the signed payload to the authorizing identity.

    Including whitelister may also be intentional future-proofing, for example for a later design supporting ERC-1271. This is a possible rationale, not a confirmed plan; the current function does not call ERC-1271, and the integration design is unspecified.

    ERC-1271 asks a specified contract whether a signature is valid. Two wallets with the same owner may both accept the same digest and signature. If both have the whitelister role and matching nonces, omitting the wallet address can permit reuse across their nonce namespaces. The gate's domain separator identifies the gate, not the signing wallet. Binding whitelister distinguishes these authorizations.

    Recommendation

    Consider removal only for the current EOA-only scheme if the small saving is preferred over explicit identity binding. If the field is retained for future-proofing, document that rationale. If removed, update the struct encoding, EIP-712 type definition, typehash, signing helpers, and tests together; retain the signer, nonzero, role, and nonce checks. For ERC-1271 support, retain whitelister in the digest unless equivalent wallet-specific binding is enforced.

  2. WhitelistEnterGate role authority

    State

    Acknowledged

    Severity

    Severity: Informational

    Description

    Let s{credit,debt}s\in\{\mathrm{credit},\mathrm{debt}\}. Write RsR_s for the side's role setter, WsW_s for its set of whitelisters, and AsA_s for its set of whitelisted accounts. The authority graph is

    RsRsWsAs.R_s \longrightarrow R_s \longrightarrow W_s \rightrightarrows A_s.

    Here xyx\longrightarrow y means that xx may change yy: the first edge permits replacement of the role setter; the second permits adding or removing whitelisters. The repeated RsR_s denotes the same role. The multiple arrow means

    wWs,wAs:AsAs{a}orAsAs{a}.\forall w\in W_s,\qquad w\longrightarrow A_s: \quad A_s\gets A_s\cup\{a\}\quad\text{or}\quad A_s\gets A_s\setminus\{a\}.

    Thus every whitelister independently controls every account's whitelist status. Whitelisters can override one another: for distinct w1,w2Wsw_1,w_2\in W_s, w1w_1 may add an account and w2w_2 may remove it, or vice versa. The last executed update determines membership; no whitelister has priority or exclusive authority over an account. A valid signature from a current whitelister permits the same update through a relayer, subject to the nonce and deadline checks. A role setter can acquire this authority by adding itself to WsW_s.

    The contract-level documentation names the roles but does not state this authority graph. In particular, removing a whitelister does not remove accounts it whitelisted. The two sides have separate authority graphs, although their role holders may coincide; an immutable open side admits all accounts regardless of AsA_s.

  3. Consider adding name and version to EIP712 domain

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    Om Parikh


    Description

    /// @dev keccak256("EIP712Domain(uint256 chainId,address verifyingContract)").bytes32 constant EIP712_DOMAIN_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218;

    current EIP712 domain doesn't include optional fields such as name and version.

    Recommendation

    Consider adding name and version to EIP712 domain for better UX and readability.