F-2024-0002·missing-validation

Fee-on-transfer tokens can cause accounting discrepancies in asset recovery functions

Acknowledgedliquid-stakinglstvalidatorgithub.com/lidofinance/community-staking-module
TL;DR

AssetRecovererLib.recoverERC20() emits the requested amount and does not measure the post-transfer balance delta, leading to misleading event data for fee-on-transfer tokens.

Severity
LOW
Impact
LOW
Likelihood
LOW
Method
MManual review
CAT.
Complexity
LOW
Exploitability
LOW
02Section · Description

Description

The AssetRecovererLib library, which is used by CSAccounting, CSFeeDistributor, and potentially other contracts inheriting from AssetRecoverer, contains a recoverERC20() function that is vulnerable to fee-on-transfer token accounting issues.

This function assumes that the full amount specified will be transferred, which may not be the case for tokens that implement a fee-on-transfer mechanism.

solidity
function recoverERC20(address token, uint256 amount) external {
IERC20(token).safeTransfer(msg.sender, amount);
emit IAssetRecovererLib.ERC20Recovered(token, msg.sender, amount);
}
03Section · Impact

Impact

If a fee-on-transfer token is recovered using this function, the actual amount transferred will be less than the amount specified. This discrepancy could lead to:

  • Incorrect accounting of recovered assets.
  • Potential loss of funds if the contract's balance is used for future calculations or operations.
  • Inconsistencies between on-chain state and off-chain records.
  • Misleading event emissions, as the emitted amount may not reflect the actual transferred amount.

While the impact is generally low due to the restricted access of these functions (only callable by a recoverer role), it still presents a risk, especially if these contracts interact with or recover a wide range of tokens in the future.

Proof of Concept:

  1. Assume a fee-on-transfer token that takes a 1% fee on each transfer.
  2. The contract has a balance of 1000 of these tokens.
  3. A recoverer calls recoverERC20 with an amount of 1000.
  4. Only 990 tokens are actually transferred due to the fee.
  5. The ERC20Recovered event is emitted with an amount of 1000, which is incorrect.
04Section · Recommendation

Recommendation

To mitigate this issue, implement a balance check before and after the transfer to determine the actual amount transferred:

solidity
function recoverERC20(address token, uint256 amount) external {
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
IERC20(token).safeTransfer(msg.sender, amount);
uint256 balanceAfter = IERC20(token).balanceOf(address(this));
uint256 actualAmountTransferred = balanceBefore - balanceAfter;
emit IAssetRecovererLib.ERC20Recovered(token, msg.sender, actualAmountTransferred);
}
F-2024-0002

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx