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
);
}