When to use call() for Solidity functions (Truffle)

Stop getting confused about when to use .call() and when not. Sometimes this suffix is required when calling functions, sometimes it is optional.

When using Truffle, you might notice that there is a difference between calling your contract’s functions by adding and omitting the suffix .call()

This post will go through some of the various situations you might be in. It will explain when and why it is required to use .call().  

If you are not familiar with the Truffle environment, go have a look at this post: Working with Truffle and Ganache.

NOTE!

call() is used only for functions that do not change the internal state of the smart contract i.e. do not modify the global variables. This discussion is referring only to these types of functions.

Although, there is a special case when you might want to use .call() for non-view functions. We will talk about it at the end.

View/constant/pure functions (Truffle knows)

If your functions have the state mutability set to view/constant/pure then Truffle knows you are trying to read something and not modify the state. 

This is why the behaviour of your tests will be the same with or without .call(). For example, if the contract has a function defined as follows:

function getName() public view returns(string memory){
    return name;
}

These two options won’t differ in any way:

const currentName = await contractInstance.getName.call();
const currentName = await contractInstance.getName();

State mutability not mentioned

When the state mutability is not mentioned but your function is supposed to be view/constant/pure, you might face some trouble if you don’t use .call().

Everything would work regardless of the existence of state mutability if:

  • the function does no modify the state of the contract AND
  • it returns a “simple” (e.g. integer)

For example, the function below does not mention state mutability. Calling it within the Truffle tests can be simply done as in the previous section.

function getNumber() public returns(uint){
    return number;
 }

Although, if you return more complex types such as strings or multiple values, you might get the following errors:

AssertionError: test failed message: expected { Object (tx, receipt, ...) } to equal 'testValue'

OR

Error: Returned values aren't valid, did it run Out of Gas? You might also see this error if you are not using the correct ABI for the contract you are retrieving data from, requesting data from a block number that does not exist, or querying a node which is not fully synced.

You have two options:

  1. add .call() when calling your function
  2. set the mutability to view/constant/pure

For example, the two options below both work:

//Solidity
function getName() public view returns(string memory){
    return name;
}
//JS TEST
it('initial name should be Ioana', async () => {
    // call contract's function
    const currentName = await contractInstance.getName();
    // check condition
    assert.equal(currentName, "Ioana", "the name is not Ioana");
});


//Solidity
function getNameNoView() public returns(string memory){
    return name;
}
//JS TEST
it('initial name should be Ioana', async () => {
  // call contract's function
  const currentName = await contractInstance.getNameNoView.call();
  // check condition
  assert.equal(currentName, "Ioana", "the name is not Ioana");
});

Contracts deployed using ABI and bytecode

If you deploy a Smart Contract using JS through the ABI and bytecode and not by using Truffle’s Migrations, your function calls should always use .call() (or .send() for “non-view” methods). It should look something like this:

ContractName.methods.functionName().call();

Note! We add .methods to announce that we want to call a function. This is because we are using the ABI to reference the contract’s components. Truffle is not helping us to interact with it because we deployed it using the ‘traditional’ method and not through migrations.

Simulate behaviour of non-view functions

This is a special case of the functionality of the .call() method. If you have a non-view function and only want to check if the result is what you expected but you don’t want to modify the state of the contract, this is for you.

Using .call() to call a function that changes the state variables of a contract will do the following:

  • run the function as usual
  • return the result
  • BUT it will not save the modifications on chain
  • AND it won’t cost any gas

This is a cool trick to use in order to save gas.

BUT BE CAREFUL! Truffle will not give you any warning if you mistakenly use .call() for a non-view function. You might notice later in your tests that the global variables of your contract are not modified as expected.

I hope this was helpful. Any suggestions are more than welcomed in the comments! 😁

2 Comments Add yours

Leave a Reply