Halborn Logo

Governance & Timelock Updates - Moonwell


Prepared by:

Halborn Logo

HALBORN

Last Updated 06/11/2024

Date of Engagement by: August 10th, 2022 - August 17th, 2022

Summary

100% of all REPORTED Findings have been addressed

All findings

4

Critical

0

High

0

Medium

2

Low

0

Informational

2


1. INTRODUCTION

Moonwell Finance engaged Halborn to conduct a security audit on their Governance & Timelock smart contracts beginning on August 10th, 2022 and ending on August 17th, 2022. The security assessment was scoped to the smart contracts provided to the Halborn team.

2. AUDIT SUMMARY

The team at Halborn was provided one week for the engagement and assigned a full-time security engineer to audit the security of the smart contract. The security engineer is a blockchain and smart-contract security expert with advanced penetration testing, smart-contract hacking, and deep knowledge of multiple blockchain protocols.

The purpose of this audit is to:

    • Ensure that smart contract functions operate as intended.

    • Identify potential security issues with the smart contracts.

In summary, Halborn identified some security risks that were addressed by the Moonwell Finance team.

3. TEST APPROACH & METHODOLOGY

Halborn performed a combination of manual and automated security testing to balance efficiency, timeliness, practicality, and accuracy regarding the scope of the smart contract audit. While manual testing is recommended to uncover flaws in logic, process, and implementation; automated testing techniques help enhance coverage of smart contracts and can quickly identify items that do not follow security best practices. The following phases and associated tools were used throughout the term of the audit:

    • Research into architecture and purpose.

    • Smart Contract manual code review and walkthrough.

    • Graphing out functionality and contract logic/connectivity/functions(solgraph)

    • Manual Assessment of use and safety for the critical Solidity variables and functions in scope to identify any arithmetic related vulnerability classes.

    • Static Analysis of security for scoped contract, and imported functions.(Slither)

    • Dynamic Analysis (ganache-cli, brownie, hardhat).

4. SCOPE

\begin{enumerate} \item Moonwell Finance Smart Contracts \begin{enumerate} \item PR 60: \href{https://github.com/moonwell-fi/moonwell-contracts-private/pull/60}{Moonwell Finance - Moonwell Core} \item PR 66: \href{https://github.com/moonwell-fi/moonwell-contracts-private/pull/66}{Moonwell Finance - Moonwell Core} \item PR 67: \href{https://github.com/moonwell-fi/moonwell-contracts-private/pull/67}{Moonwell Finance - Moonwell Core} \item PR 68: \href{https://github.com/moonwell-fi/moonwell-contracts-private/pull/68}{Moonwell Finance - Moonwell Core} \item PR 70: \href{https://github.com/moonwell-fi/moonwell-contracts-private/pull/70}{Moonwell Finance - Moonwell Core} \item PR 71: \href{https://github.com/moonwell-fi/moonwell-contracts-private/pull/71}{Moonwell Finance - Moonwell Core} \end{enumerate} \end{enumerate}

FIX COMMIT ID :

TAG :

5. RISK METHODOLOGY

Vulnerabilities or issues observed by Halborn are ranked based on the risk assessment methodology by measuring the LIKELIHOOD of a security incident and the IMPACT should an incident occur. This framework works for communicating the characteristics and impacts of technology vulnerabilities. The quantitative model ensures repeatable and accurate measurement while enabling users to see the underlying vulnerability characteristics that were used to generate the Risk scores. For every vulnerability, a risk level will be calculated on a scale of 5 to 1 with 5 being the highest likelihood or impact.
RISK SCALE - LIKELIHOOD
  • 5 - Almost certain an incident will occur.
  • 4 - High probability of an incident occurring.
  • 3 - Potential of a security incident in the long term.
  • 2 - Low probability of an incident occurring.
  • 1 - Very unlikely issue will cause an incident.
RISK SCALE - IMPACT
  • 5 - May cause devastating and unrecoverable impact or loss.
  • 4 - May cause a significant level of impact or loss.
  • 3 - May cause a partial impact or loss to many.
  • 2 - May cause temporary impact or loss.
  • 1 - May cause minimal or un-noticeable impact.
The risk level is then calculated using a sum of these two values, creating a value of 10 to 1 with 10 being the highest level of security risk.
Critical
High
Medium
Low
Informational
  • 10 - CRITICAL
  • 9 - 8 - HIGH
  • 7 - 6 - MEDIUM
  • 5 - 4 - LOW
  • 3 - 1 - VERY LOW AND INFORMATIONAL

6. SCOPE

Out-of-Scope: New features/implementations after the remediation commit IDs.

7. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

2

Low

0

Informational

2

Impact x Likelihood

HAL-01

HAL-02

HAL-03

HAL-04

Security analysisRisk levelRemediation Date
OVERPRIVILEGED ROLE ON THE BREAK GLASS GUARDIANMediumSolved - 08/18/2022
TIMELOCK DELAY IS SET TO ZERO IN THE CONSTRUCTORMediumSolved - 08/18/2022
MISSING EVENTS FOR ADMIN ONLY FUNCTIONS THAT CHANGE CRITICAL PARAMETERSInformationalSolved - 08/18/2022
PLACE VARIABLE DEFINITION AT THE BEGINNING OF THE CONTRACTInformationalSolved - 08/18/2022

8. Findings & Tech Details

8.1 OVERPRIVILEGED ROLE ON THE BREAK GLASS GUARDIAN

// Medium

Description

In the contract GovernorAlpha, the breakGlassGuardian has the authority to call the following functions to update the timelock admin:

  • GovernorAlpha.__acceptAdminOnTimelock() : The breakGlassGuardian can accept address to be a timelock admin.
  • GovernorAlpha.__executeSetTimelockPendingAdmin(address) : The breakGlassGuardian can add a pending admin.

Any compromise to the breakGlassGuardian account may allow the hacker to tamper with the project through these functions.

Code Location

GovernorAlpha.sol

    function __acceptAdminOnTimelock() public {
        require(msg.sender == breakGlassGuardian, "GovernorAlpha::__acceptAdmin: sender must be bg guardian");
        timelock.acceptAdmin();
    }

    /// @notice Fast tracks setting a pendingAdmin on the timelock. Only callable by the break glass guardian.
    function __executeSetTimelockPendingAdmin(address newPendingAdmin) public {
        require(msg.sender == breakGlassGuardian, "GovernorAlpha::__executeSetTimelockPendingAdmin: sender must be bg guardian");
        timelock.fastTrackExecuteTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin));
    }
Score
Impact: 4
Likelihood: 2
Recommendation

SOLVED: The Moonwell Team solved this issue by removing the __executeSetTimelockPendingAdmin function.

Commit ID: 4e8bec5926339106c225d0f85120ba182e52f2dd

8.2 TIMELOCK DELAY IS SET TO ZERO IN THE CONSTRUCTOR

// Medium

Description

The timelock delay is set to zero in the constructor. That can cause inconsistency in the proposals, and each proposal can bypass the timelock.

Code Location

Timelock.sol

    constructor(address admin_, uint delay_) public {
        require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay.");
        require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
        admin = admin_;
        delay = 0;
    }
Score
Impact: 3
Likelihood: 3
Recommendation

SOLVED: The Moonwell Team solved this issue by setting the delay function.

Commit ID: 4e8bec5926339106c225d0f85120ba182e52f2dd

8.3 MISSING EVENTS FOR ADMIN ONLY FUNCTIONS THAT CHANGE CRITICAL PARAMETERS

// Informational

Description

Role-only privileged functions that change critical parameters should emit events. Events allow changing parameters to be captured so that off-chain tools/interfaces can record such changes with timelocks allowing users to evaluate them and consider whether they would like to engage/exit based on how they perceive the changes to affect reliability of the protocol or profitability of implemented financial services. The alternative of directly querying the state of the on-chain contract for such changes is not considered practical for most users/usages.

Code Location

GovernorAlpha.sol

    function setProposalMaxOperations(uint newValue) public {
        require(msg.sender == address(timelock), "only timelock");
        proposalMaxOperations = newValue;
    }

    /// @notice The delay before voting on a proposal may take place, once proposed
    uint public votingDelay = 1 days;

    function setVotingDelay(uint newValue) public {
        require(msg.sender == address(timelock), "only timelock");
        votingDelay = newValue;        
    }

    /// @notice The duration of voting on a proposal, in blocks
    uint public votingPeriod = 3 days;
    function setVotingPeriod(uint newValue) public {
        require(msg.sender == address(timelock), "only timelock");
        votingPeriod = newValue;                
    }
Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: The Moonwell Team solved this issue by adding events to functions.

Commit ID: 4e8bec5926339106c225d0f85120ba182e52f2dd

8.4 PLACE VARIABLE DEFINITION AT THE BEGINNING OF THE CONTRACT

// Informational

Description

Regarding Solidity Style Guide, the variable definition can be moved to the beginning of the contract.

Code Location

GovernorAlpha.sol

    function setProposalMaxOperations(uint newValue) public {
        require(msg.sender == address(timelock), "only timelock");
        proposalMaxOperations = newValue;
    }

    /// @notice The delay before voting on a proposal may take place, once proposed
    uint public votingDelay = 1 days;

    function setVotingDelay(uint newValue) public {
        require(msg.sender == address(timelock), "only timelock");
        votingDelay = newValue;        
    }

    /// @notice The duration of voting on a proposal, in blocks
    uint public votingPeriod = 3 days;
    function setVotingPeriod(uint newValue) public {
        require(msg.sender == address(timelock), "only timelock");
        votingPeriod = newValue;                
    }
Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: The Moonwell Team solved this issue by placing variables at the beginning of the contract.

Commit ID: 4e8bec5926339106c225d0f85120ba182e52f2dd

Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.