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
Was this helpful?