Summary
A double, desynchronized deposit accounting implementation in StabilityPool
and deToken
may lead to users losses and locked rTokens.
Vulnerability Details
On StabilityPool::deposit the amount deposited is minted as deToken and saved in userDeposit
mapping.
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);
}
On withdraw the opposite is done: burning of deToken and subtracting from userDeposits
function withdraw(uint256 deCRVUSDAmount) external nonReentrant whenNotPaused validAmount(deCRVUSDAmount) {
...
@> userDeposits[msg.sender] -= rcrvUSDAmount;
if (userDeposits[msg.sender] == 0) {
delete userDeposits[msg.sender];
}
@> deToken.burn(msg.sender, deCRVUSDAmount);
rToken.safeTransfer(msg.sender, rcrvUSDAmount);
The only purpose of userDeposits
mapping is to be used to calculated users RAAC rewards:
function calculateRaacRewards(address user) public view returns (uint256) {
@> uint256 userDeposit = userDeposits[user];
uint256 totalDeposits = deToken.totalSupply();
uint256 totalRewards = raacToken.balanceOf(address(this));
if (totalDeposits < 1e6) return 0;
@> return (totalRewards * userDeposit) / totalDeposits;
}
deToken is intended to be transferable. There's no locking mechanism added to transfer
/ transferFrom
/ burn
.
An user may obtain deToken from other means than depositing to StabilityPool
. He may think he can redeem deToken for rToken at any time and that he accrue RAAC rewards and decide to hold the token.
When he calls withdraw
to get back rToken and raacRewards, the transaction reverts with underflow panic error because his userDeposits
amount is 0
.
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;
Impact
Some deToken holders may not be able to withdraw their rToken and accumulated rewards.
Tools Used
Recommendations
Remove userDeposits
mapping. Calculate raac rewards using user's balanceOf
deToken instead.