https://github.com/tadle-com/market-evm/blob/main/src/core/TokenManager.sol#L146-L203
Summary
The contract lacks proper handling for insufficient balance situations in the withdraw
function. When a user attempts to claim his claimable amount TokenManager
is responsible to check the balance of the CapitalPool
contract since the claimAbleAmount
is stored there.
Vulnerability Details
No checks are made for the balance of CapitalPool
contract as we can see here:
TokenManager.sol
137: function withdraw(
138: address _tokenAddress,
139: TokenBalanceType _tokenBalanceType
140: ) external whenNotPaused {
141: uint256 claimAbleAmount = userTokenBalanceMap[_msgSender()][
142: _tokenAddress
143: ][_tokenBalanceType];
144:
145: if (claimAbleAmount == 0) {
146: return;
147: }
148:
149: address capitalPoolAddr = tadleFactory.relatedContracts(
150: RelatedContractLibraries.CAPITAL_POOL
151: );
152:
153: if (_tokenAddress == wrappedNativeToken) {
154:
155: * @dev token is native token
156: * @dev transfer from capital pool to msg sender
157: * @dev withdraw native token to token manager contract
158: * @dev transfer native token to msg sender
159: */
160: _transfer(
161: wrappedNativeToken,
162: capitalPoolAddr,
163: address(this),
164: claimAbleAmount,
165: capitalPoolAddr
166: );
167:
168: IWrappedNativeToken(wrappedNativeToken).withdraw(claimAbleAmount);
169: payable(msg.sender).transfer(claimAbleAmount);
170: } else {
171:
172: * @dev token is ERC20 token
173: * @dev transfer from capital pool to msg sender
174: */
175: _safe_transfer_from(
176: _tokenAddress,
177: capitalPoolAddr,
178: _msgSender(),
179: claimAbleAmount
180: );
181: }
182:
183: emit Withdraw(
184: _msgSender(),
185: _tokenAddress,
186: _tokenBalanceType,
187: claimAbleAmount
188: );
189: }
withdraw
function: The function assumes that the capital pool always has sufficient funds to cover the withdrawal request. There's no check to ensure that the claimAbleAmount
is available in the capital pool before attempting the transfer.
If the balance is insufficient, the transfer will fail, but there's no error handling mechanism or pre-check to prevent this from happening.
Impact
Lack of balance checks can lead to failed transactions without informative errors, causing user dissatisfaction and potential disruption of contract operations. Furthermore, repeated failed transactions might incur unnecessary gas costs.
Tools Used
Manual review
Recommendations
Add balance checks before transfers
function withdraw(
address _tokenAddress,
TokenBalanceType _tokenBalanceType
) external whenNotPaused {
uint256 claimAbleAmount = userTokenBalanceMap[_msgSender()][_tokenAddress][_tokenBalanceType];
address capitalPoolAddr = tadleFactory.relatedContracts(RelatedContractLibraries.CAPITAL_POOL);
+ uint256 capitalPoolBalance = IERC20(_tokenAddress).balanceOf(capitalPoolAddr);
+ require(capitalPoolBalance >= claimAbleAmount, "Capital pool has insufficient balance");
address capitalPoolAddr = tadleFactory.relatedContracts(
RelatedContractLibraries.CAPITAL_POOL
);
. . .
emit Withdraw(_msgSender(), _tokenAddress, _tokenBalanceType, claimAbleAmount);
}