Issue calling Transfer function in the USDT contract

Hi @abcoathup,

This is the javascript code I’m using to call myContract function fetchTokens(...), which fetches specified amount of USDT from specified array of user addresses to MyContract. I’m using SafeERC20 for IERC20, to call safeTransferFrom(...) on USDT token contract instance.

await myContract.methods
  .fetchTokens(
    CONSTANTS.TETHER_ADDRESS,
    [accounts[0], accounts[1], accounts[2], accounts[3], accounts[4]],
    [
      "10000000000000000000",
      "10000000000000000000",
      "10000000000000000000",
      "10000000000000000000",
      "10000000000000000000",
    ]
  )
  .send({
    from: accounts[0],
    gasLimit: 6000000,
  });

And this is the relevant .sol code of MyContract:

contract MyContract is Ownable, Pausable {
using SafeERC20 for IERC20;
...
IERC20 public ERC20Instance;
...
function fetchTokens(
    address _tokenAddress,
    address[] memory _userAddresses,
    uint256[] memory _tokenAmounts
) public payable returns (bool) {
        ERC20Instance = ERC20(_tokenAddress);
        for (uint256 i = 0; i < _userAddresses.length; i++) {
            if (
                ERC20Instance.allowance(_userAddresses[i], address(this)) >=
                _tokenAmounts[i]
            ) {
                ERC20Instance.safeTransferFrom(
                    _userAddresses[i],
                    address(this),
                    _tokenAmounts[i]
                );
            } else {
                emit TokenFetchFailed(
                    _tokenAddress,
                    _userAddresses[i],
                    _tokenAmounts[i]
                );
            }
        }
    emit TokensFetched(_tokenAddress, _userAddresses, _tokenAmounts);
    return true;
 }
}

Now, the problem here is, that the execution of the javascript code gets stuck at the await call above. The code does work, i.e. the relevant USDT are transferred from those users to MyContract, but no code written below this await call gets executed.

My hunch is it could be a problem with the return value from the transferFrom( ) of USDT contract. Because, without using SafeERC20 for IERC20, this transferFrom( ) was throwing an error of ‘Improper return’ (which I came to know on truffle debugging), which SafeERC20 seems to have somehow covered up.

This is the USDT contract I’ve forked on ganache mainnet, that I’m interacting with.

How do I continue the javascript code execution, after the call to the javascript code above?

1 Like

Hi @smitrajput,

I forked mainnet and unlocked a large holder account

$ ganache-cli -f https://mainnet.infura.io/v3/MYPROJECTID -u "0x7b8c69a0f0cd43ef67948976daae77bc6a019b"

I was able to run the following script:

// src/index.js
const Web3 = require('web3');
const { setupLoader } = require('@openzeppelin/contract-loader');

async function main() {
  // Set up web3 object
  const web3 = new Web3('http://localhost:8545')

  const loader = setupLoader({ provider: web3 }).web3;

  const address = '0x8601F62e4BbF1F5316747Ff75A708DfBA7fA4Db0';
  const myContract = loader.fromArtifact('MyContract', address);
  const token = loader.fromArtifact('IERC20', '0xdac17f958d2ee523a2206206994597c13d831ec7')

  await token.methods.approve("0x8601F62e4BbF1F5316747Ff75A708DfBA7fA4Db0", "10000000000000000000").send({from: "0x7b8c69a0f660cd43ef67948976daae77bc6a019b"})
  const amount = await token.methods.allowance("0x7b8c69a0f660cd43ef67948976daae77bc6a019b", "0x8601F62e4BbF1F5316747Ff75A708DfBA7fA4Db0").call()
  console.log(`amount:${amount}`)

  const tx = await myContract.methods.fetchTokens(
    "0xdac17f958d2ee523a2206206994597c13d831ec7",
    ["0x7b8c69a0f660cd43ef67948976daae77bc6a019b"],
    [
      "10000000000000000000"
    ]
  )
  .send({
    from: "0x7b8c69a0f660cd43ef67948976daae77bc6a019b",
    gasLimit: 700000,
  });

  console.log(tx);

  console.log("Done")
}

main();

Though it was reverting and I couldn’t easily spot what the error was.

$ node src/index.js
amount:10000000000000000000
(node:18566) UnhandledPromiseRejectionWarning: Error: Returned error: VM Exception while processing transaction: revert SafeERC20: low-level call failed

Much thanks @abcoathup! I appreciate your effort.

The only problem with the code is that by putting “10000000000000000000” you mean to approve 10 USDT, but it is actually approving 10 to the power 13 USDT, as standard number of decimals for USDT is 6, and not 18 like ERC-20 tokens. Hence, even while transferring (i.e when calling fetchTokens), you are trying to transfer 10,000,000,000,000 USDT from the large holder, which he doesn’t actually own on mainnet.

Hence for the correction, just remove 12 zeros in “10000000000000000000”, while calling token.methods.approve and myContract.methods.fetchTokens. Then please let me know if you are able to see the tx and Done console logs

1 Like

Hi @smitrajput,

Apologies, I hadn’t checked the token amount and just used the values from your script.

I changed the amount to 100 token units and the transaction is successful and the script completes:

$ node src/index.js
Sending approve transaction ...
Allowance amount: 100
Sending fetchTokens transaction ...
{ transactionHash:
   '0x1c050235d8985a71ad5817d556e35e7ea33e58a0af1269779596f147ce938f71',
  transactionIndex: 0,
  blockHash:
   '0x6fd5f65b0a691167942b52651bb0052bc86425912999bf38ce43ee548312c0c7',
  blockNumber: 10648786,
  from: '0x7b8c69a0f660cd43ef67948976daae77bc6a019b',
  to: '0x1bb6c7b802a7e7d9ac9cc5708db4101b6aaa67a0',
  gasUsed: 52133,
  cumulativeGasUsed: 52133,
  contractAddress: null,
  status: true,
  logsBloom:
   '0x00000000040000000000000000000000000000000000000000000000200000000000000000000000008000000000010000000000000000000000000000000000000001000000008000000008002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000200000200000000000000000000000080000004000000000000100000000000000000000008000080000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000',
  events:
   { '0':
      { logIndex: 0,
        transactionIndex: 0,
        transactionHash:
         '0x1c050235d8985a71ad5817d556e35e7ea33e58a0af1269779596f147ce938f71',
        blockHash:
         '0x6fd5f65b0a691167942b52651bb0052bc86425912999bf38ce43ee548312c0c7',
        blockNumber: 10648786,
        address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
        type: 'mined',
        id: 'log_e9780930',
        returnValues: Result {},
        event: undefined,
        signature: null,
        raw: [Object] },
     TokensFetched:
      { logIndex: 1,
        transactionIndex: 0,
        transactionHash:
         '0x1c050235d8985a71ad5817d556e35e7ea33e58a0af1269779596f147ce938f71',
        blockHash:
         '0x6fd5f65b0a691167942b52651bb0052bc86425912999bf38ce43ee548312c0c7',
        blockNumber: 10648786,
        address: '0x1bb6C7B802a7E7d9Ac9Cc5708DB4101b6aAa67A0',
        type: 'mined',
        id: 'log_275d3971',
        returnValues: [Result],
        event: 'TokensFetched',
        signature:
         '0x411ed49b7bac32988b2d1bdc7961333a709556e9f4685cc7bddc631abf653ac4',
        raw: [Object] } } }
Token balance of MyContract: 100
Done

index.js

// src/index.js
const Web3 = require('web3');
const { setupLoader } = require('@openzeppelin/contract-loader');

async function main() {
  // Set up web3 object
  const web3 = new Web3('http://localhost:8545')

  const loader = setupLoader({ provider: web3 }).web3;

  const myContractAddress = '0x1bb6C7B802a7E7d9Ac9Cc5708DB4101b6aAa67A0';
  const tokenAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7';
  const holder = "0x7b8c69a0f660cd43ef67948976daae77bc6a019b";

  const myContract = loader.fromArtifact('MyContract', myContractAddress);
  const token = loader.fromArtifact('IERC20', tokenAddress)

  console.log('Sending approve transaction ...');
  await token.methods.approve(myContractAddress, "100").send({from: holder})
  const amount = await token.methods.allowance(holder, myContractAddress).call()
  console.log(`Allowance amount: ${amount}`)

  console.log('Sending fetchTokens transaction ...');
  const tx = await myContract.methods.fetchTokens(
    tokenAddress,
    [holder],
    [
      "100"
    ]
  )
  .send({
    from: holder,
    gasLimit: 700000,
  });

  console.log(tx);

  const balance = await token.methods.balanceOf(myContractAddress).call()
  console.log(`Token balance of MyContract: ${balance}`)

  console.log("Done")
}

main();

In the line of code above in index.js, are you sure the ABI being loaded of IERC20 represents that of TetherToken?
If yes, then is this ABI, a SafeERC20 wrapper to ERC20 ABI? And from where do I get this ABI, if I'm not using @openzeppelin/contract-loader?

Because the only difference with my script is that I'm loading the ABI for TetherToken, instead of IERC20

1 Like

Hi @smitrajput,

I changed my script to use the Tether ABI and it looks ok.

TokenABI.json

[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_upgradedAddress","type":"address"}],"name":"deprecate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"deprecated","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_evilUser","type":"address"}],"name":"addBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"upgradedAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balances","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maximumFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_maker","type":"address"}],"name":"getBlackListStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowed","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newBasisPoints","type":"uint256"},{"name":"newMaxFee","type":"uint256"}],"name":"setParams","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"redeem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"basisPointsRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBlackListed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_clearedUser","type":"address"}],"name":"removeBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_UINT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_blackListedUser","type":"address"}],"name":"destroyBlackFunds","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_initialSupply","type":"uint256"},{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_decimals","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newAddress","type":"address"}],"name":"Deprecate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint256"},{"indexed":false,"name":"maxFee","type":"uint256"}],"name":"Params","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_blackListedUser","type":"address"},{"indexed":false,"name":"_balance","type":"uint256"}],"name":"DestroyedBlackFunds","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"AddedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"RemovedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"}]

index.js

// src/index.js
const Web3 = require('web3');
const { setupLoader } = require('@openzeppelin/contract-loader');

async function main() {
  // Set up web3 object
  const web3 = new Web3('http://localhost:8545')

  const loader = setupLoader({ provider: web3 }).web3;

  const myContractAddress = '0xAE99a4940490180eb1eb6e5eC8327E569cc24E14';
  const tokenAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7';
  const holder = "0x7b8c69a0f660cd43ef67948976daae77bc6a019b";

  const myContract = loader.fromArtifact('MyContract', myContractAddress);
  //const token = loader.fromArtifact('IERC20', tokenAddress)
  const tokenAbi = require('./TokenABI.json');
  const token = new web3.eth.Contract(tokenAbi, tokenAddress)

  let balance = await token.methods.balanceOf(holder).call()
  console.log(`Token balance of "0x7b8c69a0f0cd43ef67948976daae77bc6a019b": ${balance}`)

  console.log('Sending approve transaction ...');
  await token.methods.approve(myContractAddress, "100").send({from: holder})
  const amount = await token.methods.allowance(holder, myContractAddress).call()
  console.log(`Allowance amount: ${amount}`)

  console.log('Sending fetchTokens transaction ...');
  const tx = await myContract.methods.fetchTokens(
    tokenAddress,
    [holder],
    [
      "100"
    ]
  )
  .send({
    from: holder,
    gasLimit: 700000,
  });

  console.log(tx);

  balance = await token.methods.balanceOf(myContractAddress).call()
  console.log(`Token balance of MyContract: ${balance}`)

  console.log("Done")
}

main();

output

$ node ./src/index.js
Token balance of "0x7b8c69a0f0cd43ef67948976daae77bc6a019b": 275000001140000
Sending approve transaction ...
Allowance amount: 100
Sending fetchTokens transaction ...
{ transactionHash:
   '0xb4a0a282457bf1054b6e6bc34413f7cebafa77d748ca9ff68ec67fd1f7d3506d',
  transactionIndex: 0,
  blockHash:
   '0xbe52c64133754c97821a457b936372f90e4e3b92fc2a1ccf633f7d771d48aefd',
  blockNumber: 10675018,
  from: '0x7b8c69a0f660cd43ef67948976daae77bc6a019b',
  to: '0xae99a4940490180eb1eb6e5ec8327e569cc24e14',
...
Token balance of MyContract: 100
Done

We use SafeERC20 in a smart contract to interact with tokens that may not be ERC20 compliant. There isn’t a SafeERC20 ABI.

For transactions, we don’t get the return value from send functions (only the transaction hash), so should be ok to use a generic ERC20 ABI.