Maia DAO Ecosystem - Verichains's results

Efficient liquidity renting and management across chains with Curvenized Uniswap V3.

General Information

Platform: Code4rena

Start Date: 30/05/2023

Pot Size: $300,500 USDC

Total HM: 79

Participants: 101

Period: about 1 month

Judge: Trust

Total Solo HM: 36

Id: 242

League: ETH

Maia DAO Ecosystem

Findings Distribution

Researcher Performance

Rank: 45/101

Findings: 1

Award: $592.67

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 0xTheC0der

Also found by: Verichains

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sponsor confirmed
edited-by-warden
duplicate-470

Awards

592.6743 USDC - $592.67

External Links

Lines of code

https://github.com/code-423n4/2023-05-maia/blob/main/src/maia/vMaia.sol#L67-L88

Vulnerability details

Impact

The vMaia.sol inherits as below:

vMaia <- ERC4626PartnerManager <- PartnerUtilityManager <- UtilityManager

In the vMaia contract, when users deposit Maia tokens, they receive vMaia tokens at a 1:1 ratio. By invoking specific claim...() functions users are able to claim their Gauge Weight, Governance, and Partner Governance tokens.

Before executing a claim, the contract ensures that the token balance is greater than or equal to the total of the previously claimed tokens and the amount of tokens being claimed. However, it is important to note that the token balance should first be converted to bHermes tokens by multiplying it with the bHermesRate before conducting the balance check.

// right logic
amount = amount * bHermesRate;
balanceOf[msg.sender] * bHermesRate < amount + userClaimedWeight[msg.sender];

Unfortunately, it seems that the vMaia contract overrides the checkWeight and checkGovernance, and checkPartnerGovernance modifiers without converting the user's vMaia balance to bHermes.

// wrong override in vMaia contract
balanceOf[msg.sender] < amount + userClaimedWeight[msg.sender];

When bHermesRate greater than 1, call:

  • claimOutstanding(): Revert due to balance always less than amount of tokens being claimed.
  • claimMultiple(), claimMultipleAmounts(), claimWeight(), claimGovernance(), claimPartnerGovernance(): Without multiplying the balance with the bHermesRate, users receive less amount of Weight, Governance and Partner Governance tokens than they actually have to receive.

Proof of Concept

Put the PoC.sol in ./test/maia folder

Run test:

forge test --match-path ./test/maia/PoC.sol --match-contract poc -vvv

PoC.sol

Deploy vMaia contract with bHermesRate is 2.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol";
import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";

import {vMaia, PartnerManagerFactory, ERC20} from "@maia/vMaia.sol";
import {IBaseVault} from "@maia/interfaces/IBaseVault.sol";
import {MockVault} from "./mock/MockVault.t.sol";

import {bHermes} from "@hermes/bHermes.sol";

import {DateTimeLib} from "solady/utils/DateTimeLib.sol";

import {console2} from "forge-std/console2.sol";

contract poc is DSTestPlus {
    MockVault vault;

    MockERC20 public hermes;

    MockERC20 public maia;

    vMaia public vmaia;

    uint256 bHermesRate;

    bHermes public bhermes;

    function setUp() public {
        //1 jan 2023
        hevm.warp(1672531200);

        hermes = new MockERC20("test hermes", "RTKN", 18);
        maia = new MockERC20("test maia", "tMAIA", 18);

        bhermes = new bHermes(hermes, address(this), 1 weeks, 1 days / 2);

        bHermesRate = 2;

        vmaia = new vMaia(
            PartnerManagerFactory(address(this)),
            bHermesRate,
            maia,
            "vote Maia",
            "vMAIA",
            address(bhermes),
            address(vault),
            address(0)
        );

        // Transfer bHermes to vMaia contract
        hermes.mint(address(this), 1000 ether);
        hermes.approve(address(bhermes), 1000 ether);
        bhermes.deposit(1000 ether, address(this));

        bhermes.transfer(address(vmaia), 1000 ether);
    }

    function testPoC() public {
        uint256 amount = 100 ether;
        uint256 expect = amount * bHermesRate;

        depositMaia(amount);

        // claim Outstanding
        hevm.expectRevert(abi.encodeWithSignature("InsufficientShares()"));
        vmaia.claimOutstanding();
        console2.log("claimOutstanding(): reverted");

        // claim Weight
        vmaia.claimWeight(amount);
        assertFalse(
            expect == ERC20(vmaia.gaugeWeight()).balanceOf(address(this))
        );
        console2.log(
            "claimWeight(): Expect %s, But receive %s",
            expect,
            ERC20(vmaia.gaugeWeight()).balanceOf(address(this))
        );

        // claim Governance
        vmaia.claimGovernance(amount);
        assertFalse(
            expect == ERC20(vmaia.governance()).balanceOf(address(this))
        );
        console2.log(
            "claimGovernance(): Expect %s, But receive %s",
            expect,
            ERC20(vmaia.governance()).balanceOf(address(this))
        );

        // claim PartnerGovernance
        vmaia.claimPartnerGovernance(amount);
        assertFalse(
            expect == ERC20(vmaia.partnerGovernance()).balanceOf(address(this))
        );
        console2.log(
            "claimPartnerGovernance(): Expect %s, But receive %s",
            expect,
            ERC20(vmaia.partnerGovernance()).balanceOf(address(this))
        );
    }

    function depositMaia(uint256 amount) internal {
        assertEq(vmaia.bHermesRate(), bHermesRate);

        maia.mint(address(this), amount);
        maia.approve(address(vmaia), amount);

        vmaia.deposit(amount, address(this));

        assertEq(maia.balanceOf(address(vmaia)), amount);
        assertEq(vmaia.balanceOf(address(this)), amount);
    }
}

Tools Used

Manual, Foundry

In Maia.sol#L67-88, the modifier can be fixed as below:

modifier checkWeight(uint256 amount) virtual override {
-   if (balanceOf[msg.sender] < amount + userClaimedWeight[msg.sender]) {
+   if (balanceOf[msg.sender] * bHermesRate < amount + userClaimedWeight[msg.sender]) {
        revert InsufficientShares();
    }
    _;
}

/// @dev Checks available governance allows for the call.
modifier checkGovernance(uint256 amount) virtual override {
-   if (balanceOf[msg.sender] < amount + userClaimedGovernance[msg.sender]) {
+   if (balanceOf[msg.sender] * bHermesRate < amount + userClaimedGovernance[msg.sender]) {
        revert InsufficientShares();
    }
    _;
}

/// @dev Checks available partner governance allows for the call.
modifier checkPartnerGovernance(uint256 amount) virtual override {
-   if (balanceOf[msg.sender] < amount + userClaimedPartnerGovernance[msg.sender]) {
+   if (balanceOf[msg.sender] * bHermesRate < amount + userClaimedPartnerGovernance[msg.sender]) {
        revert InsufficientShares();
    }
    _;
}

In ERC4626PartnerManager.sol, claimOutstanding() must be fixed and all of claim... function must be overridden as below:

function claimOutstanding() public virtual {
    uint256 balance = balanceOf[msg.sender] * bHermesRate;
    /// @dev Never overflows since balandeOf >= userClaimed.
-   claimWeight(balance - userClaimedWeight[msg.sender]);
+   super.claimWeight(balance - userClaimedWeight[msg.sender]);
    claimBoost(balance - userClaimedBoost[msg.sender]);
-   claimGovernance(balance - userClaimedGovernance[msg.sender]);
+   super.claimGovernance(balance - userClaimedGovernance[msg.sender]);
    claimPartnerGovernance(balance - userClaimedPartnerGovernance[msg.sender]);
}

+function claimWeight(uint256 amount) public virtual override checkWeight(amount * bHermesRate) {
+    super.claimWeight(amount * bHermesRate);
+}

+function claimGovernance(uint256 amount) public virtual override checkGovernance(amount * bHermesRate) {
+    super.claimGovernance(amount * bHermesRate);
+}

Assessed type

ERC4626

#0 - c4-judge

2023-07-09T11:30:43Z

trust1995 marked the issue as primary issue

#1 - c4-judge

2023-07-09T11:30:47Z

trust1995 marked the issue as satisfactory

#2 - c4-sponsor

2023-07-11T20:44:45Z

0xLightt marked the issue as sponsor confirmed

#3 - 0xLightt

2023-07-22T15:36:50Z

This is a duplicate of #470. The issue #470 has a more accurate solution

#4 - c4-judge

2023-07-25T07:54:28Z

trust1995 marked the issue as duplicate of #470

#5 - c4-judge

2023-07-25T14:07:20Z

trust1995 changed the severity to 2 (Med Risk)

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter