Can not call the function approve() of the USDT contract

I write a simple smart contract to test the function approve() in the USDT, here are my code:

pragma solidity ^0.4.25;

contract EIP20Interface {

    uint256 public totalSupply;

    function balanceOf(address _owner) public view returns (uint256 balance);

    function transfer(address _to, uint256 _value) public returns (bool success);

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);

    function approve(address _spender, uint256 _value) public returns (bool success);

    function allowance(address _owner, address _spender) public view returns (uint256 remaining);

    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}


contract Test {
    function tokenAllowAll(address asset, address allowee) public {
        EIP20Interface token = EIP20Interface(asset);

        if (token.allowance(address(this), allowee) != uint(-1)) {
            token.approve(allowee, uint(-1));
        }
            
    }
}

After I have deployed it on the mainnet, I just tried to call tokenAllowAll(), but if I input 0xdAC17F958D2ee523a2206206994597C13D831ec7(USDT), 0xAA9E86e4a81625D9016a43AfAe07595D8aD5f4D5(an account), if will fail and throw out an error: Gas estimation failed, but if I input 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48(USDC), 0xAA9E86e4a81625D9016a43AfAe07595D8aD5f4D5(an account) it will pass, so I am not sure what is special for USDT, it has confused me for some days, thanks for any help!

1 Like

Hi @Skyge,

USDT approve doesn’t comply with the ERC20 standard.

I modified Test.sol to use SafeERC20 and this worked in Remix on a testnet (I haven’t tried on mainnet):

Test.sol

pragma solidity ^0.5.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.4.0/contracts/token/ERC20/IERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.4.0/contracts/token/ERC20/SafeERC20.sol";

contract Test {

    using SafeERC20 for IERC20;

    function tokenAllowAll(address asset, address allowee) public {
        IERC20 token = IERC20(asset);

        if (token.allowance(address(this), allowee) != uint256(-1)) {
            token.safeApprove(allowee, uint256(-1));
        }
    }
}

ERC20 Verifier

I used https://erc20-verifier.openzeppelin.com/ to verify USDT and got the following report showing approve was non-standard:

Contract TetherToken
== ERC20 functions definition ==
[x] transfer (address, uint256) -> (bool)
[x] approve (address, uint256) -> (bool)
[x] transferFrom (address, address, uint256) -> (bool)
[✓] allowance (address, address) -> (uint256)
[✓] balanceOf (address) -> (uint256)

== Custom modifiers ==
[✓] No custom modifiers in ERC20 functions

== ERC20 events ==
[✓] Transfer (address, address, uint256)
[✓] Approval (address, address, uint256)

== ERC20 getters ==
[✓] totalSupply () -> (uint256)
[x] decimals () -> (uint8)
[✓] symbol () -> (string)
[✓] name () -> (string)

== Allowance frontrunning mitigation ==
[x] increaseAllowance (address, uint256) -> (bool)
[x] decreaseAllowance (address, uint256) -> (bool)

== Balance check in approve function ==
[✓] approve function should not check for sender's balance

USDT

https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code

    /**
    * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
    * @param _spender The address which will spend the funds.
    * @param _value The amount of tokens to be spent.
    */
    function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {

        // To change the approve amount you first have to reduce the addresses`
        //  allowance to zero by calling `approve(_spender, 0)` if it is not
        //  already 0 to mitigate the race condition described here:
        //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
        require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));

        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
    }

Does that mean I can call approve(spender, value) in the USDT contract directly, but I can not call the same function in a contract, cause it is not a standard ERC20 contract, but what is the reason for it?

Emmm, I know USDT is not compatible with ERC20 standard, so I am not check its returning value, I just call that function.

1 Like

Hi @skyge,

I deployed the Test.sol I created to mainnet and was able to use it to set an allowance on USDT.

_owner: 0x101d4106751277cfe8b2181a194BaAe84916b250
_spender: 0x13ebd3443fa5575F0Eb173e323D8419F7452CfB1

I assume the issue is that the original Test called using the interface with a return value, whilst SafeERC20 handles both with and without a return value.

1 Like

That makes sense, thanks a lot!

1 Like

3 posts were split to a new topic: Transfer of the USDT contract