Incorrect rounding down in HubPoolLogic#updateWithWithdraw
when users withdraw using underlying amount
Submitted on Sun Aug 04 2024 10:17:19 GMT-0400 (Atlantic Standard Time) by @nnez for Boost | Folks Finance
Report ID: #34030
Report type: Smart Contract
Report severity: Low
Target: https://testnet.snowtrace.io/address/0x96e957bF63B5361C5A2F45C97C46B8090f2745C2
Impacts:
- Contract fails to deliver promised returns, but doesn't lose value
- Smart contract unable to operate due to lack of token funds
Users' deposit balance is stored in fToken* amount. fToken is a type of interest-bearing token so its exchange rate back to underlying amount grows as interest index grows. Users who want to withdraw can specify whether to withdraw an exact underlying amount or withdraw with their fToken amount.
When users withdraw by specifying an underlying amount, the toFAmount
function converts this amount back to fToken. The conversion formula utilized in toFAmount
is as follows:
File: /contracts/hub/logic/HubPoolLogic.sol
function updateWithWithdraw(
HubPoolState.PoolData storage pool,
uint256 amount,
bool isFAmount
) external returns (DataTypes.WithdrawPoolParams memory withdrawPoolParams) {
// can withdraw even if pool is depreciated
// update interest indexes before the interest rates change
pool.updateInterestIndexes();
if (isFAmount) {
withdrawPoolParams.fAmount = amount;
withdrawPoolParams.underlingAmount = amount.toUnderlingAmount(pool.depositData.interestIndex);
} else {
withdrawPoolParams.underlingAmount = amount;
withdrawPoolParams.fAmount = amount.toFAmount(pool.depositData.interestIndex); @<-- incorrect rounding down
}
pool.depositData.totalAmount -= withdrawPoolParams.underlingAmount;
pool.updateInterestRates();
}
File: /contracts/hub/libraries/MathUtils.sol
function toFAmount(uint256 underlyingAmount, uint256 depositInterestIndexAtT) internal pure returns (uint256) {
return underlyingAmount.mulDiv(ONE_18_DP, depositInterestIndexAtT);
}
We can clearly see that toFAmount
rounds down the division result. This causes an issue when users withdraw small amounts, like 1 wei. The fToken
amount may round down to zero, allowing users to withdraw 1 wei of underlying token without correctly reducing their fToken
balance.
Adversaries can continously withdraw 1 wei of underlying amount of token without losing fToken
balance. Although the gas cost from transction would offset attacker's profit, it can potentially cause an accounting error in HubPool's totalAmount
.
A test in the secret gist demonstrates the following situation:
- BOB deposits 1e6 USDC and in his loan.
- BOB withdraws all in fUSDC amount, but left 1 wei of USDC in his loan.
- BOB withdraws again in underling amount, specifying 1 wei.
- BOB gets 1 wei of USDC but still keep his 1 wei of fUSDC in the loan.
- Run
forge init --no-commit --no-git --vscode
. - Create a new test file,
FolksWithdraw.t.sol
intest
directory. - Put the test from secret gist in the file: https://gist.github.com/nnez/ee5f1791aa5083286932bb3fd9c78e71
- Run
forge t --match-contract FolksWithdrawRoundingTest -vv
- Observe that BOB's usdc balance is up by 20 wei but he loses 0 amount of fUSDC in his loan.