Beanstalk Notion
Beanstalk Notion
/
🪲
Bug Reports
/
BIC Notes
/
📄
Report #34873
📄

Report #34873

Report Date
August 29, 2024
Status
Closed
Payout

Unchecked Division in Liquidity Calculation

‣
Report Info

Report ID

#34873

Report type

Smart Contract

Has PoC?

Yes

Target

https://etherscan.io/address/0xBA150C2ae0f8450D4B832beeFa3338d4b5982d26

Impacts

Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)

Description

The ConstantProduct2 contract is designed to handle liquidity calculations for a decentralized exchange involving two tokens. The calcReserveAtRatioLiquidity function calculates the reserve of a token based on given liquidity ratios. It performs a division operation using these ratios to determine the new reserve.

Vulnerability Details

The calcReserveAtRatioLiquidity function does not check whether the denominator (ratio[i]) is zero before performing the division.

Impact Details

  • Malicious actors could exploit this vulnerability to deliberately cause disruptions.
  • Users attempting to execute this function will experience transaction failures, leading to wasted gas fees.

References

    function calcReserveAtRatioLiquidity(
        uint256[] calldata reserves,
        uint256 j,
        uint256[] calldata ratios,
        bytes calldata
    ) external pure override returns (uint256 reserve) {
        uint256 i = j == 1 ? 0 : 1;
@=>     reserve = reserves[i] * ratios[j] / ratios[i];
    }

Proof of concept

  1. Initializing Reserves and Ratios:
  • Suppose we have two tokens with reserves reserves = [1000, 2000].
  • Suppose the given liquidity ratio is ratios = [0, 500], where ratios[0] is zero.
  1. Calling the calcReserveAtRatioLiquidity Function:

The calcReserveAtRatioLiquidity function is called with the parameters:

  • reserves = [1000, 2000]
  • j = 1 (calculates the reserves for the second token)
  • ratios = [0, 500]
  • bytes calldata (unused)
  1. Executing the Function:
  • The function sets the index i as 0 since j is 1.
  • The function tries to calculate reserves using the formula:

reserve = reserves[i] * ratios[j] / ratios[i];

  • With i = 0, this becomes:
reserve = reserves[0] * ratios[1] / ratios[0];
reserve = 1000 * 500 / 0;
  1. Division by Zero :
  • Since ratios[0] is zero, dividing 1000 * 500 / 0 will cause a division by zero.
  • In Solidity, this will trigger a contract failure and abort the transaction, resulting in a panic error with code 0x12.

Recommendation

Add a require statement to ensure that ratios[i] is not zero before performing the division.

function calcReserveAtRatioLiquidity(
    uint256[] calldata reserves,
    uint256 j,
    uint256[] calldata ratios,
    bytes calldata
) external pure override returns (uint256 reserve) {
    uint256 i = j == 1 ? 0 : 1;
+   require(ratios[i] != 0, "Division by zero");
    reserve = reserves[i] * ratios[j] / ratios[i];
}

Immunefi Response

We have reviewed your report and regret to inform you that we will have to close it due to inadequate proof of concept (PoC).

Immunefi review:

  • assessed impact by the triage team is in scope for the bug bounty program
  • assessed asset by the triage team is in scope for the bug bounty program
  • The submitted PoC does not correspond to the selected impact.
  • Technical Review:
  • POC doesn't demonstrate griefing and simply showing a revert.

To ensure the proper escalation and evaluation of your report, Immunefi has checked the PoC to see if it matches the assessed impact and bug description, as well as verified the accuracy of your claims.

Please note that the project's team will receive a report of the closed submission and may choose to re-open it at their discretion. However, they are under no obligation to do so.

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

import "forge-std/Test.sol";
import "../src/functions/ConstantProduct2.sol";

contract ConstantProduct2Test is Test {
    ConstantProduct2 constantProduct2;

    function setUp() public {
        constantProduct2 = new ConstantProduct2();
    }

    function testDivisionByZeroInCalcReserveAtRatioLiquidity() public {
        uint256[] memory reserves = new uint256[](2);
        reserves[0] = 1000;
        reserves[1] = 2000;

        uint256[] memory ratios = new uint256[](2);
        ratios[0] = 0;
        ratios[1] = 500;

        bytes memory data = "";

        // Expect a revert with panic code 0x12 (division by zero)
        vm.expectRevert();
        constantProduct2.calcReserveAtRatioLiquidity(reserves, 1, ratios, data);
    }
}