01. Relevant GitHub Links
02. Summary
The Transfer event is emitted multiple times when the DebtToken is minted and burned.
03. Vulnerability Details
The DebtToken::mint
function is emitting the Transfer event at the end. However, that event is emitted in the _update
function.
function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
if (user == address(0) || onBehalfOf == address(0)) revert InvalidAddress();
if (amount == 0) {
return (false, 0, totalSupply());
}
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
uint256 scaledBalance = balanceOf(onBehalfOf);
bool isFirstMint = scaledBalance == 0;
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}
_userState[onBehalfOf].index = index.toUint128();
uint256 amountToMint = amount + balanceIncrease;
_mint(onBehalfOf, amountToMint.toUint128());
@> emit Transfer(address(0), onBehalfOf, amountToMint);
emit Mint(user, onBehalfOf, amountToMint, balanceIncrease, index);
return (scaledBalance == 0, amountToMint, totalSupply());
}
The function that overrides the _update
function is also emitting a Transfer evnet once again, so there are a total of three Transfer events emitted.
function _update(address from, address to, uint256 amount) internal virtual override {
if (from != address(0) && to != address(0)) {
revert TransfersNotAllowed();
}
uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
super._update(from, to, scaledAmount);
emit Transfer(from, to, amount);
}
Here we have two cases where we are firing an event with scaledAmount and an event based on the amount value, which may be the intended behavior, but it's still a problem because they have the same event name.
Also, the same problem occurs with burn functions that use the _update
function.
If you look at the actual logs, you'll see that the Transfer event is emitted multiple times.
│ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], value: 48674145964211444886 [4.867e19])
│ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], value: 50000000000000000000 [5e19])
│ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], value: 50000000000000000000 [5e19])
│ │ ├─ emit Mint(caller: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], onBehalfOf: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], amount: 50000000000000000000 [5e19], balanceIncrease: 0, index: 1027239389814120488478727539 [1.027e27])
04. Impact
06. Tools Used
Manual Code Review and Foundry
07. Recommended Mitigation
Rename the event or emit the Transfer event from only one location.