Tadle

Tadle
DeFi
30,000 USDC
View results
Submission Details
Severity: low
Invalid

Approval Race Protection in Certain ERC20 Tokens Causes Reverts

Summary

Vulnerability Details

Some standard ERC20 tokens have approval race protections. Specifically, tokens like USDT and KNC do not allow approving an amount ( M > 0 ) when an existing amount ( N > 0 ) is already approved. In the current implementation, the approve function is called within the _transfer function, which is triggered for every tillIn() and withdraw() function call. The approve function does not reset the approval to zero before setting it to the desired value. Due to this, transactions involving these tokens can revert.

The issue is present in the approve function of the CapitalPool.sol :

/**
* @dev Approve token for token manager
* @notice only can be called by token manager
* @param tokenAddr address of token
*/
function approve(address tokenAddr) external {
address tokenManager = tadleFactory.relatedContracts(
RelatedContractLibraries.TOKEN_MANAGER
);
(bool success, ) = tokenAddr.call(
abi.encodeWithSelector(
APPROVE_SELECTOR,
tokenManager,
type(uint256).max
)
);
if (!success) {
revert ApproveFailed();
}
}

Additionally, this function is called by the _transfer function in TokenManager.sol:

function _transfer(
address _token,
address _from,
address _to,
uint256 _amount,
address _capitalPoolAddr
) internal {
uint256 fromBalanceBef = IERC20(_token).balanceOf(_from);
uint256 toBalanceBef = IERC20(_token).balanceOf(_to);
if (
_from == _capitalPoolAddr &&
IERC20(_token).allowance(_from, address(this)) == 0x0
) {
ICapitalPool(_capitalPoolAddr).approve(address(this));
}
_safe_transfer_from(_token, _from, _to, _amount);
uint256 fromBalanceAft = IERC20(_token).balanceOf(_from);
uint256 toBalanceAft = IERC20(_token).balanceOf(_to);
if (fromBalanceAft != fromBalanceBef - _amount) {
revert TransferFailed();
}
if (toBalanceAft != toBalanceBef + _amount) {
revert TransferFailed();
}
}

Note: submitting this issue `Med` because this code is used in critical funcitons and called frequently

Impact

The approve function directly sets the allowance to the maximum value without first resetting it to zero. This can cause transactions to revert for tokens that have built-in protections against changing the allowance from a non-zero value to another non-zero value.

Tools Used

Manual Review

Recommendations

Modify the approve function in CapitalPool.sol to first set the allowance to zero before setting it to the desired value. This ensures compatibility with tokens that have approval race protections.

Updates

Lead Judging Commences

0xnevi Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Other
Assigned finding tags:

[invalid] finding-CapitalPool-USDT-approve-zero-first

I believe this is invalid, - For weird ERC20s with front-running approval protection such as UDST (only known instance so far), max approval is likely only required to be invoked once, considering the supply cap of such tokens. (USDT supply is at 53.8 billion (53.8e9 * 1e9, so this is 100% sufficient) - If approvals are insufficient, a new proxy for tadle market can always be deployed via the TadleFactory contract and migrated

Support

FAQs

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