Summary
The msg.value check in the _tokenAddress == wrappedNativeToken branch of the TokenManager::tillIn() function is incorrect, which may cause user losses.
Vulnerability Details
In the TokenManager::tillIn() function, as indicated by the @> mark in the code snippet, the function only reverts when msg.value < _amount. If a user mistakenly sends an amount greater than _amount while using ETH to call functions like PreMarkets::listOffer() or PreMarkets::createOffer(), the excess ETH will remain in the TokenManager contract, leading to potential user losses.
function tillIn(
address _accountAddress,
address _tokenAddress,
uint256 _amount,
bool _isPointToken
)
external
payable
onlyRelatedContracts(tadleFactory, _msgSender())
onlyInTokenWhiteList(_isPointToken, _tokenAddress)
{
if (_tokenAddress == wrappedNativeToken) {
* @dev token is native token
* @notice check msg value
* @dev if msg value is less than _amount, revert
* @dev wrap native token and transfer to capital pool
*/
@> if (msg.value < _amount) {
revert Errors.NotEnoughMsgValue(msg.value, _amount);
}
@> IWrappedNativeToken(wrappedNativeToken).deposit{value: _amount}();
@> _safe_transfer(wrappedNativeToken, capitalPoolAddr, _amount);
} else {
_transfer(
_tokenAddress,
_accountAddress,
capitalPoolAddr,
_amount,
capitalPoolAddr
);
}
}
Poc
To demonstrate the issue, add the following test code to test/PreMarkets.t.sol and run it:
function testTokenManager_tillIn_eth_error() public {
console2.log("ETH balance at the start of address (tokenManager)",address(tokenManager).balance);
vm.startPrank(user);
preMarktes.createOffer{value: 0.12 * 1e18}(
CreateOfferParams(
marketPlace,
address(weth9),
1000,
0.01 * 1e18,
12000,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
deal(user2, 2e18);
vm.stopPrank();
address userOfferAddr = GenerateAddress.generateOfferAddress(0);
vm.prank(user2);
preMarktes.createTaker{value: 0.1035 * 1e18}(userOfferAddr, 1000);
vm.stopPrank();
address user2StockAddr = GenerateAddress.generateStockAddress(1);
vm.prank(user1);
systemConfig.updateMarket(
"Backpack",
address(mockPointToken),
0.01 * 1e18,
block.timestamp - 1,
3600
);
vm.startPrank(user);
mockPointToken.approve(address(tokenManager), 10000 * 10 ** 18);
deliveryPlace.settleAskMaker(userOfferAddr, 1000);
vm.stopPrank();
vm.prank(user2);
deliveryPlace.closeBidTaker(user2StockAddr);
console2.log("weth9.balanceOf(address(capitalPool)):",weth9.balanceOf(address(capitalPool)));
console2.log("ETH balance at the end of address(tokenManager):",address(tokenManager).balance);
}
From the output, we can see that the excess ETH remains in the TokenManager contract address, leading to potential losses for users.
Code Snippet
https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/TokenManager.sol#L47-L103
Impact
The incorrect msg.value check in the _tokenAddress == wrappedNativeToken branch of the TokenManager::tillIn() function can cause users to lose excess ETH, which remains locked in the contract.
Tools Used
Manual Review
Recommendations
Consider making the following changes:
if (_tokenAddress == wrappedNativeToken) {
/**
* @dev token is native token
* @notice check msg value
* @dev if msg value is less than _amount, revert
* @dev wrap native token and transfer to capital pool
*/
- if (msg.value < _amount) {
+ if (msg.value != _amount) {
revert Errors.NotEnoughMsgValue(msg.value, _amount); // The error code should be modified at the same time
}
IWrappedNativeToken(wrappedNativeToken).deposit{value: _amount}();
_safe_transfer(wrappedNativeToken, capitalPoolAddr, _amount);
} else {
/// @notice token is ERC20 token
_transfer(
_tokenAddress,
_accountAddress,
capitalPoolAddr,
_amount,
capitalPoolAddr
);
}