DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: low
Invalid

Inaccurate Token Accounting in Fee-on-Transfer Token Handling

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

  • Inaccurate Accounting

  • Subsequent Transfer Issues

  • Potential Exploits

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();
// Calculate how many new Deposited Beans will be minted
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());
}
// Calculate how many Beans to add as LP
uint256 newDepositedLPBeans = usdAmount.mul(C.exploitAddLPRatio()).div(DECIMALS);
// Mint the Deposited Beans to Beanstalk.
C.bean().mint(address(this), newDepositedBeans);
// Mint the LP Beans and add liquidity to the well.
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
);
// Increment underlying balances of Unripe Tokens
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();
// Hitung berapa banyak Deposited Beans baru yang akan dicetak
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());
}
// Hitung berapa banyak Beans yang akan ditambahkan sebagai LP
uint256 newDepositedLPBeans = usdAmount.mul(C.exploitAddLPRatio()).div(DECIMALS);
// Cetak Deposited Beans ke Beanstalk.
C.bean().mint(address(this), newDepositedBeans);
// Cetak LP Beans dan tambahkan likuiditas ke well.
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
);
// Tambahkan saldo underlying dari Unripe Tokens
LibUnripe.incrementUnderlying(C.UNRIPE_BEAN, newDepositedBeans);
LibUnripe.incrementUnderlying(C.UNRIPE_LP, newLP);
s.sys.fert.recapitalized = s.sys.fert.recapitalized.add(usdAmount);
}

Explanation:

  1. Added 'balanceBefore' variable to store token balance before transfer.

  2. Added variable 'balanceAfter' to store token balance after transfer.

  3. Calculate the number of tokens actually received 'actualTokenAmountIn' by subtracting 'balanceBefore' from 'balanceAfter'.

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Known - Bean Part 1

Support

FAQs

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