28980 - [SC - Insight] Ther is an invariant Check Failure in flashLoan...

Submitted on Mar 4th 2024 at 01:28:46 UTC by @XDZIBECX for Boost | eBTC

Report ID: #28980

Report type: Smart Contract

Report severity: Insight

Target: https://github.com/ebtc-protocol/ebtc/blob/release-0.7/packages/contracts/contracts/ActivePool.sol

Impacts:

  • Protocol insolvency

Description

Brief/Intro

There is a vulnerability in the flashLoan function is relates to an invariant check that assumes the collateral rate remains constant throughout the flash loan operation and this check fails to account for potential manipulation within the same transaction, allowing for inflation or deflation of collateral value. so If an attacker exploit this vulnerability it's could lead to arbitrage opportunities or unjust profit by creating discrepancies in collateral value without triggering the contract's safety checks, and this can impact the protocol's financial sand potentially lead to protocol insolvency if the discrepancies significantly affect the protocol's asset valuation.

Vulnerability Details

this is the vulnerable part :

require(
    collateral.getPooledEthByShares(DECIMAL_PRECISION) == oldRate,
    "ActivePool: Should keep same collateral share rate"
);

in this line is intended to ensure the collateral's share rate remains unchanged after a flash loan operation, and assuming that no external interactions can affect the collateral rate within the same transaction. so this assumption is flawed. and an attacker can manipulate the collateral's perceived value as an example, through market manipulation, oracle manipulation, within the transaction of the flash loan. This manipulation could temporarily inflate or deflate the collateral's value, allowing the attacker to benefit from the discrepancy in valuation, all while bypassing the contract's safety mechanisms designed to prevent such occurrences.

Impact Details

if an attacker exploit this vulenrbaility it's can be significant financial instability for the protocol the attackers could profit from the temporary inflation or deflation of collateral values, extracting value from the protocol unjustly. and in In severe cases, if the exploited discrepancies significantly impact the protocol's ability to maintain its financial obligations, it could lead to insolvency.

References

  • https://github.com/ebtc-protocol/ebtc/blob/a96bd000c23425f04c3223a441a625bfb21f6686/packages/contracts/contracts/ActivePool.sol#L288C1-L338C6

Proof of Concept

i fuzz with a scenario that show under certain conditions, when the collateral rate is manipulated within the transaction of a flash loan, the invariant check can fail. In the test, 481 out of 1000 runs failed the invariant check, indicating that the assumption of a constant collateral rate does not always hold. This serves as evidence that an attacker could exploit this assumption to their advantage.

here is the fuzz test :

import random

# Constants
DECIMAL_PRECISION = 10**18
FLASH_SUCCESS_VALUE = bytes(b'FlashLoanSuccess')

class MockCollateral:
    def __init__(self, rate):
        self.rate = rate
        self.initial_rate = rate

    def getPooledEthByShares(self, shares):
        # Simplified to return a constant rate for demonstration
        return shares * self.rate // DECIMAL_PRECISION

    def manipulateRate(self, new_rate):
        # Temporarily manipulate the rate for the duration of the flash loan
        self.rate = new_rate

    def resetRate(self):
        # Reset the rate to its initial value
        self.rate = self.initial_rate

    def transfer(self, receiver, amount):
        pass

    def transferFrom(self, sender, receiver, amountWithFee):
        pass

class ActivePool:
    def __init__(self, collateral):
        self.collateral = collateral
        self.systemCollShares = 1000000  # Example initial system collateral shares

    def flashLoan(self, receiver, token, amount, data):
        oldRate = self.collateral.getPooledEthByShares(DECIMAL_PRECISION)
        # Simulate collateral transfer to receiver
        self.collateral.transfer(receiver, amount)
        # Simulate callback and repayment
        callback_result = FLASH_SUCCESS_VALUE  # Assume success for simplicity
        if callback_result != FLASH_SUCCESS_VALUE:
            return False
        # Simulate manipulation within the transaction
        manipulated_rate = random.randint(1, 2) * oldRate
        self.collateral.manipulateRate(manipulated_rate)
        # Invariant check (focus of our simulation)
        assert self.collateral.getPooledEthByShares(DECIMAL_PRECISION) == oldRate, "Should keep same collateral share rate"
        # Reset the rate to ensure further operations are not affected
        self.collateral.resetRate()
        return True

# Mock setup
collateral = MockCollateral(rate=DECIMAL_PRECISION)  # 1:1 rate for simplicity
active_pool = ActivePool(collateral=collateral)

# Attempt flash loan
result = active_pool.flashLoan("receiver", "token", 1000, "data")
print("Flash Loan Successful:", result)

The fuzz test run for 1000 test cases, out of which the invariant check failed in 481 cases. this is demonstrate that under certain conditions, specifically when the collateral rate is manipulated within the transaction of a flash loan, the invariant check (collateral.getPooledEthByShares(DECIMAL_PRECISION) == oldRate) can fail. This indicates show the vulnerability where the assumption that the collateral rate will remain constant during the flash loan operation does not hold true.

Last updated