Ethers.js: Contract Interface: Provide the option to explicitly call a function

Created on 19 Jan 2019  ·  15Comments  ·  Source: ethers-io/ethers.js

What

As a user, I did like to have the option to explicitly call a contract function,
even if the function is not marked constant in the ABI object.

Why

I might actually know that the function doesn't modify the state or I wan't to test that.

How

The API design could be as simple as:

myContract.someFunction.ethCall(arguments);

Open for suggestions :)

PS

Thanks for this project! Very thoughtful API design 👍

discussion

Most helpful comment

To use this in the v5 branch, use:

let data = await contract.staticCall.transfer(toAddress, amount);

Let me know if you have any problems with it. :)

All 15 comments

The way I current suggest people do this, is to instantiate a second instance of the contract, but with the ABI altered.

let contract = new Contract( address, [ “function transfer(address, uint)” ], signer);
let contractCallable = new Contract(address, [ “function transfer(address, uint) constant”, provider);

I agree it’s currently a bit cumbersome, and I am experimenting with ways to add this, as it is something people occasionally do.

Out of curiousity, what situations do you use this though?

The way I current suggest people do this, is to instantiate a second instance of the contract, but with the ABI altered.

Same thing as I currently do 😄

let contract = new Contract( address, [ “function transfer(address, uint)” ], signer);
let contractCallable = new Contract(address, [ “function transfer(address, uint) constant”, provider);

I agree it’s currently a bit cumbersome, and I am experimenting with ways to add this, as it is something people occasionally do.

Out of curiousity, what situations do you use this though?

Right now I use it in [1], only used for the test suite though.
But this could also move into a real adaptor/utility library in the future.
Thing is, we have a common contract which in itself all functions can be pure.
The way we extend that contract can force some of them to modify the state,
so I have to drop those state declarations to avoid compile errors.
Best thing would be if I could 'force' the state declaration in solidity, but I don't know a way to do that. 😁

[1] https://github.com/leapdao/solEVM-enforcer/pull/56

This has been added to my v5 branch. I don't know if there is backwards compatible way to do it in v4 that is clean, but rest assured, it will be available in the next version. :)

To use this in the v5 branch, use:

let data = await contract.staticCall.transfer(toAddress, amount);

Let me know if you have any problems with it. :)

I am trying to get the output from a non-payable function. I don't know if I understood correctly how to do it, but what I tried is:

  async function makeContractCallable (contract, fnName, provider, args) {
    const signature = contract.interface.functions[fnName].signature
    const address = contract.address
    let contractCallable = new ethers.Contract(address, [ "function " + signature + " constant" ], provider)
    contractCallable.functions[fnName](...args)
    try {
      var tx = await contractCallable.functions[fnName](...args)
      console.log(tx)
    } catch (e) { console.log(e) }
  }

Sadly, console.log returns an empty array...

What am I missing here?

Thanks in advance for your help.

If I understand correctly, I think you probably need to include the return type the ABI signature:

let contractCallable = new ethers.Contract(address, [
    "function " + signature + " constant returns(address)" // <-- the return type
], provider)

In the above example, I assume it returns an address, but you will need to know the return type so it knows how to interpret the resulting binary data.

Let me know if there is still another issue. :)

Wow, this seems to work. I will triple check it now, but it logs something and doesn't throw the error. Thanks so much!!

@ricmoo Damn, due to the conferencing I last time didn't manage to complete this task so now I am back and I remember it was working once, but for some reason, I am now getting this error

Error: call exception (address="0xE270c59Fd30b151DbD25a28F6A8aE231fD4b1d93", method="set(uint8)", args=[53], version=4.0.37)
    at Object.n [as throwError] (bundle.js:formatted:8680)
    at bundle.js:formatted:8210
    at async makeContractCallable (bundle.js:formatted:52530)
    at async sendTx (bundle.js:formatted:52497)

Code

    async function makeContractCallable (contract, fnName, provider, args, allArgs) {
      const signature = contract.interface.functions[fnName].signature
      const address = contract.address
      const type = contract.interface.functions[fnName].inputs[0].type
      debugger
      let contractCallable = new ethers.Contract(address, [
        `function ${signature} constant returns(${type})`
      ], provider)
      let signer = await provider.getSigner()
      const callableAsCurrentSigner = await contractCallable.connect(signer)
      try {
        if (allArgs.overrides) { return await callableAsCurrentSigner.functions[fnName](...args, allArgs.overrides) }
        else { return await callableAsCurrentSigner.functions[fnName](...args) }
      } catch (e) { console.log(e) }
    }

And the smart contract code of this example looks like this

pragma solidity >=0.4.0 <0.7.0;

contract SimpleStorage {

    uint8 storedData;

    function set(uint8 x) public {
        storedData = x;
    }

    function get() public view returns (uint8) {
        return storedData*2;
    }
}

@ninabreznik Hmmm... Can you print out the contractCallable? It seems like it should error. You are creating an ABI that looks like: function set(uint8) constant returns (uint8), but the set method does not return any data. So, I would expect the call to return 0x, while the coder would expect 32 bytes of data.

That said, it looks like the error is happening before it even gets there. Can you also verify and print the bytecode of the address before calling this?

@ricmoo I don't know what to say. You are my hero. This what looked strange to you was exactly what was wrong. Set doesn't return anything. I was checking JS code character by character but didn't go back and check solidity. So, problem solved, I hope you come to CM, so I can buy you a smoothie or mango sticky rice or something. Thank you and so sorry for being too blind to see where the problem was.

Thank you!!

@ricmoo I have a similar conflict testing a proxy contract in hardhat ethers stack
I am using abi of logic contract and proxy's address to create instance of proxy where methods should be called
However, I am getting this error Error: Transaction reverted: function call to a non-contract account

Any suggestions please for creating contract objects while testing proxies?
I can post lines of code. Any help appreciated Thanks!

@livingrock7 I'm getting the same error, same setup. Deploying proxy contract on hardhat and trying to test it. Did you figure it out?

I'm afraid I do not understand the issues you are having @livingrock7 and @colinsteidtmann, and don't see the issue related to this issue. Can you open a new issue explaining what you are doing and what the issue you are having is?

@ricmoo Yes, I asked about it on Stackexchange (https://ethereum.stackexchange.com/questions/97457/error-transaction-reverted-function-call-to-a-non-contract-account). I'm not sure if it has to do with ethers.js but it could.

Hey @colinsteidtmann, I've tried to provide an answer on your StackExchange question. Let me know if it doesn't help.

Was this page helpful?
0 / 5 - 0 ratings