web3x icon indicating copy to clipboard operation
web3x copied to clipboard

Not having `call` for transaction method is a mistake.

Open hickscorp opened this issue 6 years ago • 3 comments

The fact that a contract.methods.aTransactionMethod(args...).send() is available but not a contract.methods.aTransactionMethod(args...).call() is a big problem for us, as it is very common practice to run a call "blank" before performing a real send transaction.

The reason behind this sort of choice is that revert messages cannot be read when they occur on a send - they can only be read when using call using direct bytecode ABI encoding.

An example of what this led us to do:

const REVERT_FUNCTION_ID = "0x08c379a0";

...

  safeSendTx = async (state: State, opts: any) => {
    if (!isInitialized(state)) {
      throw new Error("The provider isn't ready to relay transactions.");
    }
    // First, simulate the transaction for free.
    await this.simulateTxSend(state, opts);
    // If we're here, it's unlikelly that a revert will occur, as the simulation
    // didn't throw.
    const tx = await state.eth.sendTransaction(opts);
    const hash = await tx.getTxHash();
    try {
      return await tx.getReceipt();
    } catch (ex) {
      // Get some info about the failed transaction hash.
      const fullTx = await state.eth.getTransaction(hash);
      // As the transaction failed, we will try and simulate it again in the block
      // it failed to try and retrieve a "revert" message.
      await this.simulateTxSend(opts, fullTx.blockNumber || undefined);
      throw new Error("The transaction failed without a revert message.");
    }
  };

  simulateTxSend = async (state: State, opts: any, block?: BlockType) => {
    if (!isInitialized(state)) {
      throw new Error("The provider isn't ready to relay transactions.");
    }
    const txString = await state.eth.call(opts, block);
    if (!txString.startsWith(REVERT_FUNCTION_ID)) {
      return txString;
    }
    const enc = txString.substring(REVERT_FUNCTION_ID.length);
    const dec = abiCoder.decodeParameter("string", enc);
    throw new Error(dec);
  };

We have to call it like this:

          api
            .safeSendTx(state, {
              ...utils.chainGasOpts,
              data: transact.methods.approve(account, i).encodeABI(),
              from: state.account,
              to: utils.chainManifest.transact,
            })

This is really cumbersome - it would be great to have access to call even on transaction methods exactly like web3 and truffle do - allowing for shooting "blanks".

Please expose the option to call methods of contracts that are transactions.

hickscorp avatar Jul 20 '19 18:07 hickscorp

I can look into this for next patch release.

In the meantime, the call method should be present on the object, even if not exposed through the TypeScript interface. Underlying it's a Tx object: https://github.com/xf00f/web3x/blob/master/web3x/src/contract/tx.ts#L68

So with appropriate casting you should be able to use it without tonnes of hackery.

xf00f avatar Jul 20 '19 22:07 xf00f

@xf00f Thank you for the feedback.

contract.methods.doStuff(...) gives me a TxSend<T>. How would I cast that into a TxCall<T>? The only way I found so far is:

// Inside a generic defining T from the original (method : TxSend<T>):
const callMethod : TxCall<T> = method as any;

Is this what you meant? I'm not sure - as if the underlying implementation changes, this would still compile and fail only at runtime...

hickscorp avatar Jul 21 '19 17:07 hickscorp

Would it be a good idea to change the references to TxSend in the web3x-codegen project to either of:

  • Tx<${name}TransactionReceipt> (https://github.com/xf00f/web3x/blob/v4.0.4/web3x/src/contract/tx.ts#L68)
  • A new interface (TxSendAndCall<${name}TransactionReceipt>? TxSendWithLocalCallCheck<${name}TransactionReceipt>? something else?) that inherits from TxSend and TxCall interfaces like the Tx class, but includes some indication of the intended use (such as comments suggesting that developers use the TxCall portions to check if the call would work locally, and then use the TxSend portions to actually call the method).

The references to TxSend:

  • https://github.com/xf00f/web3x/blob/v4.0.4/web3x-codegen/src/index.ts#L63 (importing the interface)
  • https://github.com/xf00f/web3x/blob/v4.0.4/web3x-codegen/src/index.ts#L317 (generating for your contract methods)
  • https://github.com/xf00f/web3x/blob/v4.0.4/web3x-codegen/src/index.ts#L339 (more generating for your contract methods)
  • https://github.com/xf00f/web3x/blob/v4.0.4/web3x-codegen/src/index.ts#L425 (generating for the smart contract's constructor)

uhhhh2 avatar Sep 18 '19 15:09 uhhhh2