Call contract function with array uint256[] as parameter from web3

Summary: Unable to call contract function from web3 that has uint as parameter, but can call the same from truffle console directly.

I have a contract function as follows:

function clearOrders(
  uint256[] calldata prices,
  uint256 firstId,
  uint256 lastId
) external returns (string memory status) {

  uint256 i = 0;

  while (firstId <= lastId) {
    
    processOrder(
        firstId,
        prices[i]
    );

    i++;
    firstId++;

  }

  return 'success';

}

function processOrder(
  uint256 id,
  uint256 price
) internal {

// do something  

}

Now calling the function from truffle console works fine:

truffle(ganache)> (await myContract.clearOrders([125444000, 135494000], 1, 2, {from: accounts[0]}))

but when calling the function clearOrders from web3:

let prices = []
let a = 125.444
let b = 135.494

prices.push(web3.utils.toWei(a.toString(), 'mwei'))
prices.push(web3.utils.toWei(b.toString(), 'mwei'))

console.log('prices[]: ' + prices)
// prices[]: 125444000,135494000

let firstId = 1
let lastId = 2

try {
  let result = await myContract.methods
    .clearOrders(
      prices,
      firstId,
      lastId,
    )
    .send({from: accounts[0]})
    .on("transactionHash", hash => {
      console.log('Submitted to block and received transaction hash: ' + hash)
    })
    .on("error", error => {
      console.log('error 1: ' + error)
      return false
    })
    .then(function(receipt) {
      if(receipt.status) {
        console.log('Success: ' + receipt.transactionHash)
        return true
      } else {
        console.log('error 2')
        return false
      }
    })
  console.log('result: ' + result)
} catch(error) {
  console.log('error 3: ' + error)
}

gives output:


prices[]: 125444000,135494000
error 3: Error: Returned error: VM Exception while processing transaction: revert
error 1: Error: Returned error: VM Exception while processing transaction: revert
Error: Returned error: VM Exception while processing transaction: revert
at Object.ErrorResponse (/Users/megatower/projects/my-project/node_modules/web3-core-helpers/lib/errors.js:28:19)
    at /Users/megatower/projects/my-project/node_modules/web3-core-requestmanager/lib/index.js:291:36
    at XMLHttpRequest.request.onreadystatechange (/Users/megatower/projects/my-project/node_modules/web3-providers-http/lib/index.js:98:13)
    at XMLHttpRequestEventTarget.dispatchEvent (/Users/megatower/projects/my-project/node_modules/xhr2-cookies/dist/xml-http-request-event-target.js:34:22)
    at XMLHttpRequest._setReadyState (/Users/megatower/projects/my-project/node_modules/xhr2-cookies/dist/xml-http-request.js:208:14)
    at XMLHttpRequest._onHttpResponseEnd (/Users/megatower/projects/my-project/node_modules/xhr2-cookies/dist/xml-http-request.js:318:14)
    at IncomingMessage.<anonymous> (/Users/megatower/projects/my-project/node_modules/xhr2-cookies/dist/xml-http-request.js:289:61)
    at IncomingMessage.emit (node:events:388:22)
    at endReadableNT (node:internal/streams/readable:1295:12)
    at processTicksAndRejections (node:internal/process/task_queues:80:21) {
  data: {
    '0x9ae3c26045af8fc5dffcd16bc1233ce730f26f6af149a4d737ae6921647fbda8': { error: 'revert', program_counter: 1726, return: '0x' },
    stack: 'c: VM Exception while processing transaction: revert\n' +
      '    at Function.c.fromResults (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:4:194812)\n' +
      '    at w.processBlock (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:42:53376)\n' +
      '    at runMicrotasks (<anonymous>)\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:93:5)',
    name: 'c'
  }
}

Am assuming the error has something to do with BigNumber as I read that parameters passed to contract function as array need to be BN? I’m not sure what is happening here.

2 Likes

Hi @megatower66,

I would try creating your array as an array of BigNumbers.

You could also create a unit test for this.

Hi @abcoathup - I did try creating array of BigNumbers but still doesn’t work.

 npm install bignumber.js

then in .js file:

const BigNumber = require('bignumber.js');

let prices = []
let a = 125.444
let b = 135.494

a1 = web3.utils.toWei(a.toString(), 'mwei')
b1 = web3.utils.toWei(b.toString(), 'mwei')

prices.push(BigNumber(a1))
prices.push(BigNumber(b1))

console.log('prices[]: ' + prices)
// prices[]: 125444000,135494000

When I run the function await myContract.methods.clearOrders(prices, firstId, lastId,) still getting the same error.

What could be wrong?

EDIT: Just to add, the .js file that’s calling the function is not being called from a client (web browser), but as node index.js from terminal.

1 Like

So I was transferring an ERC20 token in processOrder from myContract to another recipient. Once I commented the _token.transfer(to, quantity) line from the function, the web3 call went through.

Am assuming this has to do something with allowance? (I think this shouldn’t be the case as caller in this case → myContract has enough token balance and allowance isn’t checked in transfer function but only in transferFrom function)

But my question is how come in truffle console I didn’t have to set allowance when I directly ran:

truffle(ganache)> (await myContract.clearOrders([125444000, 135494000], 1, 2, {from: accounts[0]}))

and it went through (i.e., tokens were transferred during processOrder execution with no error).

But if allowance isn’t an issue, then am still puzzled as to why error is resolved when .transfer line is omitted. myContract has enough token balance that needs to be transferred to recipient.

FWIW, am calling _token.transfer through an interface:

import './IToken.sol';

function processOrder(
  uint256 id,
  uint256 price
) internal {

  // tokenAddress, to and amount calculated
  IToken(tokenAddress).transfer(to, amount);
  // if above line is commented, web3 works fine. 
  // But if this isn't commented, then truffle console works but web3 fails!

}

and IToken.sol as

// contracts/IToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol";

interface IToken {

    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    function burn(address from, uint256 amount) external;
    function revokeRole(bytes32 role, address account) external;
    function mint(address to, uint256 amount) external;
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
}
1 Like

Hi @megatower66,

If you are doing a transfer in MyContract, then as long as MyContract has a balance of tokens more than the amount that you are transferring then that should have been fine.

You may want to check the balance of tokens that MyContract has when interacting via the console and via web3 and double check that you are interacting with the same contract.

Hi @abcoathup,

MyContract is the same deployment that I interact via truffle console and via web3. In web3, I’ve hardcoded MyContract address received from Truffle console.

Here’s the exact sequence:

> truffle console
truffle(ganache)> MyContract.deployed().then(function(instance){mycontract = instance})
undefined
truffle(ganache)> mycontract.address
'0x5cA043b64EC851dE27737FC3CBA5bB75AAeDb3d6'
truffle(ganache)> (await mycontract.clearOrders([125444000, 135494000], 1, 2, {from: accounts[0]}))
{
  tx: '0xa7386e46b0f74b7aa1cde49235adc837c4dfe27abd927691f5e02c8898892f33',
  receipt: {
    transactionHash: '0xa7386e46b0f74b7aa1cde49235adc837c4dfe27abd927691f5e02c8898892f33',
    transactionIndex: 0,
    blockHash: '0x369f2c1fe11e69f48438160df1975c4abd1105307bb83d8cc0cb1aa905eab25d',
    blockNumber: 41,
    from: '0xc1163e837355e0f22d740ac9833312b5db3bcb9d',
    to: '0x5ca043b64ec851de27737fc3cba5bb75aaedb3d6',
    gasUsed: 115683,
    cumulativeGasUsed: 115683,
    contractAddress: null,
    logs: [],
    status: true,
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000100000000000000008000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400000000002000000000000000000800000000000000000010000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000040002000000000000000000000000000000000000000000000',
    rawLogs: [ [Object] ]
  },
  logs: []
}

Now, in web3 (index.js):

var Web3 = require('web3');

var MyContractJSON = require('../path/to/build/MyContract.json')
const abi = MyContractJSON.abi
const address = '0x5cA043b64EC851dE27737FC3CBA5bB75AAeDb3d6'

async function start() {

  var web3 = await new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:7545'))
  const contract = await new web3.eth.Contract(abi, address)
  const accounts = await web3.eth.getAccounts()

  let orders = await contract.methods.getQueuedOrders().call({from:accounts[0]})
  console.log(orders)
  // prints order array; works fine --> correct contract is connected

  /*
    even with BigNumber, error is thrown

    let prices = []
    let a = 125.444
    let b = 135.494

    a1 = web3.utils.toWei(a.toString(), 'mwei')
    b1 = web3.utils.toWei(b.toString(), 'mwei')

    prices.push(BigNumber(a1))
    prices.push(BigNumber(b1))

    console.log('prices[]: ' + prices)
    // prices[]: 125444000,135494000

  */

  let result = await contract.methods
    .clearOrders(
      [125444000, 135494000], // hard-coded values, still throws error
      1,
      2  
    )
    .send({from: accounts[0]})
    .on("error", error => {
      console.log(error)
    })

  console.log('result: ' + result)
  
}

start()

and then:

> node index.js

throws highly unreadable truffle error. I’m so frustrated right now!

  • Even sender is accounts[0] for both truffle console and web3 while requesting function via send
  • contract address is the same
  • contract abi is the same too (getQueuedOrders().call() returns same array)
  • only difference is the way values are used to call function clearOrders but values are the same too ([125444000, 135494000], 1, 2)

Definitely something is going on when array is send through web3 while truffle console seems to be picking the values correctly.

Everything else is the same (ganache local blockchain, local accounts, MyContract is the same as well, MyContract has tokens as well and transfer works perfectly while making a call from console… uff!)

What am I missing?!?

If you post a minimum version of your contract that shows the problem and steps to reproduce I can try reproducing when I am back in the office.

1 Like

Hi @abcoathup,

The code is pretty large spread across:

  1. on-chain oracle (MyContract in our example)
  2. factory (that creates tokens)
  3. client (frontend contract that interacts with on-chain oracle)
  4. off-chain oracle (the index.js file that interacts with on-chain oracle)

I’ll try to create a minimal version of above.

#4 (off-chain oracle, not a contract) interacts with #1 (on-chain oracle) through the function clearOrders and processes the orders (through processOrder).

Painstakingly, I have been commenting line by line to figure out the piece of code that brings the error in web3. Right now, if I remove the while loop in clearOrders and call processOrder just once, it works in web3!!!

i.e., if processOrder is called once by passing first value in the array prices[0], web3 works fine.

However, if I call processOrder again using prices[0] and prices[1] as below:

Now web3 fails (but truffle console still working!)

There must be something happening inside processOrder that’s failing if calling one after another.

I haven’t much clue (my experience with solidity is pretty low and most of the code is by browsing this awesome forum (so yay! :pray:).

But I think something that I’m doing with struct Order in processOrder (modifying it as well, and transferring tokens), that maybe causing the error. Here’s the minimal version of the function:

Is there something in the above code that would get web3 to throw revert error if called twice or more in succession, but works just fine if called once? Something that’s beyond my XP level.

PS: when processOrder is called multiple times, does it wait for the code to execute before making subsequent calls in while loop? something similar to async-await pattern in js. just guessing.

1 Like

So I tried adding line by line to processOrder function and found that:

above line (and nothing related to Order struct) if called more than once gives the revert error in web3.

variable order.creator and amount are also correct.

if only this function is called once, everything works fine in web3 but if called more than once in contract, either through for loop, while loop, or even by calling twice without any loop, web3 fails.

The contract also has sufficient tokens of tokenAddress.

There is something very trivial going on wrong here. All this while truffle console works beautifully.

  1. What could be the difference in truffle console and web3 when they both are connected to local ganache blockchain but multiple calls to transfer tokens via internal call to function fails in web3:
    ganache-cli -p 7545 -i 5777

@abcoathup can you suggest?

1 Like

Hi @megatower66,

Two possibilities come to mind, you are running out of gas (increase the gas provided in the transaciton) or running out of tokens (which you don’t believe you are) in your loop.

Wherever possible we want to avoid loops.

1 Like

Thanks @abcoathup! Seems like removing loop and processing one by one cleared all the errors! Can’t understand why EVM doesn’t throw legible errors?!? There should have been OUT_OF_GAS error, but 0x revert doesn’t help.

Anyhow, for now it’s settled. On to the next problem of optimizing gas!

1 Like