Fee-on-transfer / rebasing collateral over-credits the depositor because deposit records the requested amount, not the amount received
Description
_deposit_collateral credits the user's balance with amount_collateral and only afterwards pulls the tokens via transferFrom, never measuring how much the contract actually received.
# dsc_engine.vy:223-229 (_deposit_collateral)
self.user_to_token_address_to_amount_deposited[msg.sender][
token_collateral_address
] += amount_collateral # @> credits requested amount, not received
log CollateralDeposited(msg.sender, amount_collateral)
success: bool = extcall IERC20(token_collateral_address).transferFrom(
msg.sender, self, amount_collateral
)
For a fee-on-transfer token the contract receives amount_collateral - fee, but the depositor is credited the full amount_collateral. The internal accounting now exceeds the engine's real token balance.
Risk
Likelihood:
Medium. Fee-on-transfer and rebasing tokens are common; the constructor accepts any token address with no exclusion.
Impact:
High. The depositor is credited collateral the protocol never received, inflating their borrowing power and letting them mint DSC against phantom value. When users redeem, the last redeemers' transfer reverts because the engine is short on tokens, locking funds — a direct over-credit / theft of value from other depositors.
Proof of Concept
Deposit a 2%-fee token and compare credited vs received.
# token charges 2% transfer fee
engine.deposit_collateral(fee_token, 100 * 10**18)
# contract received 98e18 but user credited 100e18
assert engine.get_collateral_balance_of_user(user, fee_token) == 100 * 10**18 # over-credited
Recommended Mitigation
Credit the measured balance delta, not the requested amount.
- self.user_to_token_address_to_amount_deposited[msg.sender][
- token_collateral_address
- ] += amount_collateral
- log CollateralDeposited(msg.sender, amount_collateral)
- success: bool = extcall IERC20(token_collateral_address).transferFrom(
- msg.sender, self, amount_collateral
- )
+ balance_before: uint256 = staticcall IERC20(token_collateral_address).balanceOf(self)
+ success: bool = extcall IERC20(token_collateral_address).transferFrom(msg.sender, self, amount_collateral)
+ assert success, "DSCEngine_TransferFailed"
+ received: uint256 = staticcall IERC20(token_collateral_address).balanceOf(self) - balance_before
+ self.user_to_token_address_to_amount_deposited[msg.sender][token_collateral_address] += received
+ log CollateralDeposited(msg.sender, received)