Summary
The users deposit rToken to stability pool and receive RAAC reward while withdrawing. Althouth RToken.transferfrom()
function uses normalized amount as its param, both StabilityPool.deposit()
and StabilityPool.withdraw()
use scaled amount of rToken for deposit and withdraw. Therefore, depositors can't withdraw total deposited rToken in the pool.
Vulnerability Details
Both StabilityPool.deposit()
and StabilityPool.withdraw()
use scaled amount of rToken for deposit and withdraw.
function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
_update();
@> rToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
deToken.mint(msg.sender, deCRVUSDAmount);
@> userDeposits[msg.sender] += amount;
_mintRAACRewards();
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}
function withdraw(uint256 deCRVUSDAmount) external nonReentrant whenNotPaused validAmount(deCRVUSDAmount) {
_update();
if (deToken.balanceOf(msg.sender) < deCRVUSDAmount) revert InsufficientBalance();
uint256 rcrvUSDAmount = calculateRcrvUSDAmount(deCRVUSDAmount);
uint256 raacRewards = calculateRaacRewards(msg.sender);
if (userDeposits[msg.sender] < rcrvUSDAmount) revert InsufficientBalance();
@> userDeposits[msg.sender] -= rcrvUSDAmount;
if (userDeposits[msg.sender] == 0) {
delete userDeposits[msg.sender];
}
deToken.burn(msg.sender, deCRVUSDAmount);
@> rToken.safeTransfer(msg.sender, rcrvUSDAmount);
if (raacRewards > 0) {
raacToken.safeTransfer(msg.sender, raacRewards);
}
emit Withdraw(msg.sender, rcrvUSDAmount, deCRVUSDAmount, raacRewards);
}
However, RToken.transferfrom()
receives normalized amount as its param. Therefore, the scaled amount of the deposited amount at withdraw timestamp will be less than the amount at deposit timestamp and users can't withdraw total deposited rToken in the pool.
function transferFrom(address sender, address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
uint256 scaledAmount = amount.rayDiv(_liquidityIndex);
return super.transferFrom(sender, recipient, scaledAmount);
}
Impact
The users can't withdraw total deposited amount of rToken in stability pool.
Tools Used
Manual Review
Recommendations
Use normalized amount for deposit() and withdraw() in stability pool.
function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
_update();
+ lendingPool.updateState();
+ normalizedAmount = amount.rayMul(lendingPool.getNormalizedIncome());
+ rToken.safeTransferFrom(msg.sender, address(this), normalizedAmount);
- rToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
deToken.mint(msg.sender, deCRVUSDAmount);
userDeposits[msg.sender] += amount;
_mintRAACRewards();
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}
function withdraw(uint256 deCRVUSDAmount) external nonReentrant whenNotPaused validAmount(deCRVUSDAmount) {
_update();
if (deToken.balanceOf(msg.sender) < deCRVUSDAmount) revert InsufficientBalance();
uint256 rcrvUSDAmount = calculateRcrvUSDAmount(deCRVUSDAmount);
uint256 raacRewards = calculateRaacRewards(msg.sender);
if (userDeposits[msg.sender] < rcrvUSDAmount) revert InsufficientBalance();
userDeposits[msg.sender] -= rcrvUSDAmount;
if (userDeposits[msg.sender] == 0) {
delete userDeposits[msg.sender];
}
deToken.burn(msg.sender, deCRVUSDAmount);
+ lendingPool.updateState();
+ normalizedAmount = rcrvUSDAmount.rayMul(lendingPool.getNormalizedIncome());
+ rToken.safeTransfer(msg.sender, normalizedAmount);
- rToken.safeTransfer(msg.sender, rcrvUSDAmount);
if (raacRewards > 0) {
raacToken.safeTransfer(msg.sender, raacRewards);
}
emit Withdraw(msg.sender, rcrvUSDAmount, deCRVUSDAmount, raacRewards);
}