Skip to content

Instantly share code, notes, and snippets.

@GalloDaSballo
Last active May 3, 2023 19:38
Show Gist options
  • Save GalloDaSballo/5456459835e4e2414002d52274174fa5 to your computer and use it in GitHub Desktop.
Save GalloDaSballo/5456459835e4e2414002d52274174fa5 to your computer and use it in GitHub Desktop.

Description

ERC20.transfer will not work for some tokens that don't return a boolean such as USDT

Because Solidity expects a bool to be returned, due to the interface for IERC20, transactions with tokens that do not return a bool such as USDT will revert

Steps to reproduce

  1. Transfer USDT
  2. Try to rescue the USDT
  3. TX will revert

Expected behavior

Transfer should work

Actual behavior

Tx will revert

Screenshots

If applicable, add screenshots to help explain your problem.

Additional information

See POC of a demo

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

import {Test} from "forge-std/Test.sol";

interface IERC20 {
    
    function transfer(address, uint256) external returns (bool);
}

contract USDTLike {
    uint256 balance;
    function transfer(address, uint256 amount) external {
        balance += amount;
        // Return nothing
    }
}
contract UsdtRevertDemo is Test {

    IERC20 usdt;
    function setUp() public {
        usdt = IERC20(address(new USDTLike()));
    }

    function test_usdt() public {
        usdt.transfer(address(1), 123);
    }
}

Which will revert

Running 1 test for test/CdpID.invariants.t.sol:UsdtRevertDemo
[FAIL. Reason: EvmError: Revert] test_usdt() (gas: 27672)
Test result: FAILED. 0 passed; 1 failed; finished in 9.55ms

Failing tests:
Encountered 1 failing test in test/CdpID.invariants.t.sol:UsdtRevertDemo
[FAIL. Reason: EvmError: Revert] test_usdt() (gas: 27672)

You can verify that tokens such as USDT do not return a boolean https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7

## Mitigation

Use the safeTransferLib

library RescueFundsLib { using SafeTransferLib for IERC20;

/**
 * @dev The address used to identify ETH.
 */
address public constant ETH_ADDRESS =
    address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);

/**
 * @dev Rescues funds from a contract.
 * @param token_ The address of the token contract.
 * @param userAddress_ The address of the user.
 * @param amount_ The amount of tokens to be rescued.
 */
function rescueFunds(
    address token_,
    address userAddress_,
    uint256 amount_
) internal {
    require(userAddress_ != address(0));

    if (token_ == ETH_ADDRESS) {
        (bool success, ) = userAddress_.call{value: address(this).balance}(
            ""
        );
        require(success);
    } else {
        IERC20(token_).safeTransfer(userAddress_, amount_); // @audit use safeTransfer here
    }
}

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment