Reach out for an audit or to learn more about Macro
or Message on Telegram

Mento A-2

Security Audit

Jan 9th, 2024

Version 1.0.0

Presented by 0xMacro

Table of Contents

Introduction

This document includes the results of the security audit for Mento's smart contract code as found in the section titled ‘Source Code’. The security audit was performed by the Macro security team from October 17, 2023 to October 20, 2023.

The purpose of this audit is to review the source code of certain Mento Solidity contracts, and provide feedback on the design, architecture, and quality of the source code with an emphasis on validating the correctness and security of the software in its entirety.

Disclaimer: While Macro’s review is comprehensive and has surfaced some changes that should be made to the source code, this audit should not solely be relied upon for security, as no single audit is guaranteed to catch all possible bugs.

Overall Assessment

The following is an aggregation of issues found by the Macro Audit team:

Severity Count Acknowledged Won't Do Addressed
Low 3 1 - 2
Code Quality 7 1 2 4
Gas Optimization 1 1 - -

Mento was quick to respond to these issues.

Specification

Our understanding of the specification was based on the following sources:

Source Code

The following source code was reviewed during the audit:

Specifically, we audited the following contracts within this repository:

Contract SHA256
./contracts/governance/Airgrab.sol

9024a0ce18d292dd6beb54adbc82c4672d0b3c48040f957eb3bf7e8365aad7e2

./contracts/governance/Emission.sol

57bcc0cf7d4502be94bd5bee45ec84a28d239471701ea881045d2fb166480e16

./contracts/governance/Factory.sol

2fe6f7b8c6fed70ca54d764cc37f838f4497b078b9ef161d9812c66a245d2d14

./contracts/governance/MentoGovernor.sol

0236961d15161f0ef18cadf93b0726aec9f70ee2f5b069335266a32573b1ddfe

./contracts/governance/MentoToken.sol

32086fde6fe77ccd7d2b06b6eb65d44f4dc54525d19e7c4aff852781808e318b

./contracts/governance/TimelockController.sol

d3929f0cea34b98766f04dbde63ba3356dc5d9f7b7e598b74f7d51391669aec9

Additionally, the following forked contracts were reviewed for specific Celo changes but not fully audited as they a direct for from Raribles Locking contracts:

Contract SHA256
./contracts/governance/locking/Locking.sol

bd49ed658cb552a9b882c7b44c165612a8e5b702859fe45523b870f605c81f39

./contracts/governance/locking/LockingBase.sol

19febb767f7ccc4e7c361d7846099484eff0c0e8268b8ee09c3020497216a3a1

./contracts/governance/locking/LockingRelock.sol

1aad15ff22b5b4141b203c5572c632d9f080ca80c424f45da92f133d2e7a59b7

./contracts/governance/locking/LockingVotes.sol

e83679ec33f11f3ec0afa0f0f3ed2e42a8624de5b589d0ba8b10ab3af96b5843

./contracts/governance/locking/interfaces/ILocking.sol

ae88067d6bf351fb26c8531e810151cd94b41293e15f091d67f08d4dc42fcd2c

./contracts/governance/locking/interfaces/INextVersionLock.sol

b8f5d90ef5be5b91630d3a712760758ecf379260536ab79fe68179e349cc5523

./contracts/governance/locking/libs/LibBrokenLine.sol

591926c64bee1afaa163fd28307e6cb7b5f2240801b372825cba7e1096199f26

./contracts/governance/locking/libs/LibIntMapping.sol

2ad938c7a98263a4496b7909ab509a43c270cd267905ab5982440a9a7666e2a9

Note: This document contains an audit solely of the Solidity contracts listed above. Specifically, the audit pertains only to the contracts themselves, and does not pertain to any other programs or scripts, including deployment scripts.

Issue Descriptions and Recommendations

Click on an issue to jump to it, or scroll down to see them all.

Security Level Reference

We quantify issues in three parts:

  1. The high/medium/low/spec-breaking impact of the issue:
    • How bad things can get (for a vulnerability)
    • The significance of an improvement (for a code quality issue)
    • The amount of gas saved (for a gas optimization)
  2. The high/medium/low likelihood of the issue:
    • How likely is the issue to occur (for a vulnerability)
  3. The overall critical/high/medium/low severity of the issue.

This third part – the severity level – is a summary of how much consideration the client should give to fixing the issue. We assign severity according to the table of guidelines below:

Severity Description
(C-x)
Critical

We recommend the client must fix the issue, no matter what, because not fixing would mean significant funds/assets WILL be lost.

(H-x)
High

We recommend the client must address the issue, no matter what, because not fixing would be very bad, or some funds/assets will be lost, or the code’s behavior is against the provided spec.

(M-x)
Medium

We recommend the client to seriously consider fixing the issue, as the implications of not fixing the issue are severe enough to impact the project significantly, albiet not in an existential manner.

(L-x)
Low

The risk is small, unlikely, or may not relevant to the project in a meaningful way.

Whether or not the project wants to develop a fix is up to the goals and needs of the project.

(Q-x)
Code Quality

The issue identified does not pose any obvious risk, but fixing could improve overall code quality, on-chain composability, developer ergonomics, or even certain aspects of protocol design.

(I-x)
Informational

Warnings and things to keep in mind when operating the protocol. No immediate action required.

(G-x)
Gas Optimizations

The presented optimization suggestion would save an amount of gas significant enough, in our opinion, to be worth the development cost of implementing it.

Issue Details

L-1

Token can be changed mid lifecycle bricking the emission schedule

Topic
Configuration
Status
Impact
Medium
Likelihood
Low

Reference: Emission.sol#L46-49

The Emission contract includes an onlyOwner setter function for the token. If this token is changed mid-cycle, the emission schedule will be disrupted, counting previously emitted tokens as emitted for the new token and potentially bricking the programmed amounts of governance token supply for any given time.

Remediations to Consider

Consider allowing this function to only set the token contract once.

L-2

Initialization functions of contracts can be removed

Topic
Configuration
Status
Impact
Low
Likelihood
Medium

Contracts like MentoToken.sol, Emission.sol, and Airgrab.sol have initializers and/or setters that are used to set values after deployment. This is due to circular dependencies where they each need the address of another contract that may yet be deployed in the factory contract’s createGovernance() call. However, there is a way to know the address of a contract before it is deployed, since when deploying a contract using the CREATE opcode, it will deterministically deploy the contract with an address based on the contract’s nonce value. Since createGoverance() will only be called once, these values are known and the addresses of the contracts to be deployed can be predicted in advance and used in the constructors of other contracts before their deployment, provided no calls are made to them before they are deployed.

Remediations to Consider

Remove unnecessary setters and initializers by predicting the required addresses before they are deployed. This will reduce the bytecode size of the contracts as well as remove the perception that these contracts are upgradable/mutable.

L-3

Locking contract can underflow

Topic
Configuration
Status
Acknowledged
Impact
Medium
Likelihood
Low

In LockingBase.sol’s getLock()function, a call is made to divUp() to get the lockSlope. The lockAmount is passed in as the a parameter which gets subtracted by 1 before it is divided.

function divUp(uint96 a, uint96 b) internal pure returns (uint96) {
  return ((a - 1) / b) + 1;
}

Reference: LockingBase#L219-L221

If lockSlope is 0, then the function will revert with an underflow. The case where the lockSlope can be set to zero is if the amountMultiplied is less than the ST_FORMULA_DIVIDER, which can occur if the cliff is set to zero, the slopePeriod is equal to the minSlopePeriod, which is 1, and the amount is sufficiently small, ≤ 5.

function getLock(
  uint96 amount,
  uint32 slopePeriod,
  uint32 cliff
) public view returns (uint96 lockAmount, uint96 lockSlope) {
  require(cliff >= minCliffPeriod, "cliff period < minimal lock period");
  require(slopePeriod >= minSlopePeriod, "slope period < minimal lock period");

  uint96 cliffSide = (uint96(cliff - uint32(minCliffPeriod)) * (ST_FORMULA_CLIFF_MULTIPLIER)) /
    (MAX_CLIFF_PERIOD - uint32(minCliffPeriod));
  uint96 slopeSide = (uint96((slopePeriod - uint32(minSlopePeriod))) * (ST_FORMULA_SLOPE_MULTIPLIER)) /
    (MAX_SLOPE_PERIOD - uint32(minSlopePeriod));
  uint96 multiplier = cliffSide + (slopeSide) + (ST_FORMULA_CONST_MULTIPLIER);

  uint256 amountMultiplied = uint256(amount) * uint256(multiplier);
  lockAmount = uint96(amountMultiplied / (ST_FORMULA_DIVIDER));
  lockSlope = divUp(lockAmount, slopePeriod);
}

Reference: LockingBase#L200-L221

Remediations to Consider

This case should be handled by a custom error, or the minimum amount to lock should be sufficiently high to prevent this underflow from occurring.

Response by Mento

We are not going to fix this because:

1- Probability of this happening is really small, only happens for really low lock amounts

2- Impact is low, ie a wrong revert message

3- We want to keep the changes to the forked library minimal

Q-1

Lack of address(0) checks

Topic
Sanity check
Status
Wont Do
Quality Impact
Low

References: Emission.sol#L46-49, Emission.sol#L55-58, LockingBase.sol#L148, MentoToken.sol#L31-53

Consider adding sanity checks for all address setters to avoid misconfigurations with address(0) as a value.

Response by Mento

We think address(0) check does not provide value in our case. We believe address(0) is not that different than address(1) when the function is called by the governance.

Q-2

Unnecessary uint256 cast

Topic
Redundant code
Status
Quality Impact
Low

Reference: Airgrab.sol#L131

In the Airgrab.sol contract, the modifier canClaim() unnecessarily casts the input parameter amount to uint256, even though it is already a uint256 variable. Consider removing this unnecessary typecast.

Q-3

Duplicated constant variable declaration

Topic
Redundant code
Status
Wont Do
Quality Impact
Low

Reference: Airgrab.sol#L28-29, LockingBase.sol#L18-19

Both Airgrab and LockingBase contracts declare constant variables MAX_CLIFF_PERIOD and MAX_SLOPE_PERIOD. Consider moving these declarations to the shared imported interface ILocking.sol to keep one source of truth for these values.

Response by Mento

ILocking is not shared between LockingBase and Airgrab. Fixing this would require us to introduce a shared interface and we are not sure if it worths it

Q-4

treasury address can be set in the constructor

Topic
Good practice
Status
Quality Impact
Medium

Reference: Airgrab.sol#L66, LockingBase.sol#L190

The treasury variable, in Airgrab, is set in the initialize() function, used as a workaround for the circular dependencies with the Token and LockingContract. However, this value does not need to be initialized after deployment. Consider making it immutable and setting it in the constructor instead.

Q-5

Misspellings

Topic
Good practice
Status
Quality Impact
Low

There are some comments that have spelling errors:

  • “cliam” should be spelled “claim”
* @dev Check if the account can cliam

Reference: Airgrab.sol#L116

  • “incldued” should be spelled “included”
* if the account hasn't already claimed and if it's incldued

Reference: Airgrab.sol#L118

  • “MerkeTree” should be spelled “MerkleTree”
* @notice This contract implements a token airgrab gated by a MerkeTree and KYC using fractal.

Reference: Airgrab.sol#L18

  • “scheduledRemainigSupply” should be spelled “scheduledRemainingSupply”
uint256 scheduledRemainigSupply = (TOTAL_EMISSION_SUPPLY * (positiveAggregate - negativeAggregate)) / SCALER;

Reference: Emission.sol#L100

Q-6

Revert with custom errors instead of require statements

Topic
Good practice
Status
Acknowledged
Quality Impact
Low

Throughout the codebase, require statements are used, which revert with a set static string. For solidity versions 0.8.4+, custom errors can be used, which can return dynamic values to give more insight into what caused the error, as well as cost less gas on average.

Remediations to Consider

Replace all require statements with conditions that revert with a custom error containing relevant error information, in order to make it more clear why an error is occurring to the user as well as save slightly on gas costs.

Response by Mento

We use require statements across whole code base and want to keep it consistent.

Q-7

Duplicate ERC20 inheritance

Topic
Redundant code
Status
Quality Impact
Low

MentoToken.sol inherits from both ERC20, and ERC20Burnable, however, ERC20Burnable also inherits ERC20.

contract MentoToken is ERC20, ERC20Burnable {

Reference: MentoToken.sol#L12

abstract contract ERC20Burnable is Context, ERC20 {

Reference: ERC20Burnable.sol#L14

Remediations to Consider

Remove the ERC20 dependency and only inherit from ERC20Burnable to prevent duplicate inheritance.

G-1

Unnecessary reentrancy guards in Airgrab

Topic
Gas Savings
Status
Acknowledged
Gas Savings
Low

In Airgrab.sol, there are reentrancy guards on the functions claim() and drain(). However, there is no path for a reentrancy attack to occur. claim() properly follows the checks effects and interactions pattern by setting claimed to true for the sender before making an external call to the locking contract. Since the canClaim modifier is run and requires claimed for the sender to be false, there is no way to claim multiple times, even if the locking contract was malicious. For drain(), there would be no benefit to reenter the call as it is just transferring the contract’s balance of tokens to the treasury contract, and no intermediate state can be manipulated.

Remediations to Consider

Remove the reentrancy guards on claim() and drain() and remove it as a dependency, to reduce the gas costs of calling these functions and reduce the deployment cost of the contract.

Response by Mento

We agree that it looks unnecessary, but we still want to keep it as it is a function that will be used just for once per user and extra gas spent will be negligible, especially on Celo.

Disclaimer

Macro makes no warranties, either express, implied, statutory, or otherwise, with respect to the services or deliverables provided in this report, and Macro specifically disclaims all implied warranties of merchantability, fitness for a particular purpose, noninfringement and those arising from a course of dealing, usage or trade with respect thereto, and all such warranties are hereby excluded to the fullest extent permitted by law.

Macro will not be liable for any lost profits, business, contracts, revenue, goodwill, production, anticipated savings, loss of data, or costs of procurement of substitute goods or services or for any claim or demand by any other party. In no event will Macro be liable for consequential, incidental, special, indirect, or exemplary damages arising out of this agreement or any work statement, however caused and (to the fullest extent permitted by law) under any theory of liability (including negligence), even if Macro has been advised of the possibility of such damages.

The scope of this report and review is limited to a review of only the code presented by the Mento team and only the source code Macro notes as being within the scope of Macro’s review within this report. This report does not include an audit of the deployment scripts used to deploy the Solidity contracts in the repository corresponding to this audit. Specifically, for the avoidance of doubt, this report does not constitute investment advice, is not intended to be relied upon as investment advice, is not an endorsement of this project or team, and it is not a guarantee as to the absolute security of the project. In this report you may through hypertext or other computer links, gain access to websites operated by persons other than Macro. Such hyperlinks are provided for your reference and convenience only, and are the exclusive responsibility of such websites’ owners. You agree that Macro is not responsible for the content or operation of such websites, and that Macro shall have no liability to your or any other person or entity for the use of third party websites. Macro assumes no responsibility for the use of third party software and shall have no liability whatsoever to any person or entity for the accuracy or completeness of any outcome generated by such software.