File Location: protocol/contracts/libraries/LibFertilizer.sol#L85-L143
Vulnerability Details
Without measuring the balance before and after the transfer, there's no way to ensure that enough tokens were transferred, in the cases where the token has a fee-on-transfer mechanic. If there are latent funds in the contract, subsequent transfers will succeed.
Impact
Tools Used
Inspection manual
Solidity
Recommendations
To fix the fee-on-token-transfer vulnerability, we need to measure the balance before and after the transfer to ensure the right amount of tokens has been transferred. Here is the corrected version of the ‘addUnderlying’ function in ‘LibFertilizer.sol’.
Code snippet:
L85-L143
function addUnderlying(
uint256 tokenAmountIn,
uint256 usdAmount,
uint256 minAmountOut
) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 percentToFill = usdAmount.mul(C.precision()).div(remainingRecapitalization());
uint256 newDepositedBeans;
if (
C.unripeBean().totalSupply() >
s.sys.silo.unripeSettings[C.UNRIPE_BEAN].balanceOfUnderlying
) {
newDepositedBeans = (C.unripeBean().totalSupply()).sub(
s.sys.silo.unripeSettings[C.UNRIPE_BEAN].balanceOfUnderlying
);
newDepositedBeans = newDepositedBeans.mul(percentToFill).div(C.precision());
}
uint256 newDepositedLPBeans = usdAmount.mul(C.exploitAddLPRatio()).div(DECIMALS);
C.bean().mint(address(this), newDepositedBeans);
address barnRaiseWell = LibBarnRaise.getBarnRaiseWell();
address barnRaiseToken = LibBarnRaise.getBarnRaiseToken();
C.bean().mint(address(this), newDepositedLPBeans);
IERC20(barnRaiseToken).transferFrom(
LibTractor._user(),
address(this),
uint256(tokenAmountIn)
);
IERC20(barnRaiseToken).approve(barnRaiseWell, uint256(tokenAmountIn));
C.bean().approve(barnRaiseWell, newDepositedLPBeans);
uint256[] memory tokenAmountsIn = new uint256[](2);
IERC20[] memory tokens = IWell(barnRaiseWell).tokens();
(tokenAmountsIn[0], tokenAmountsIn[1]) = tokens[0] == C.bean()
? (newDepositedLPBeans, tokenAmountIn)
: (tokenAmountIn, newDepositedLPBeans);
uint256 newLP = IWell(barnRaiseWell).addLiquidity(
tokenAmountsIn,
minAmountOut,
address(this),
type(uint256).max
);
LibUnripe.incrementUnderlying(C.UNRIPE_BEAN, newDepositedBeans);
LibUnripe.incrementUnderlying(C.UNRIPE_LP, newLP);
s.sys.fert.recapitalized = s.sys.fert.recapitalized.add(usdAmount);
}
Fixed code:
function addUnderlying(
uint256 tokenAmountIn,
uint256 usdAmount,
uint256 minAmountOut
) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 percentToFill = usdAmount.mul(C.precision()).div(remainingRecapitalization());
uint256 newDepositedBeans;
if (
C.unripeBean().totalSupply() >
s.sys.silo.unripeSettings[C.UNRIPE_BEAN].balanceOfUnderlying
) {
newDepositedBeans = (C.unripeBean().totalSupply()).sub(
s.sys.silo.unripeSettings[C.UNRIPE_BEAN].balanceOfUnderlying
);
newDepositedBeans = newDepositedBeans.mul(percentToFill).div(C.precision());
}
uint256 newDepositedLPBeans = usdAmount.mul(C.exploitAddLPRatio()).div(DECIMALS);
C.bean().mint(address(this), newDepositedBeans);
address barnRaiseWell = LibBarnRaise.getBarnRaiseWell();
address barnRaiseToken = LibBarnRaise.getBarnRaiseToken();
C.bean().mint(address(this), newDepositedLPBeans);
uint256 balanceBefore = IERC20(barnRaiseToken).balanceOf(address(this));
IERC20(barnRaiseToken).transferFrom(
LibTractor._user(),
address(this),
uint256(tokenAmountIn)
);
uint256 balanceAfter = IERC20(barnRaiseToken).balanceOf(address(this));
uint256 actualTokenAmountIn = balanceAfter.sub(balanceBefore);
IERC20(barnRaiseToken).approve(barnRaiseWell, actualTokenAmountIn);
C.bean().approve(barnRaiseWell, newDepositedLPBeans);
uint256[] memory tokenAmountsIn = new uint256[](2);
IERC20[] memory tokens = IWell(barnRaiseWell).tokens();
(tokenAmountsIn[0], tokenAmountsIn[1]) = tokens[0] == C.bean()
? (newDepositedLPBeans, actualTokenAmountIn)
: (actualTokenAmountIn, newDepositedLPBeans);
uint256 newLP = IWell(barnRaiseWell).addLiquidity(
tokenAmountsIn,
minAmountOut,
address(this),
type(uint256).max
);
LibUnripe.incrementUnderlying(C.UNRIPE_BEAN, newDepositedBeans);
LibUnripe.incrementUnderlying(C.UNRIPE_LP, newLP);
s.sys.fert.recapitalized = s.sys.fert.recapitalized.add(usdAmount);
}
Explanation:
Added 'balanceBefore' variable to store token balance before transfer.
Added variable 'balanceAfter' to store token balance after transfer.
Calculate the number of tokens actually received 'actualTokenAmountIn' by subtracting 'balanceBefore' from 'balanceAfter'.