Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: low
Invalid

`LendingPool::_rebalanceLiquidity()` emits `LiquidityRebalanced` with incorrect values

Summary

LendingPool::_rebalanceLiquidity() emits LiquidityRebalanced with the wrong asset balance/buffer.

Vulnerability Details

The LendingPool::_rebalanceLiquidity() internal function is responsible for rebalancing liquidity by meeting the desired buffer. This function is called upon LendingPool::deposit(), LendingPool::withdraw(), and LendingPool::borrow() and only rebalances assuming curveVault is not address 0 and there is a difference between buffers:

function _rebalanceLiquidity() internal {
// if curve vault is not set, do nothing
if (address(curveVault) == address(0)) {
return;
}
uint256 totalDeposits = reserve.totalLiquidity; // Total liquidity in the system
uint256 desiredBuffer = totalDeposits.percentMul(liquidityBufferRatio);
uint256 currentBuffer = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
if (currentBuffer > desiredBuffer) {
uint256 excess = currentBuffer - desiredBuffer;
// Deposit excess into the Curve vault
_depositIntoVault(excess);
} else if (currentBuffer < desiredBuffer) {
uint256 shortage = desiredBuffer - currentBuffer;
// Withdraw shortage from the Curve vault
_withdrawFromVault(shortage);
}
emit LiquidityRebalanced(currentBuffer, totalVaultDeposits);
}

The function determines the desiredBuffer based on the total liquidity and buffer ratio, while getting the currentBuffer based on the current balance of the reserve asset in the contract:

uint256 totalDeposits = reserve.totalLiquidity; // Total liquidity in the system
uint256 desiredBuffer = totalDeposits.percentMul(liquidityBufferRatio);
uint256 currentBuffer = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);

It is then determined that if the currentBuffer is higher than the desiredBuffer, deposit into Curve, else, withdraw from Curve.

if (currentBuffer > desiredBuffer) {
uint256 excess = currentBuffer - desiredBuffer;
// Deposit excess into the Curve vault
_depositIntoVault(excess);
} else if (currentBuffer < desiredBuffer) {
uint256 shortage = desiredBuffer - currentBuffer;
// Withdraw shortage from the Curve vault
_withdrawFromVault(shortage);
}

Both _depositIntoVault() & _withdrawFromVault() functions update the current balance of the reserve asset (essentially meeting the expectation of making the currentBuffer the desiredBuffer) and the totalVaultDeposits state:

function _depositIntoVault(uint256 amount) internal {
IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;
}
function _withdrawFromVault(uint256 amount) internal {
curveVault.withdraw(amount, address(this), msg.sender, 0, new address[](0));
totalVaultDeposits -= amount;
}

Afterwards, execution returns back to _rebalanceLiquidity() where the following event is emitted:

emit LiquidityRebalanced(currentBuffer, totalVaultDeposits);

As is, the event emits currentBuffer for the 1st argument, which is the local value set before the deposit or withdraw. This results in an incorrect value as the balance of the reserve asset has changed. The currentBuffer is no longer the amount of liquidity in the buffer.

Furthermore, as per the defined interface for the event, the 1st argument should represent the liquidity in the buffer and the 2nd argument should be the liquidity in the vault:

/**
* @notice Emitted when liquidity is rebalanced
* @param bufferAmount The amount of liquidity in the buffer
* @param vaultAmount The amount of liquidity in the Curve vault
*/
event LiquidityRebalanced(uint256 bufferAmount, uint256 vaultAmount);

Impact

Frontend and off-chain services relying on the LiquidityRebalanced event will receive incorrect values for liquidity in the buffer.

Tools Used

Recommendations

Pass in desiredBuffer instead of currentBuffer for the 1st argument in LiquidityRebalanced:

function _rebalanceLiquidity() internal {
// if curve vault is not set, do nothing
if (address(curveVault) == address(0)) {
return;
}
uint256 totalDeposits = reserve.totalLiquidity; // Total liquidity in the system
uint256 desiredBuffer = totalDeposits.percentMul(liquidityBufferRatio);
uint256 currentBuffer = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
if (currentBuffer > desiredBuffer) {
uint256 excess = currentBuffer - desiredBuffer;
// Deposit excess into the Curve vault
_depositIntoVault(excess);
} else if (currentBuffer < desiredBuffer) {
uint256 shortage = desiredBuffer - currentBuffer;
// Withdraw shortage from the Curve vault
_withdrawFromVault(shortage);
}
- emit LiquidityRebalanced(currentBuffer, totalVaultDeposits);
+ emit LiquidityRebalanced(desiredBuffer, totalVaultDeposits);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_rebalanceLiquidity emits an event with stale buffer

Invalidated by appeal in 1090

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_rebalanceLiquidity emits an event with stale buffer

Invalidated by appeal in 1090

Appeal created

inallhonesty Lead Judge
25 days ago
inallhonesty Lead Judge 25 days ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

LendingPool::_rebalanceLiquidity emits an event with stale buffer

Invalidated by appeal in 1090

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.