From 119dfa5b8b7f6b5b6be1e73afa21f30a386ad398 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 4 Dec 2023 08:55:34 -0500 Subject: [PATCH] Errors not caught in 4.x (#6623) * catch TransactionPollingTimeoutError * add contract tests * eslint fix * text fixes * fix * increase coverage * fix * changelog --- CHANGELOG.md | 33 +++++++++ .../src/tx/baseTransaction.ts | 10 ++- .../test/integration/contract_erc20.test.ts | 60 +++++++++++++++ packages/web3-eth/CHANGELOG.md | 6 +- packages/web3-eth/src/types.ts | 2 + packages/web3-eth/src/utils/send_tx_helper.ts | 4 +- .../web3_eth/send_signed_transaction.test.ts | 69 +++++++++++++++++ .../prepare_transaction_for_signing.test.ts | 74 ++++++++++++++++++- 8 files changed, 248 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7ca239e669..a9d7fc08cba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2254,3 +2254,36 @@ If there are any bugs, improvements, optimizations or any new feature proposal f - Will populate `data` for transactions in contract for metamask provider instead of `input` (#6534) ## [Unreleased] + +### Added + +#### web3 + + +#### web3-eth + +- Catch `TransactionPollingTimeoutError` was added to send transaction events (#6623) + +#### web3-utils + +- `SocketProvider` now contains public function `getPendingRequestQueueSize`, `getSentRequestsQueueSize` and `clearQueues` (#6479) +- Added `safeDisconnect` as a `SocketProvider` method to disconnect only when request queue size and send request queue size is 0 (#6479) +- Add `isContractInitOptions` method (#6555) + +### Changed + +#### web3-core + + +#### web3-eth-contract + + +### Fixed + +#### web3-rpc-methods + +- Fix web3-types import #6590 (#6589) + +#### web3-utils + +- Fix unecessary array copy when pack encoding (#6553) diff --git a/packages/web3-eth-accounts/src/tx/baseTransaction.ts b/packages/web3-eth-accounts/src/tx/baseTransaction.ts index f0cace3af4c..cb47d2bad22 100644 --- a/packages/web3-eth-accounts/src/tx/baseTransaction.ts +++ b/packages/web3-eth-accounts/src/tx/baseTransaction.ts @@ -421,10 +421,12 @@ export abstract class BaseTransaction { } // No chain ID provided // -> return Common provided or create new default Common - return ( - common?.copy() ?? - new Common({ chain: this.DEFAULT_CHAIN, hardfork: this.DEFAULT_HARDFORK }) - ); + + if (common?.copy && typeof common?.copy === 'function') { + return common.copy(); + } + + return new Common({ chain: this.DEFAULT_CHAIN, hardfork: this.DEFAULT_HARDFORK }); } /** diff --git a/packages/web3-eth-contract/test/integration/contract_erc20.test.ts b/packages/web3-eth-contract/test/integration/contract_erc20.test.ts index 15d1852a055..5e268691aa5 100644 --- a/packages/web3-eth-contract/test/integration/contract_erc20.test.ts +++ b/packages/web3-eth-contract/test/integration/contract_erc20.test.ts @@ -120,6 +120,66 @@ describe('contract', () => { value, ); }); + + it('send tokens from the account that does not have ether', async () => { + const tempAccount = await createTempAccount(); + const test = await createNewAccount({ + unlock: true, + refill: false, + }); + + let catchError = false; + let catchErrorPromise; + try { + const promiEvent = contractDeployed.methods + .transfer(tempAccount.address, '0x1') + .send({ ...sendOptions, from: test.address }); + + catchErrorPromise = new Promise(resolve => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + promiEvent.on('error', err => { + // Returned error: insufficient funds for gas * price + value: balance 0, tx cost 25000327300000000, overshot 25000327300000000 + resolve(err); + }); + }); + await promiEvent; + } catch (e) { + // Returned error: insufficient funds for gas * price + value: balance 0, tx cost 25000327300000000, overshot 25000327300000000 + catchError = true; + } + expect(await catchErrorPromise).toBeDefined(); + expect(catchError).toBe(true); + }); + it('send tokens from the account that does not have tokens', async () => { + const tempAccount = await createTempAccount(); + const test = await createNewAccount({ + unlock: true, + refill: true, + }); + + let catchError = false; + let catchErrorPromise; + try { + const promiEvent = contractDeployed.methods + .transfer(tempAccount.address, '0x1') + .send({ ...sendOptions, from: test.address }); + + catchErrorPromise = new Promise(resolve => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + promiEvent.on('error', err => { + // Transaction has been reverted by the EVM + resolve(err); + }); + }); + await promiEvent; + } catch (e) { + // Transaction has been reverted by the EVM + catchError = true; + } + expect(await catchErrorPromise).toBeDefined(); + expect(catchError).toBe(true); + }); + it.each([signAndSendContractMethodEIP1559, signAndSendContractMethodEIP2930])( 'should transfer tokens with local wallet %p', async signAndSendContractMethod => { diff --git a/packages/web3-eth/CHANGELOG.md b/packages/web3-eth/CHANGELOG.md index aac5c9d1097..8724ecb756a 100644 --- a/packages/web3-eth/CHANGELOG.md +++ b/packages/web3-eth/CHANGELOG.md @@ -213,4 +213,8 @@ Documentation: - Dependencies updated -## [Unreleased] \ No newline at end of file +## [Unreleased] + +### Added + +- Catch `TransactionPollingTimeoutError` was added to send transaction events (#6623) diff --git a/packages/web3-eth/src/types.ts b/packages/web3-eth/src/types.ts index ae97084ea72..e1bba319d69 100644 --- a/packages/web3-eth/src/types.ts +++ b/packages/web3-eth/src/types.ts @@ -21,6 +21,7 @@ import { TransactionRevertInstructionError, TransactionRevertWithCustomError, InvalidResponseError, + TransactionPollingTimeoutError, } from 'web3-errors'; import { FormatType, @@ -50,6 +51,7 @@ export type SendTransactionEventsBase = | TransactionRevertedWithoutReasonError> | TransactionRevertInstructionError> | TransactionRevertWithCustomError> + | TransactionPollingTimeoutError | InvalidResponseError | ContractExecutionError; }; diff --git a/packages/web3-eth/src/utils/send_tx_helper.ts b/packages/web3-eth/src/utils/send_tx_helper.ts index 6cabb1a44cc..2e3385d5d1c 100644 --- a/packages/web3-eth/src/utils/send_tx_helper.ts +++ b/packages/web3-eth/src/utils/send_tx_helper.ts @@ -37,6 +37,7 @@ import { isNullish } from 'web3-validator'; import { ContractExecutionError, InvalidResponseError, + TransactionPollingTimeoutError, TransactionRevertedWithoutReasonError, TransactionRevertInstructionError, TransactionRevertWithCustomError, @@ -243,7 +244,8 @@ export class SendTxHelper< _error instanceof ContractExecutionError || _error instanceof TransactionRevertWithCustomError || _error instanceof TransactionRevertedWithoutReasonError || - _error instanceof TransactionRevertInstructionError) && + _error instanceof TransactionRevertInstructionError || + _error instanceof TransactionPollingTimeoutError) && this.promiEvent.listenerCount('error') > 0 ) { this.promiEvent.emit('error', _error); diff --git a/packages/web3-eth/test/integration/web3_eth/send_signed_transaction.test.ts b/packages/web3-eth/test/integration/web3_eth/send_signed_transaction.test.ts index 550e59f9bc8..1fe6c4f4c63 100644 --- a/packages/web3-eth/test/integration/web3_eth/send_signed_transaction.test.ts +++ b/packages/web3-eth/test/integration/web3_eth/send_signed_transaction.test.ts @@ -33,6 +33,7 @@ import { isHexStrict } from 'web3-validator'; import { Web3Eth, InternalTransaction, transactionSchema } from '../../../src'; import { closeOpenConnection, + createNewAccount, createTempAccount, getSystemTestBackend, getSystemTestProvider, @@ -55,6 +56,74 @@ describe('Web3Eth.sendSignedTransaction', () => { await closeOpenConnection(web3Eth); }); + describe('Should catch errors', () => { + it('send ether from the account that does not have ether', async () => { + let onErrorReceived = false; + let catchErrorReceived = false; + const from = await createNewAccount({ + unlock: true, + refill: false, + }); + let pr; + try { + const promiEvent = web3Eth.sendTransaction({ + to: tempAcc.address, + from: from.address, + }); + pr = new Promise(resolve => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + promiEvent.on('error', () => { + onErrorReceived = true; + resolve(true); + }); + }); + await promiEvent; + } catch (e) { + catchErrorReceived = true; + } + await pr; + expect(onErrorReceived).toBe(true); + expect(catchErrorReceived).toBe(true); + }); + it('send and wait timeout', async () => { + let onErrorReceived = false; + let catchErrorReceived = false; + const from = await createNewAccount({ + unlock: true, + refill: true, + }); + + web3Eth.setConfig({ + transactionReceiptPollingInterval: 10, + transactionPollingTimeout: 1000, + }); + + const currentNonce = await web3Eth.getTransactionCount(from.address); + let pr; + try { + const promiEvent = web3Eth.sendTransaction({ + to: tempAcc.address, + from: from.address, + value: '0x1', + nonce: currentNonce + BigInt(1000), + }); + pr = new Promise(resolve => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + promiEvent.on('error', () => { + onErrorReceived = true; + resolve(true); + }); + }); + await promiEvent; + } catch (e) { + catchErrorReceived = true; + } + await pr; + expect(onErrorReceived).toBe(true); + expect(catchErrorReceived).toBe(true); + }); + }); + describe('Transaction Types', () => { it('should send a signed simple value transfer - type 0x0', async () => { const temp = await createTempAccount(); diff --git a/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts b/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts index cf301264407..d8d30ee0f65 100644 --- a/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts +++ b/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts @@ -15,18 +15,25 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -import { EthExecutionAPI } from 'web3-types'; +import { + Common, + EthExecutionAPI, + HexString, + Web3NetAPI, + Transaction as TransactionType, +} from 'web3-types'; import { Web3Context } from 'web3-core'; import HttpProvider from 'web3-providers-http'; import { isNullish } from 'web3-validator'; + +import { ethRpcMethods } from 'web3-rpc-methods'; + +import { bytesToHex, hexToBytes } from 'web3-utils'; import { AccessListEIP2930Transaction, FeeMarketEIP1559Transaction, Transaction, } from 'web3-eth-accounts'; -import { ethRpcMethods } from 'web3-rpc-methods'; - -import { bytesToHex, hexToBytes } from 'web3-utils'; import { prepareTransactionForSigning } from '../../src/utils/prepare_transaction_for_signing'; import { validTransactions } from '../fixtures/prepare_transaction_for_signing'; @@ -36,6 +43,65 @@ describe('prepareTransactionForSigning', () => { config: { defaultNetworkId: '0x1' }, }); + describe('default', () => { + it('use default common', async () => { + const context = new Web3Context({ + provider: new HttpProvider('http://127.0.0.1'), + config: { defaultNetworkId: '0x1' }, + }); + context.defaultChain = 'mainnet'; + context.defaultCommon = { + customChain: { + name: 'test', + networkId: 457, + chainId: 1458, + }, + baseChain: 'mainnet', + }; + + async function transactionBuilder(options: { + transaction: TransactionType; + web3Context: Web3Context; + privateKey?: HexString | Uint8Array; + fillGasPrice?: boolean; + fillGasLimit?: boolean; + }): Promise { + const tx = { ...options.transaction }; + + if (isNullish(tx.common)) { + if (options.web3Context.defaultCommon) { + const common = options.web3Context.defaultCommon as unknown as Common; + const chainId = common.customChain.chainId as string; + const networkId = common.customChain.networkId as string; + const name = common.customChain.name as string; + tx.common = { + ...common, + customChain: { chainId, networkId, name }, + }; + } + } + return tx as unknown as ReturnType; + } + + context.transactionBuilder = transactionBuilder; + + const ethereumjsTx = await prepareTransactionForSigning( + { + chainId: 1458, + nonce: 1, + gasPrice: BigInt(20000000000), + gas: BigInt(21000), + to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', + from: '0x2c7536E3605D9C16a7a3D7b1898e529396a65c23', + value: '1000000000', + input: '', + }, + context, + ); + expect(Number(ethereumjsTx.common.networkId())).toBe(457); + expect(ethereumjsTx.common.chainName()).toBe('test'); + }); + }); describe('should return an web3-utils/tx instance with expected properties', () => { it.each(validTransactions)( 'mockBlock: %s\nexpectedTransaction: %s\nexpectedPrivateKey: %s\nexpectedAddress: %s\nexpectedRlpEncodedTransaction: %s\nexpectedTransactionHash: %s\nexpectedMessageToSign: %s\nexpectedV: %s\nexpectedR: %s\nexpectedS: %s',