Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · March 2026

Yellow Contract

for Yellow Network

Yellow engaged Guardian to review the security of their Yellow contract. From the 2nd of March 2026 to the 5th of March 2026, a team of 2 auditors reviewed the source code in scope.

Published
Review window
March 2 to 5, 2026
Language
Solidity
Chains
Ethereum
Sector
Infrastructure
  • 0 Critical
  • 0 High
  • 1 Medium
  • 8 Low
  • 0 Informational

9 resolved

Scope

Overview

Yellow engaged Guardian to review the security of their Yellow contract. From the 2nd of March 2026 to the 5th of March 2026, a team of 2 auditors reviewed the source code in scope.

Findings 9

  1. M-01 Medium Quorum Floor Change Affects Previous Proposals Logical Error Resolved
    Location
    Governor.sol

    Description

    The quorum value is determined as the maximum of the fractional quorum and quorumFloor. While the fractional quorum, inherited from GovernorVotesQuorumFraction, is calculated using the snapshot at the time of proposal creation, quorumFloor uses the current value and is not snapshotted at proposal creation.

    The queue and execute flows perform quorum checks based on updated value. As a result, any update to quorumFloor retroactively affects all proposals that have not yet been executed, including proposals whose voting period has already ended. A proposal may satisfy quorum and be marked as passed under the floor value in effect during voting, but a subsequent increase to quorumFloor can cause quorum validation to fail during the queuing or execution phase, preventing successful proposal execution.

    Recommendation

    Snapshot the quorum floor per proposal (store floor at proposal creation/snapshot) and use that stored value when computing quorum for that proposal’s snapshot block. Alternatively, make the floor change apply only to proposals created after a certain timepoint.

    Resolution

    Yellow Team: Resolved.

  2. L-01 Low Quorum Floor Can Be Set Above Voting Supply Validation Resolved
    Location
    Governor.sol

    Description

    Governance can set _quorumFloor to an absolute value larger than the maximum possible votes at a snapshot (e.g., larger than total voting units / total locked supply), making quorum impossible to reach and effectively freezing proposal success/execution. Because setQuorumFloor itself is onlyGovernance, a malicious or compromised governance moment can permanently brick future governance.

    Recommendation

    Add an upper bound when setting the floor (e.g., <= token.totalSupply() or <= expected max voting units).

    Resolution

    Yellow Team: Resolved.

  3. L-02 Low Undelegated Locks Raise Quorum With No Votes Logical Error Resolved
    Location
    Locker.sol

    Description

    In src/Locker.sol, lock(uint256 amount) calls _transferVotingUnits(address(0), msg.sender, received) regardless of delegation status. In OZ Votes, this unconditionally adds to _totalCheckpoints (the total supply used in quorum calculation).

    However, when the user has not called delegate(), their delegatee defaults to address(0), so _moveDelegateVotes(address(0), address(0), amount) hits the from != to guard and is a no-op; no voting power is assigned to any address.

    Recommendation

    Consider auto-self-delegating on first lock when delegates(msg.sender) == address(0) (e.g., call _delegate(msg.sender, msg.sender) in lock()).

    Resolution

    Yellow Team: Resolved.

  4. L-03 Low No Late-Quorum Protection On Governor Best Practices Resolved
    Location
    Governor.sol

    Description

    YellowGovernor (src/Governor.sol) does not inherit OZ's GovernorPreventLateQuorum extension. Without it, a whale who holds sufficient voting power can wait until the final block of the voting period and cast a decisive vote that triggers quorum and determines the outcome in a single transaction. Other tokenholders have no opportunity to observe the vote, react, or counter-vote because the proposal deadline has already passed.

    Recommendation

    Consider inheriting GovernorPreventLateQuorum and setting a reasonable voteExtension.

    Resolution

    Yellow Team: Resolved.

  5. L-04 Low Ownership Can Be Renounced And Cause Permanent DoS Configuration Resolved
    Location
    Treasury.sol

    Description

    The treasury owner is the deployer initially, and ownership will be transferred to the Timelock through a two-step transfer, after which the Timelock will accept the ownership.

    However, the renounceOwnership function from Ownable.sol is not overridden and could cause a complete DoS if used by mistake during this process. Calling renounceOwnership not only removes the current owner but also clears the pendingOwner. As a result, the Timelock would not be able to accept ownership, and all funds would become permanently locked in the treasury.

    Additionally, the Timelock itself can also renounce ownership via malicious governance, which would prevent the withdraw function in the treasury from being used.

    Another point to note is that the deployer remains the owner until the Timelock accepts ownership. During this period, the deployer can withdraw funds from the treasury without any governance approval.

    Recommendation

    Since the treasury must always have an owner, override the renounceOwnership function to always revert, preventing the treasury from being left without an owner.

    Resolution

    Yellow Team: Resolved.

  6. I-01 Low Rebasing Tokens Cannot Be Used In Locker Warning Resolved
    Location
    Locker.sol

    Description

    The Treasury and Locker contracts determine the exact transferred amount by comparing the before and after balances of users (or the locker itself) during transfers. This enables support for fee-on-transfer tokens.

    However, the Locker is not compatible with rebasing or deflationary tokens. If the underlying token balance gradually decreases over time, withdrawals will fail, particularly for later or final users. This happens because the Locker’s balance decays over time, while the withdraw function attempts to transfer the original _balances recorded at the time of locking.

    Recommendation

    Be aware of this limitation. No changes are necessary if rebasing tokens are not intended to be used as the underlying asset in the Locker.

    Resolution

    Yellow Team: Resolved.

  7. I-02 Low Mismatch Between Tests And Code Comment Informational Resolved
    Location
    Locker.sol

    Description

    The NatSpec comment in Locker states: “Single-asset vault with a 7-day time-locked withdrawal mechanism.”

    However, in the tests, the immutable UNLOCK_PERIOD is set to 14 days.

    Recommendation

    While the tests do not necessarily need to be changed, ensure that the contract is deployed with a 7-day unlock period, or update the comment in Locker.sol accordingly.

    Resolution

    Yellow Team: Resolved.

  8. I-03 Low Proposals Uncancellable Once Past Pending State Best Practices Resolved
    Location
    Locker.sol

    Description

    OZ Governor v5 base _validateCancel (Governor.sol:787-789) only allows cancelling during Pending. Once voting starts, proposal cancellation reverts for everyone, including the proposer. This is the default behavior in the OpenZeppelin Governor implementation. Neither GovernorTimelockControl nor YellowGovernor overrides this behavior. Once voting begins, only token holders can determine the outcome and no kill switch exists.

    As a result, if a malicious proposal somehow gets voted through and queued:

    1. Attacker proposes something like treasury.withdraw(token, attacker, fullBalance).
    2. It passes the vote (social engineering, bribery, voter apathy, etc).
    3. Gets queued in the timelock.
    4. Community notices during the 2-day window.
    5. governor.cancel() — reverts.
    6. timelock.cancel() — reverts.
    7. Only option is a counter-proposal, but that needs votingDelay + votingPeriod + timelockDelay (~10

    days). The malicious one executes in day 2.

    1. Anyone calls execute() (open EXECUTOR_ROLE), treasury gone.

    While this is essentially a governance decision rather than an exploitable vulnerability, OpenZeppelin provides GovernorProposalGuardian for such edge cases, allowing proposals to be cancelled after voting when necessary.

    Recommendation

    Consider using OpenZeppelin’s GovernorProposalGuardian and assigning a trusted guardian (e.g., a multisig) that can cancel proposals at any stage.

    However, this introduces an element of centralization. No change is required if the Yellow protocol intentionally chooses token holders/governance as the sole decision-maker without a kill switch.

    Resolution

    Yellow Team: Resolved.

  9. I-04 Low Misleading Error Name In Constructor Validation Best Practices Resolved
    Location
    Locker.sol

    Description

    In Locker.sol:34, when unlockPeriod_ is zero the constructor reverts with InvalidAmount(). But the unlock period is a time duration, not a token amount. The same InvalidAmount() error is also used in lock() for 0 amount deposits.

    Recommendation

    Consider using a specific error like InvalidPeriod().

    Resolution

    Yellow Team: Resolved.

More from Yellow Network

  1. Nitewatch

    12 findings 12 findings: 3 low, 9 informational
  2. Token.sol

    0 findings No findings

Put your code through the same review.

This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.

Get a quote