diff --git a/ethers/contract.nim b/ethers/contract.nim index 7f1e117..3ee74be 100644 --- a/ethers/contract.nim +++ b/ethers/contract.nim @@ -8,6 +8,7 @@ import ./provider import ./signer import ./events import ./fields +import ./exceptions export basics export provider @@ -116,6 +117,38 @@ proc call(contract: Contract, let response = await contract.provider.call(transaction, overrides) return decodeResponse(ReturnType, returnMultiple, response) +proc populateTransaction( + contract: Contract, + tx: Transaction +): Future[Transaction] {.async.} = + + if signer =? contract.signer: + try: + return await signer.populateTransaction(tx) + except EstimateGasError as e: + if nonce =? e.transaction.nonce: + + if lastSeenNonce =? signer.lastSeenNonce and + lastSeenNonce > nonce: + discard await signer.cancelTransaction(e.transaction) + let revertReason = if not e.parent.isNil: e.parent.msg + else: "unknown" + info "A cancellation transaction has been sent to prevent stuck " & + "transactions", + nonce = e.transaction.nonce, + revertReason + + # nonce wasn't incremented by another transaction, so force update the + # lastSeenNonce + else: + signer.updateNonce(nonce - 1, force = true) + trace "nonce decremented -- estimateGas failed but no further " & + "nonces were generated. Prevents stuck txs.", + failedNonce = nonce, + newNonce = nonce - 1 + + raiseContractError e.msgStack + proc send(contract: Contract, function: string, parameters: tuple, @@ -123,7 +156,7 @@ proc send(contract: Contract, Future[?TransactionResponse] {.async.} = if signer =? contract.signer: let transaction = createTransaction(contract, function, parameters, overrides) - let populated = await signer.populateTransaction(transaction, cancelOnEstimateGasError = true) + let populated = await contract.populateTransaction(transaction) let txResp = await signer.sendTransaction(populated) return txResp.some else: diff --git a/ethers/exceptions.nim b/ethers/exceptions.nim new file mode 100644 index 0000000..dfecb2e --- /dev/null +++ b/ethers/exceptions.nim @@ -0,0 +1,7 @@ +import ./basics + +func msgStack*(error: ref EthersError): string = + var msg = error.msg + if not error.parent.isNil: + msg &= " -- Parent exception: " & error.parent.msg + return msg \ No newline at end of file diff --git a/ethers/signer.nim b/ethers/signer.nim index 1c53207..d40a738 100644 --- a/ethers/signer.nim +++ b/ethers/signer.nim @@ -66,7 +66,9 @@ method estimateGas*(signer: Signer, method getChainId*(signer: Signer): Future[UInt256] {.base, gcsafe.} = signer.provider.getChainId() -method getNonce(signer: Signer): Future[UInt256] {.base, gcsafe, async.} = +func lastSeenNonce*(signer: Signer): ?UInt256 = signer.lastSeenNonce + +method getNonce*(signer: Signer): Future[UInt256] {.base, gcsafe, async.} = var nonce = await signer.getTransactionCount(BlockTag.pending) if lastSeen =? signer.lastSeenNonce and lastSeen >= nonce: @@ -84,7 +86,7 @@ method updateNonce*( signer.lastSeenNonce = some nonce return - if nonce > lastSeen: + if force or nonce > lastSeen: signer.lastSeenNonce = some nonce method decreaseNonce*(signer: Signer) {.base, gcsafe.} = @@ -92,8 +94,7 @@ method decreaseNonce*(signer: Signer) {.base, gcsafe.} = signer.lastSeenNonce = some lastSeen - 1 method populateTransaction*(signer: Signer, - transaction: Transaction, - cancelOnEstimateGasError = false): + transaction: Transaction): Future[Transaction] {.base, async.} = if sender =? transaction.sender and sender != await signer.getAddress(): diff --git a/testmodule/testContracts.nim b/testmodule/testContracts.nim index bc5adae..0de9bf7 100644 --- a/testmodule/testContracts.nim +++ b/testmodule/testContracts.nim @@ -16,6 +16,7 @@ type method mint(token: TestToken, holder: Address, amount: UInt256): ?TransactionResponse {.base, contract.} method myBalance(token: TestToken): UInt256 {.base, contract, view.} +method doRevert(token: TestToken, reason: string): ?TransactionResponse {.base, contract.} for url in ["ws://localhost:8545", "http://localhost:8545"]: diff --git a/testnode/contracts/TestToken.sol b/testnode/contracts/TestToken.sol index 99e6e56..d971532 100644 --- a/testnode/contracts/TestToken.sol +++ b/testnode/contracts/TestToken.sol @@ -21,4 +21,9 @@ contract TestToken is ERC20 { function myBalance() public view returns (uint256) { return balanceOf(msg.sender); } + + function doRevert(string memory reason) public { + // Revert every tx with given reason + require(false, reason); + } }