Tadle

Tadle
DeFiFoundry
27,750 USDC
View results
Submission Details
Severity: medium
Invalid

Critical functions can be DOSed

Summary

It was noted that the reason the protocol decided to implement the balance check in the following implementation is due to fee-on-transfer tokens like USDT. They stated that they were going to accept all ERC20 tokens. However, this implementation also opens up another vulnerability that the protocol is currently not aware of.

Description

The vulnerability involves a malicious intent by an attacker to disrupt the smooth operation of the protocol by front-running and observing transactions before sending dust amounts to user addresses or protocol-specific important contracts. This could lead to a temporary DoS, but could also be implemented on a large scale.

The TokenManager::withdraw function allows the transfer of both ERC20 tokens and native tokens. It facilitates the transfer from the capital pool to msg.sender. However, while making this transfer call, it invokes an internal _transfer function that checks the protocol-specific contract (_capitalPoolAddr) fromBalanceBef and fromBalanceAft to ensure that there was no fee on transfer when users send funds into the _capitalPoolAddr, which is a sound logic. But this opens up a path for an attacker to send in dust amounts to break this logic.

An attacker could monitor transactions in the mempool and send in dust amounts before the transaction gets added to a block and the state changes.

The same issue is possible with the (toBalanceAft != toBalanceBef + _amount) check.

Code snippet

if (fromBalanceAft != fromBalanceBef - _amount) {
revert TransferFailed();
}

Impact

Temporary DOS to the protocol and it users

POC

function withdraw(
address _tokenAddress,
TokenBalanceType _tokenBalanceType
) external whenNotPaused {
uint256 claimAbleAmount = userTokenBalanceMap[_msgSender()][
_tokenAddress
][_tokenBalanceType];
if (claimAbleAmount == 0) {
return;
}
address capitalPoolAddr = tadleFactory.relatedContracts(
RelatedContractLibraries.CAPITAL_POOL
);
if (_tokenAddress == wrappedNativeToken) {
/**
* @dev token is native token
* @dev transfer from capital pool to msg sender
* @dev withdraw native token to token manager contract
* @dev transfer native token to msg sender
*/
_transfer( //@audit can be DOSED with dust also - transfers weth from the capitalPool back to this contract (tokenManager)
wrappedNativeToken,
capitalPoolAddr,
address(this),
claimAbleAmount,
capitalPoolAddr
);
IWrappedNativeToken(wrappedNativeToken).withdraw(claimAbleAmount);
payable(msg.sender).transfer(claimAbleAmount);
} else {
/**
* @dev token is ERC20 token
* @dev transfer from capital pool to msg sender
*/
_safe_transfer_from(
_tokenAddress,
capitalPoolAddr,
_msgSender(),
claimAbleAmount
);
}
emit Withdraw(
_msgSender(),
_tokenAddress,
_tokenBalanceType,
claimAbleAmount
);
}

Mitigation

Restructure the logic and make new provisions for transfer

Updates

Lead Judging Commences

0xnevi Lead Judge
12 months ago
0xnevi Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

mansa11 Auditor
12 months ago
0xnevi Lead Judge
12 months ago
0xnevi Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.