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!

2 Likes

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);
    }
3 Likes

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.

2 Likes

That makes sense, thanks a lot!

1 Like

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

Thanks for the clarification, it works on the mainnet, @Skyge @abcoathup just wondering is there any usdt asset address on testnet for testing purpose?

Hi, welcome! :wave:

I do not think there is any USDT on the testnet from the acutal Tether, so if you wants to have a test on a testnet, you can use the source code on the mainnet to deploy a new one on the testnet to simulate.

Thanks a lot for your reply.

@Skyge I’ve deployed the USDT contract to Ropsten testnet at 0xd5ffb36998e2e2df1402e33801a1949852055cb0, and I wrote some code to test how to deposit usdt into the smart contact and withdraw the deposited usdt. I’ve safeapprove the contract address at 0xd5ffb36998e2e2df1402e33801a1949852055cb0, but still I couldn’t depost usdt by calling safeTransferFrom, I am quite new to smart contract, just wondering what’s missing here? Thanks very much.


contract Test {

    using SafeERC20 for IERC20;
  
    function tokenAllowAll(address _asset, uint256 _amount ) public {
        IERC20 usdt = IERC20(_asset);
 
        if (usdt.allowance(msg.sender, address(this)) < uint256(_amount)) {
            usdt.safeApprove(address(this), uint256(_amount));
        }
    }
    
     function deposit(address _asset,uint256 _amount) public {
        IERC20 usdt = IERC20(_asset);
        
        require (usdt.allowance(msg.sender,address(this)) >= _amount,"Allowance must larger than deposit amount!");
        usdt.safeTransferFrom(msg.sender, address(this), _amount);

    }
    
    function withdraw(address _asset,address _recipient, uint256 _amount) public {
        IERC20 usdt = IERC20(_asset);
        
        require (usdt.allowance(msg.sender,address(this)) >= _amount,"Owner must allow the tractions");
        
        usdt.safeTransfer( _recipient, _amount);
    }   
}

I think you should call the approve() out of this contract rather than use your function tokenAllowAll , so I think the correct steps should be:

1. usdt.approve(Test.address, amount);
2. deposit(asset, amount);

Have I made myself clear?

Thanks a lot. @Skyge

1 Like

i have same problem , on testnet trc20 token work fine but on mainnet after i sent usdt to contract but withdraw function show error
“REVERT opcode executed. Message: *SafeERC20: ERC20 operation did not succeed”

my contract is

pragma solidity ^0.8.0;

import "../github/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";

contract Invest {
    
    using SafeERC20 for IERC20;
    
    uint256 public target = 1000 * (10**6);
    
    address public owner;
    
    constructor(uint256 _target){
        target = _target;
        owner = msg.sender;
    }
    
    function withdraw(IERC20 token, address to) external isOwner {
        uint256 balance = token.balanceOf(address(this));
        require(balance >= target,"balance is low");
        transferToken(token, to, balance);
    }
    
    function setTarget(uint256 newTarget) external isOwner {
        require(newTarget > target,'new target must be greater current');
        target = newTarget;
    }
    
    modifier isOwner() 
    {
        require(msg.sender == owner,'only owner can do it');
        _;
    }
    
    function transferToken(IERC20 token, address to, uint256 amount) private {
        token.safeTransfer(to, amount);
    }
    
}

Hi, I am not sure what are your steps, I have deployed new contracts and it seems like all work well.

  • deploy an ERC20 contract
  • deploy Invest contract
  • mint some token and transfer to Invest contract
  • call function withdraw.

All my transaction are here: Address 0x483da389e735ceefd7c617d73854c837bbf2f53f | Etherscan

i will accept usdt token and withdraw , not myself token .

i created this contract for save usdt and only when target balance is grater possible withdraw

solved by call approve before transfer
thank u

1 Like

Where could I find the version 0.6.12 of SafeERC20 that still uses safemath Lib?

I spent so much time trying to figure out why my called failed until i broke it down to USDT approval!

1 Like

I'm new to smart contract tech totally new I want someone who can teach me how to develop smart contracts and deploy them expectially ones related to this approve(address _spender, uint256 _value) on ERC20 or TRC20 network

This function could be used.

function safeApprove(address token, address to, uint value) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'APPROVE_FAILED');
}
1 Like