javaethereumgo-ethereumerc20web3-java

Why am I getting "Error processing transaction request: intrinsic gas too low" error when trying to add tUSDT to a particular account?


I am trying to send test USDT to a particular account in Java using the following code:

final Web3j web3 = createWeb3If(ethNetworkUrl);
final Credentials credentials = Credentials.create(privateKey);
final ERC20 usdtContract = ERC20.load(usdtContractAddress, web3, credentials, new TestGasProvider());

usdtContract.transfer(exchangeAddress, BigInteger.valueOf(10)).send();

The last statement results in the following exception:

java.lang.RuntimeException: Error processing transaction request: intrinsic gas too low
    at org.web3j.tx.TransactionManager.processResponse(TransactionManager.java:176)
    at org.web3j.tx.TransactionManager.executeTransaction(TransactionManager.java:81)
    at org.web3j.tx.ManagedTransaction.send(ManagedTransaction.java:128)
    at org.web3j.tx.Contract.executeTransaction(Contract.java:367)
    at org.web3j.tx.Contract.executeTransaction(Contract.java:350)
    at org.web3j.tx.Contract.executeTransaction(Contract.java:344)
    at org.web3j.tx.Contract.executeTransaction(Contract.java:339)
    at org.web3j.tx.Contract.lambda$executeRemoteCallTransaction$3(Contract.java:410)
    at org.web3j.protocol.core.RemoteCall.send(RemoteCall.java:42)
    at com.dpisarenko.minimalcryptoexchange.delegates.TransferUsdtToExchangeAccount.execute(TransferUsdtToExchangeAccount.java:57)

TestGasProvider is defined as:

public class TestGasProvider extends StaticGasProvider {
    public static final BigInteger GAS_PRICE = BigInteger.valueOf(10L);
    public static final BigInteger GAS_LIMIT = BigInteger.valueOf(1L);

    public TestGasProvider() {
        super(GAS_PRICE, GAS_LIMIT);
    }
}

usdtContract was deployed using this script, which calls deploy.js:

async function main() {
  const USDT = await ethers.getContractFactory("USDT");
  const usdt = await USDT.deploy(1000000000000000);

  console.log("USDT contract deployed to:", usdt.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

This contract is running on a local testnet set up as described here.

What do I need to change in any of these components (testnet, contract, deploy scripts, Java code) in order to send any amount of USDT to a particular address (without any errors)?

Update 1: If I change TestGasProvider to

public class TestGasProvider extends StaticGasProvider {
    public static final BigInteger GAS_PRICE = BigInteger.valueOf(1L);
    public static final BigInteger GAS_LIMIT = BigInteger.valueOf(1000000000L);

    public TestGasProvider() {
        super(GAS_PRICE, GAS_LIMIT);
    }
}

I get another error:

java.lang.RuntimeException: Error processing transaction request: exceeds block gas limit
    at org.web3j.tx.TransactionManager.processResponse(TransactionManager.java:176)
    at org.web3j.tx.TransactionManager.executeTransaction(TransactionManager.java:81)
    at org.web3j.tx.ManagedTransaction.send(ManagedTransaction.java:128)
    at org.web3j.tx.Contract.executeTransaction(Contract.java:367)
    at org.web3j.tx.Contract.executeTransaction(Contract.java:350)
    at org.web3j.tx.Contract.executeTransaction(Contract.java:344)
    at org.web3j.tx.Contract.executeTransaction(Contract.java:339)
    at org.web3j.tx.Contract.lambda$executeRemoteCallTransaction$3(Contract.java:410)
    at org.web3j.protocol.core.RemoteCall.send(RemoteCall.java:42)
    at com.dpisarenko.minimalcryptoexchange.delegates.TransferUsdtToExchangeAccount.execute(TransferUsdtToExchangeAccount.java:57)

Update 1

I am looking to submit a set of code changes to the branch i16 of the minimal-crypto-exchange project which passes the following test:

Step 1

Set up the environment as described here.

Step 2

Set a breakpoint on line usdtContract.transfer(exchangeAddress, BigInteger.valueOf(10)).send(); in TransferUsdtToExchangeAccount class:

Screenshot of the code part with breakpoint

Step 3

Start the process engine application in debug mode. Its Java main method is located here.

Wait until you see the message starting to acquire jobs in the console output:

11:59:16.031 [JobExecutor[org.camunda.bpm.engine.spring.components.jobexecutor.SpringJobExecutor]] INFO  org.camunda.bpm.engine.jobexecutor - ENGINE-14018 JobExecutor[org.camunda.bpm.engine.spring.components.jobexecutor.SpringJobExecutor] starting to acquire jobs

Step 4

Login with the credentials demo/demo at http://localhost:8080.

Screenshot of the login form

After login you should see a page like this:

Main Camunda dashboard with tasklist link highlighted

Step 5

Click on the tasklist link. You should see a page that looks like this:

"Start process" link the tasklist

Press the "Start process" link. Following screen will appear:

"Start process" dialog box with "Send USDT to the exchange account" process highlighted

Click on Send USDT to the exchange account process link. Following dialog box will appear:

"Start process" dialog

Enter an arbitrary value into the "business key" field and press the "Start" button.

Step 6

After a couple of seconds, the breakpoint from step 2 will activate.

The problem will be solved if usdtContract.transfer(exchangeAddress, BigInteger.valueOf(10)).send() is executed without errors.

Notes

  1. You are allowed to modify the amount in usdtContract.transfer(exchangeAddress, BigInteger.valueOf(10)).send(); from 10 to something else.
  2. You can also modify the parameters of the Ethereum testnet specified in docker-compose.yml and genesis.json, as well as those of the USDT smart contract which is deployed using this script.
  3. Your solution must work in this controlled environment (i. e. no faucets must be used).

Update 2

I made following changes:

  1. The set-up tutorial now contains step 7 in which ETH is added to the exchange account.
  2. Now a new version of the ETH testnet is being used, major changes being that log output is more verbose and the gas price is set to 1 (see --miner.gasprice 1 in entrypoint.sh).
  3. Modified the code in TransferUsdtToExchangeAccount so that now USDT is transferred not from the exchange account (which has zero balance), but from the buffer account.

Now I am receiving the error

org.web3j.protocol.exceptions.TransactionException: Transaction 0x4bce379a2673c4564b2eb6080607b00d1a8ac232fbddf903f353f4eeda566cae
has failed with status: 0x0. Gas used: 32767. 
Revert reason: 'ERC20: transfer amount exceeds allowance'.

Solution

  • My skills with Ethereum are still not sharp enough to give you a proper answer, but I hope you get some guidance.

    The error states that you are trying to transfer by a party A certain quantity in the name of another party B, to a third one C, but the amount you are trying to transfer, using transferFrom, is greater than the one party B approved party A to send.

    You can check the actual allowance between to parties using the method with the same name of your contract.

    Please, consider review this integration test from the web3j library in Github. It is different than yours but I think it could be helpful.

    Especially, it states that the actual transferFrom operation should be performed by the beneficiary of the allowance. Please, see the relevant code:

    final String aliceAddress = ALICE.getAddress();
    final String bobAddress = BOB.getAddress();
    ContractGasProvider contractGasProvider = new DefaultGasProvider();
    HumanStandardToken contract =
            HumanStandardToken.deploy(
                            web3j,
                            ALICE,
                            contractGasProvider,
                            aliceQty,
                            "web3j tokens",
                            BigInteger.valueOf(18),
                            "w3j$")
                    .send();
    
    //...
    
    // set an allowance
    assertEquals(contract.allowance(aliceAddress, bobAddress).send(), (BigInteger.ZERO));
    
    transferQuantity = BigInteger.valueOf(50);
    TransactionReceipt approveReceipt =
            contract.approve(BOB.getAddress(), transferQuantity).send();
    
    HumanStandardToken.ApprovalEventResponse approvalEventValues =
            contract.getApprovalEvents(approveReceipt).get(0);
    
    assertEquals(approvalEventValues._owner, (aliceAddress));
    assertEquals(approvalEventValues._spender, (bobAddress));
    assertEquals(approvalEventValues._value, (transferQuantity));
    
    assertEquals(contract.allowance(aliceAddress, bobAddress).send(), (transferQuantity));
    
    // perform a transfer as Bob
    transferQuantity = BigInteger.valueOf(25);
    
    // Bob requires his own contract instance
    HumanStandardToken bobsContract =
            HumanStandardToken.load(
                    contract.getContractAddress(), web3j, BOB, STATIC_GAS_PROVIDER);
    
    TransactionReceipt bobTransferReceipt =
            bobsContract.transferFrom(aliceAddress, bobAddress, transferQuantity).send();
    
    HumanStandardToken.TransferEventResponse bobTransferEventValues =
            contract.getTransferEvents(bobTransferReceipt).get(0);
    assertEquals(bobTransferEventValues._from, (aliceAddress));
    assertEquals(bobTransferEventValues._to, (bobAddress));
    assertEquals(bobTransferEventValues._value, (transferQuantity));
    
    //...
    

    This fact is also indicated in this OpenZeppelin forum post.