status-im/nim-web3

Contract call API is not flexible enough

Closed this issue · 5 comments

Currently the contract call can be done as follows:

let myContract = web3.contractSender(MyContract)
await myContract.myMethod(args)

This way we'll create the call data and send it with eth_sendTransaction or eth_call, depending on the {.view.} method pragma. Also note that we can set call options (such as gasLimit, gasPrice, etc) through modifying myContract object. A more flexible way would be:

let myContract = web3.contractSender(MyContract)
await myContract.myMethod(args).call(callOptions) # eth_call
await myContract.myMethod(args).send(sendOptions) # eth_sendTransaction, returns TxHash
await myContract.myMethod(args).sendAndMine(sendOptions) # eth_sendTransaction, waits for tx to be mined, returns result.
await myContract.myMethod(args).sign(pk).send(sendOptions) # sign, eth_sendRawTransaction, return TxHash
await myContract.myMethod(args).sign(pk).sendAndMine(sendOptions) # sign, eth_sendRawTransaction, waits for tx to be mined, returns result.

I wouldn't use "mine", rather, execute, given that we'll be using staking before long. But in fact, why have this dichotomy at all? When a TX is sent, it'll get mined when it's mined, so I would instead pick an async "send()" and a synchronous "syncSend" or something similar, like the various other tools currently do.

When a TX is sent, it'll get mined when it's mined, so I would instead pick an async "send()" and a synchronous "syncSend" or something similar, like the various other tools currently do.

Both calls are actually async in nim. The first will send a tx and return its hash, the second will return the return value of the method. In order to return the return value, we need to wait for transaction to be mined. Now "mined" might not be the best term, I'm ok with sendAndExecute or maybe just exec.

I see. Just thinking out loud, but would it be against standard to unite those calls under send() and have it return a result promise alongside a tx hash?

E.g. on send you instantly know the TX hash, so return:

{
    transactionHash: "0x7dec07531aae8178e9d0b0abbd317ac3bb6e8e0fd37c2733b4e0d382ba34c5d2",
    gasLimit: 230000,
    gasCost: 1e12,
    ... etc,
    result: <Promise>
}

and once it resolves

{
    transactionHash: "0x7dec07531aae8178e9d0b0abbd317ac3bb6e8e0fd37c2733b4e0d382ba34c5d2",
    gasLimit: 230000,
    gasCost: 1e12,
    ... etc,
    result: {
    // The block this transaction was mined into
    blockHash: "0xca1d4d9c4ac0b903a64cf3ae3be55cc31f25f81bf29933dd23c13e51c3711840",
    blockNumber: 3346629,

    // The index into this block of the transaction
    transactionIndex: 1,

    // The address of the contract (if one was created)
    contractAddress: null,

    // Gas
    cumulativeGasUsed: utils.bigNumberify("42000"),
    gasUsed: utils.bigNumberify("21000"),

    // Logs (an Array of Logs)
    log: [ ],
    logsBloom: "0x00" ... [ 256 bytes of 0 ] ... "00",
    }
}

Well it's not that straightforward either.

  • TxHash might not be known if the tx is sent using eth_sendTransaction, that is unsigned. So it should be a future anyway.
  • Returning an object or Future[object] with another future inside is not a widely used practice in nim.
  • Returning an object with a future inside implies that the future is being worked on, but it might not be needed by the user.
  • I suppose in most cases the user would need either txhash, or the result, but almost never both.

Hmm, agreed. Ok.