Tadle

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

System fails to properly handle ERC20 tokens which return false instead of reverting which could result in loss of user funds

Summary

The Rescuable contract includes safe_transfer and safe_transfer_from functions to handle token transfers. These functions are used by the TokenManager contract for depositing and withdrawing funds. However, the current implementation is vulnerable to issues with ERC20 tokens that do not revert on failure but instead return false. This behavior is technically compliant with the ERC20 standard but can lead to unexpected issues if not handled correctly.

Vulnerability Details

Some ERC20 tokens do not revert on failure; instead, they return false when the transfer or transferFrom fails (e.g., ZRX, EURS). The current implementation of _safe_transfer and _safe_transfer_from in the Rescuable contract does not account for this case. It only checks if the low-level call to the token contract was successful, but it does not check the return value of the token's function.

Current Implementation:

function _safe_transfer(address token, address to, uint256 amount) internal {
(bool success,) = token.call(abi.encodeWithSelector(TRANSFER_SELECTOR, to, amount));
if (!success) {
revert TransferFailed();
}
}
function _safe_transfer_from(address token, address from, address to, uint256 amount) internal {
(bool success,) = token.call(abi.encodeWithSelector(TRANSFER_FROM_SELECTOR, from, to, amount));
if (!success) {
revert TransferFailed();
}
}
  • Potential Token Transfers with Insufficient Funds: If an ERC20 token function returns false but does not revert, the _safe_transfer and _safe_transfer_from functions will not catch this scenario. This means that the functions might execute as if the transfer was successful, even if it was not.

  • User Exploitation: Attackers could exploit this behavior to "transfer" tokens they do not actually own or create fake offers and withdraw amounts that were not properly transferred, leading to potential theft and loss of funds.


PoC

You can observe the aforementioned behavior with the following Proof of Concept:

Steps:

  1. Create a new file called CapitalPool.t.sol in the test folder.

  2. Copy and paste the following content into it:

PoC code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
contract CapitalPool {
error CallFailed();
function callToNotRevertingFunction(address token) external {
(bool success,) = token.call(abi.encodeWithSignature("notRevert()"));
if (!success) {
revert CallFailed();
}
}
}
contract Token {
function notRevert() external pure returns (bool) {
return false;
}
}
contract CapitalPoolTest is Test {
CapitalPool public capitalPool;
Token public token;
function setUp() external {
capitalPool = new CapitalPool();
token = new Token();
}
function test_CallWillSucceed() external {
capitalPool.callToNotRevertingFunction(address(token));
}
}
  1. Run the test using the command: forge test --mt test_CallWillSucceed --via-ir -vv

Console output

Ran 1 test for test/CapitalPool.t.sol:CapitalPoolTest
[PASS] test_CallWillSucceed() (gas: 10493)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 13.04ms (2.00ms CPU time)

As demonstrated, the test passes because the low-level call was successful, and we do not check for the returned value from the function call. This mirrors the issue found in the safe_transfer and safe_transfer_from functions.

Impact

Attackers could exploit this vulnerability to drain funds from the protocol by creating offers and withdrawing amounts that were not actually transferred.

Tools Used

VSCode, Foundry

Recommendations

Update the _safe_transfer and _safe_transfer_from functions to use OpenZeppelin’s safeTransfer and safeTransferFrom methods. This ensures that both the success of the transaction and the return value are properly handled.

Updates

Lead Judging Commences

0xnevi Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

[invalid] finding-weird-erc-20-return-boolean-Rescuable

I believe the issues and duplicates do not warrant low severity severity as even if the call to transfers returns false instead of reverting, there is no impact as it is arguably correct given there will be insufficient funds to perform a rescue/withdrawal. This will not affect `tillIn()` as there are explicit balance [checks that revert accordingly](https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/TokenManager.sol#L255-L260) to prevent allowing creation of offers without posting the necessary collateral

Support

FAQs

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