harmony-one/sdk

Transactions to smart contracts fail silently

Daniel-VDM opened this issue · 6 comments

While developing a dapp, I sent a transaction to a smart contract with insufficient gas. However, I got no indication that the transaction failed.

Now that the hmy_sendRawTransaction RPC returns an error if the transaction is unable to be submitted to the mem-pool, such errors should be 'reportable'.

Quickly inspecting the SDK, I found the following snippet:

protected async sendTransaction(signed: Transaction) {
try {
const result = await signed.sendTransaction();
this.contract.setStatus(ContractStatus.SENT);
return result;
} catch (error) {
throw error;
}
}

Note that we never check if the transaction is rejected, maybe there is a good place to start.

@Daniel-VDM if you use send or call instead of sendTransaction it will get the receipt. sendTransaction api is made like this I guess. Also, you can see that, contract status is set to SENT and you are expected to call confirm following the call to sendTransaction. You can avoid these by directly using send.

@gupadhyaya Yes I followed such structure, here is a snippet:

contract.methods.startProject(
    testTitle,
    testDescription,
    testDuration,
    testAmount
).send({
    from: fromAccount,
}).then((res) => {
    const projectInfo = res.events.ProjectStarted.returnValues;
    console.log(projectInfo)
})

Correct me if I'm wrong, but I noticed that the returned value of hmy_sendRawTransaction is expected to be the transaction id (or hash).
Here is the snippet:

const updateNonce: boolean = params && params.nonce !== undefined ? false : true;
this.signTransaction(updateNonce).then((signed) => {
this.sendTransaction(signed).then((sent) => {
const [txn, id] = sent;
this.transaction = txn;
this.contract.transaction = this.transaction;
this.confirm(id).then(() => {
this.transaction.emitter.resolve(this.contract);
});
});
});
};

The id is now transaction failed:transaction underpriced as that is what the RPC returns. So the confirm just hangs forever -- hence the silent failure.

for send(options) try manual gas like,

const options = {
  gasPrice: process.env.GAS_PRICE,
  gasLimit: process.env.GAS_LIMIT,
};

for send(options) try manual gas like,

const options = {
  gasPrice: process.env.GAS_PRICE,
  gasLimit: process.env.GAS_LIMIT,
};

Setting the correct gas resolved the issue, but we should check and error if the transaction errored in my opinion.

@gupadhyaya Yes I followed such structure, here is a snippet:

contract.methods.startProject(
    testTitle,
    testDescription,
    testDuration,
    testAmount
).send({
    from: fromAccount,
}).then((res) => {
    const projectInfo = res.events.ProjectStarted.returnValues;
    console.log(projectInfo)
})

Correct me if I'm wrong, but I noticed that the returned value of hmy_sendRawTransaction is expected to be the transaction id (or hash).
Here is the snippet:

const updateNonce: boolean = params && params.nonce !== undefined ? false : true;
this.signTransaction(updateNonce).then((signed) => {
this.sendTransaction(signed).then((sent) => {
const [txn, id] = sent;
this.transaction = txn;
this.contract.transaction = this.transaction;
this.confirm(id).then(() => {
this.transaction.emitter.resolve(this.contract);
});
});
});
};

The id is now transaction failed:transaction underpriced as that is what the RPC returns. So the confirm just hangs forever -- hence the silent failure.

Thanks for pointing out, we should add error throwing before txn finished.

#45 had fixed it.

if (this.transaction.isRejected()) {
this.transaction.emitter.reject(id); // in this case, id is error message