From d0b5697115469108e179aa743bac01622f1dde04 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 29 Jun 2022 16:35:31 +0200 Subject: [PATCH 01/10] :art: Update the web3.accounts to sign the transaction --- packages/web3-common/src/web3_base_wallet.ts | 4 +- packages/web3-eth-accounts/src/account.ts | 34 ++-- .../test/unit/account.test.ts | 15 +- .../utils/prepare_transaction_for_signing.ts | 43 ++-- packages/web3/src/types.ts | 23 ++- packages/web3/src/web3.ts | 70 +++++-- .../web3/test/integration/web3.abi.test.ts | 50 +++++ .../test/integration/web3.accounts.test.ts | 184 ++++++++++++++++++ .../{main_web3.test.ts => web3.test.ts} | 85 +------- .../web3_external_providers.test.ts | 44 +++++ scripts/system_tests_utils.ts | 55 ++++++ 11 files changed, 442 insertions(+), 165 deletions(-) create mode 100644 packages/web3/test/integration/web3.abi.test.ts create mode 100644 packages/web3/test/integration/web3.accounts.test.ts rename packages/web3/test/integration/{main_web3.test.ts => web3.test.ts} (77%) create mode 100644 packages/web3/test/integration/web3_external_providers.test.ts diff --git a/packages/web3-common/src/web3_base_wallet.ts b/packages/web3-common/src/web3_base_wallet.ts index 376addf6f9d..f0c54ad32a8 100644 --- a/packages/web3-common/src/web3_base_wallet.ts +++ b/packages/web3-common/src/web3_base_wallet.ts @@ -22,14 +22,14 @@ export interface Web3BaseWalletAccount { [key: string]: unknown; readonly address: string; readonly privateKey: string; - readonly signTransaction: (tx: Record) => { + readonly signTransaction: (tx: Record) => Promise<{ readonly messageHash: HexString; readonly r: HexString; readonly s: HexString; readonly v: HexString; readonly rawTransaction: HexString; readonly transactionHash: HexString; - }; + }>; readonly sign: (data: Record | string) => { readonly messageHash: HexString; readonly r: HexString; diff --git a/packages/web3-eth-accounts/src/account.ts b/packages/web3-eth-accounts/src/account.ts index e6fe85804d8..551ef3d5748 100644 --- a/packages/web3-eth-accounts/src/account.ts +++ b/packages/web3-eth-accounts/src/account.ts @@ -30,12 +30,7 @@ import { } from 'web3-errors'; import { utils, getPublicKey } from 'ethereum-cryptography/secp256k1'; import { keccak256 } from 'ethereum-cryptography/keccak'; -import { - TransactionFactory, - FeeMarketEIP1559TxData, - AccessListEIP2930TxData, - TxData, -} from '@ethereumjs/tx'; +import { TransactionFactory, TypedTransaction } from '@ethereumjs/tx'; import { ecdsaSign, ecdsaRecover } from 'secp256k1'; import { pbkdf2Sync } from 'ethereum-cryptography/pbkdf2'; import { scryptSync } from 'ethereum-cryptography/scrypt'; @@ -120,15 +115,13 @@ export const sign = (data: string, privateKey: HexString): signResult => { * @param transaction * @param privateKey */ -export const signTransaction = ( - transaction: TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData, +export const signTransaction = async ( + transaction: TypedTransaction, privateKey: HexString, -): signTransactionResult => { - // TODO : Send calls to web3.transaction package for : - // Transaction Validation checks - - const tx = TransactionFactory.fromTxData(transaction); - const signedTx = tx.sign(Buffer.from(privateKey.substring(2), 'hex')); + // To make it compatible with rest of the API, have to keep it async + // eslint-disable-next-line @typescript-eslint/require-await +): Promise => { + const signedTx = transaction.sign(Buffer.from(privateKey.substring(2), 'hex')); if (isNullish(signedTx.v) || isNullish(signedTx.r) || isNullish(signedTx.s)) throw new SignerError('Signer Error'); @@ -142,17 +135,16 @@ export const signTransaction = ( throw new SignerError(errorString); } - const rlpEncoded = signedTx.serialize().toString('hex'); - const rawTx = `0x${rlpEncoded}`; - const txHash = keccak256(Buffer.from(rawTx, 'hex')); + const rawTx = bytesToHex(signedTx.serialize()); + const txHash = keccak256(hexToBytes(rawTx)); return { - messageHash: `0x${Buffer.from(signedTx.getMessageToSign(true)).toString('hex')}`, + messageHash: bytesToHex(Buffer.from(signedTx.getMessageToSign(true))), v: `0x${signedTx.v.toString('hex')}`, r: `0x${signedTx.r.toString('hex')}`, s: `0x${signedTx.s.toString('hex')}`, rawTransaction: rawTx, - transactionHash: `0x${Buffer.from(txHash).toString('hex')}`, + transactionHash: bytesToHex(txHash), }; }; @@ -398,7 +390,9 @@ export const privateKeyToAccount = (privateKey: string | Buffer): Web3Account => return { address: privateKeyToAddress(pKey), privateKey: pKey, - signTransaction: (tx: Record) => signTransaction(tx, pKey), + signTransaction: (_tx: Record) => { + throw new SignerError('Don not have network access to sign the transaction'); + }, sign: (data: Record | string) => sign(typeof data === 'string' ? data : JSON.stringify(data), pKey), encrypt: async (password: string, options?: Record) => { diff --git a/packages/web3-eth-accounts/test/unit/account.test.ts b/packages/web3-eth-accounts/test/unit/account.test.ts index e3f9aef56bd..38ec5a52c0f 100644 --- a/packages/web3-eth-accounts/test/unit/account.test.ts +++ b/packages/web3-eth-accounts/test/unit/account.test.ts @@ -15,6 +15,7 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ +import { TransactionFactory } from '@ethereumjs/tx'; import { Web3ValidatorError } from 'web3-validator'; import { isHexStrict, Address, utf8ToHex } from 'web3-utils'; import { @@ -70,10 +71,13 @@ describe('accounts', () => { }); describe('Signing and Recovery of Transaction', () => { - it.each(transactionsTestData)('sign transaction', txData => { + it.each(transactionsTestData)('sign transaction', async txData => { const account = create(); - const signedResult = signTransaction(txData, account.privateKey); + const signedResult = await signTransaction( + TransactionFactory.fromTxData(txData), + account.privateKey, + ); expect(signedResult).toBeDefined(); expect(signedResult.messageHash).toBeDefined(); expect(signedResult.rawTransaction).toBeDefined(); @@ -83,10 +87,13 @@ describe('accounts', () => { expect(signedResult.v).toBeDefined(); }); - it.each(transactionsTestData)('Recover transaction', txData => { + it.each(transactionsTestData)('Recover transaction', async txData => { const account = create(); const txObj = { ...txData, from: account.address }; - const signedResult = signTransaction(txObj, account.privateKey); + const signedResult = await signTransaction( + TransactionFactory.fromTxData(txObj), + account.privateKey, + ); expect(signedResult).toBeDefined(); const address: Address = recoverTransaction(signedResult.rawTransaction); diff --git a/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts b/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts index 8cdd22104a8..32a145cd3da 100644 --- a/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts +++ b/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts @@ -17,7 +17,7 @@ along with web3.js. If not, see . import Common from '@ethereumjs/common'; import { TransactionFactory, TxOptions } from '@ethereumjs/tx'; -import { EthExecutionAPI, FMT_BYTES, FMT_NUMBER, FormatType } from 'web3-common'; +import { EthExecutionAPI, FormatType, ETH_DATA_FORMAT } from 'web3-common'; import { Web3Context } from 'web3-core'; import { HexString, toNumber } from 'web3-utils'; import { isNullish } from 'web3-validator'; @@ -32,10 +32,7 @@ import { formatTransaction } from './format_transaction'; import { transactionBuilder } from './transaction_builder'; const getEthereumjsTxDataFromTransaction = ( - transaction: FormatType< - PopulatedUnsignedTransaction, - { number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX } - >, + transaction: FormatType, ) => ({ nonce: transaction.nonce, gasPrice: transaction.gasPrice, @@ -46,30 +43,18 @@ const getEthereumjsTxDataFromTransaction = ( type: transaction.type, chainId: transaction.chainId, accessList: ( - transaction as FormatType< - PopulatedUnsignedEip2930Transaction, - { number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX } - > + transaction as FormatType ).accessList, maxPriorityFeePerGas: ( - transaction as FormatType< - PopulatedUnsignedEip1559Transaction, - { number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX } - > + transaction as FormatType ).maxPriorityFeePerGas, maxFeePerGas: ( - transaction as FormatType< - PopulatedUnsignedEip1559Transaction, - { number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX } - > + transaction as FormatType ).maxFeePerGas, }); const getEthereumjsTransactionOptions = ( - transaction: FormatType< - PopulatedUnsignedTransaction, - { number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX } - >, + transaction: FormatType, web3Context: Web3Context, ) => { const hasTransactionSigningOptions = @@ -118,19 +103,13 @@ export const prepareTransactionForSigning = async ( privateKey, })) as unknown as PopulatedUnsignedTransaction; - const formattedTransaction = formatTransaction(populatedTransaction, { - number: FMT_NUMBER.HEX, - bytes: FMT_BYTES.HEX, - }) as unknown as FormatType< - PopulatedUnsignedTransaction, - { number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX } - >; + const formattedTransaction = formatTransaction( + populatedTransaction, + ETH_DATA_FORMAT, + ) as unknown as FormatType; validateTransactionForSigning( - formattedTransaction as unknown as FormatType< - Transaction, - { number: FMT_NUMBER.HEX; bytes: FMT_BYTES.HEX } - >, + formattedTransaction as unknown as FormatType, ); return TransactionFactory.fromTxData( diff --git a/packages/web3/src/types.ts b/packages/web3/src/types.ts index 7879231bee1..2402c3c7da2 100644 --- a/packages/web3/src/types.ts +++ b/packages/web3/src/types.ts @@ -17,7 +17,7 @@ along with web3.js. If not, see . import { EthExecutionAPI } from 'web3-common'; import { SupportedProviders } from 'web3-core'; -import Eth from 'web3-eth'; +import Eth, { Transaction } from 'web3-eth'; import { encodeFunctionSignature, encodeFunctionCall, @@ -29,20 +29,18 @@ import { ContractAbi, } from 'web3-eth-abi'; import { - create, - privateKeyToAccount, recoverTransaction, hashMessage, recover, encrypt, - decrypt, sign, signTransaction, + Web3Account, } from 'web3-eth-accounts'; import Contract, { ContractInitOptions } from 'web3-eth-contract'; import { ENS } from 'web3-eth-ens'; import { Iban } from 'web3-eth-iban'; -import { Address } from 'web3-utils'; +import { Address, Bytes } from 'web3-utils'; export type Web3ContractConstructor = Omit & { new (jsonInterface: Abi, address?: Address, options?: ContractInitOptions): Contract; @@ -79,14 +77,21 @@ export interface Web3EthInterface extends Eth { decodeLog: typeof decodeLog; }; accounts: { - create: typeof create; - privateKeyToAccount: typeof privateKeyToAccount; - signTransaction: typeof signTransaction; + create: () => Web3Account; + privateKeyToAccount: (privateKey: Buffer | string) => Web3Account; + signTransaction: ( + transaction: Transaction, + privateKey: Bytes, + ) => ReturnType; recoverTransaction: typeof recoverTransaction; hashMessage: typeof hashMessage; sign: typeof sign; recover: typeof recover; encrypt: typeof encrypt; - decrypt: typeof decrypt; + decrypt: ( + keystore: string, + password: string, + options?: Record, + ) => Promise; }; } diff --git a/packages/web3/src/web3.ts b/packages/web3/src/web3.ts index 2f248a17d74..59439a7a9a0 100644 --- a/packages/web3/src/web3.ts +++ b/packages/web3/src/web3.ts @@ -15,9 +15,9 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ // eslint-disable-next-line max-classes-per-file -import { EthExecutionAPI } from 'web3-common'; +import { EthExecutionAPI, ETH_DATA_FORMAT, format } from 'web3-common'; import { SupportedProviders, Web3Context } from 'web3-core'; -import Web3Eth from 'web3-eth'; +import Web3Eth, { prepareTransactionForSigning, Transaction } from 'web3-eth'; import Iban from 'web3-eth-iban'; import Net from 'web3-net'; import { ENS, registryAddresses } from 'web3-eth-ens'; @@ -47,7 +47,7 @@ import { Wallet, } from 'web3-eth-accounts'; import * as utils from 'web3-utils'; -import { Address } from 'web3-utils'; +import { Address, Bytes } from 'web3-utils'; import { Web3EthInterface } from './types'; import packageJson from '../package.json'; @@ -67,14 +67,56 @@ export class Web3 extends Web3Context { public eth: Web3EthInterface; public constructor(provider?: SupportedProviders | string) { + const signTransactionWithState = async (transaction: Transaction, privateKey: Bytes) => { + const tx = await prepareTransactionForSigning(transaction, this); + + const privateKeyBytes = format({ eth: 'bytes' }, privateKey, ETH_DATA_FORMAT); + + return signTransaction(tx, privateKeyBytes); + }; + + const privateKeyToAccountWithState = (privateKey: Buffer | string) => { + const account = privateKeyToAccount(privateKey); + + return { + ...account, + signTransaction: async (transaction: Transaction) => + signTransactionWithState(transaction, account.privateKey), + }; + }; + + const decryptWithState = async ( + keystore: string, + password: string, + options?: Record, + ) => { + const account = await decrypt( + keystore, + password, + (options?.nonStrict as boolean) ?? true, + ); + + return { + ...account, + signTransaction: async (transaction: Transaction) => + signTransactionWithState(transaction, account.privateKey), + }; + }; + + const createWithState = () => { + const account = create(); + + return { + ...account, + signTransaction: async (transaction: Transaction) => + signTransactionWithState(transaction, account.privateKey), + }; + }; + const accountProvider = { - create, - privateKeyToAccount, - decrypt: async ( - keystore: string, - password: string, - options?: Record, - ) => decrypt(keystore, password, (options?.nonStrict as boolean) ?? true), + create: createWithState, + privateKeyToAccount: privateKeyToAccountWithState, + decrypt: decryptWithState, }; const wallet = new Wallet(accountProvider); @@ -138,15 +180,15 @@ export class Web3 extends Web3Context { // Accounts helper accounts: { - create, - privateKeyToAccount, - signTransaction, + create: createWithState, + privateKeyToAccount: privateKeyToAccountWithState, + signTransaction: signTransactionWithState, recoverTransaction, hashMessage, sign, recover, encrypt, - decrypt, + decrypt: decryptWithState, wallet, }, }); diff --git a/packages/web3/test/integration/web3.abi.test.ts b/packages/web3/test/integration/web3.abi.test.ts new file mode 100644 index 00000000000..86a455575d4 --- /dev/null +++ b/packages/web3/test/integration/web3.abi.test.ts @@ -0,0 +1,50 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +import { validEncodeParametersData } from '../shared_fixtures/data'; +import { + getSystemTestProvider, + waitForOpenConnection, + closeOpenConnection, +} from '../shared_fixtures/system_tests_utils'; +import { Web3 } from '../../src/index'; + +describe('web3.abi', () => { + let clientUrl: string; + let web3: Web3; + + beforeAll(async () => { + clientUrl = getSystemTestProvider(); + web3 = new Web3(clientUrl); + + await waitForOpenConnection(web3); + }); + + afterAll(async () => { + await closeOpenConnection(web3); + }); + + it('hash correctly', () => { + const validData = validEncodeParametersData[0]; + + const encodedParameters = web3.eth.abi.encodeParameters( + validData.input[0], + validData.input[1], + ); + expect(encodedParameters).toEqual(validData.output); + }); +}); diff --git a/packages/web3/test/integration/web3.accounts.test.ts b/packages/web3/test/integration/web3.accounts.test.ts new file mode 100644 index 00000000000..0be04cab82a --- /dev/null +++ b/packages/web3/test/integration/web3.accounts.test.ts @@ -0,0 +1,184 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +import { Web3Account } from 'web3-eth-accounts'; +import { + getSystemTestProvider, + getSystemTestAccounts, + createNewAccount, + waitForOpenConnection, + closeOpenConnection, +} from '../shared_fixtures/system_tests_utils'; +import { Web3 } from '../../src/index'; + +const hexRegx = /0[xX][0-9a-fA-F]+/; + +describe('web3.accounts', () => { + let clientUrl: string; + let accounts: string[]; + let web3: Web3; + + beforeAll(async () => { + clientUrl = getSystemTestProvider(); + accounts = await getSystemTestAccounts(); + web3 = new Web3(clientUrl); + + await waitForOpenConnection(web3); + }); + + afterAll(async () => { + await closeOpenConnection(web3); + }); + + describe('create', () => { + it('should create account', () => { + const account: Web3Account = web3.eth.accounts.create(); + + expect(account).toEqual( + expect.objectContaining({ + address: expect.stringMatching(hexRegx), + privateKey: expect.stringMatching(hexRegx), + }), + ); + }); + + describe('signTransaction', () => { + it('should be able to sign the transaction from created account', async () => { + const account: Web3Account = web3.eth.accounts.create(); + const tx = { + from: account.address, + to: accounts[0], + value: web3.utils.toWei('0.1', 'ether'), + gas: '0x5218', + data: '0x1', + }; + + // Fund this account with some ether + await expect( + web3.eth.sendTransaction({ + from: accounts[0], + to: account.address, + value: web3.utils.toWei('0.5', 'ether'), + }), + ).resolves.toBeDefined(); + + // Sign the tx from that account + const signedTx = await account.signTransaction(tx); + + expect(signedTx).toEqual( + expect.objectContaining({ + messageHash: expect.stringMatching(hexRegx), + rawTransaction: expect.stringMatching(hexRegx), + transactionHash: expect.stringMatching(hexRegx), + v: expect.stringMatching(hexRegx), + r: expect.stringMatching(hexRegx), + s: expect.stringMatching(hexRegx), + }), + ); + + // The signed transaction is accepted by the node + await expect( + web3.eth.sendSignedTransaction(signedTx.rawTransaction), + ).resolves.toEqual( + expect.objectContaining({ transactionHash: signedTx.transactionHash }), + ); + }); + + it('should throw error if gas is to low', async () => { + const account: Web3Account = web3.eth.accounts.create(); + + const tx = { + from: account.address, + to: accounts[0], + value: web3.utils.toWei('0.1', 'ether'), + gas: '0x1', + data: '0x1', + }; + + await expect(account.signTransaction(tx)).rejects.toThrow('gasLimit is too low.'); + }); + }); + }); + + describe('signTransaction', () => { + it('should be able to sign the transaction from created account', async () => { + const account: Web3Account = web3.eth.accounts.create(); + + const tx = { + from: account.address, + to: accounts[0], + value: web3.utils.toWei('0.1', 'ether'), + gas: '0x5218', + data: '0x1', + }; + + // Fund this account with some ether + await expect( + web3.eth.sendTransaction({ + from: accounts[0], + to: account.address, + value: web3.utils.toWei('0.5', 'ether'), + }), + ).resolves.toBeDefined(); + + // Sign the tx from that account + const signedTx = await web3.eth.accounts.signTransaction(tx, account.privateKey); + + expect(signedTx).toEqual( + expect.objectContaining({ + messageHash: expect.stringMatching(hexRegx), + rawTransaction: expect.stringMatching(hexRegx), + transactionHash: expect.stringMatching(hexRegx), + v: expect.stringMatching(hexRegx), + r: expect.stringMatching(hexRegx), + s: expect.stringMatching(hexRegx), + }), + ); + + // The signed transaction is accepted by the node + await expect(web3.eth.sendSignedTransaction(signedTx.rawTransaction)).resolves.toEqual( + expect.objectContaining({ transactionHash: signedTx.transactionHash }), + ); + }); + + it('should throw error if gas is to low', async () => { + const account: Web3Account = web3.eth.accounts.create(); + + const tx = { + from: account.address, + to: accounts[0], + value: web3.utils.toWei('0.1', 'ether'), + gas: '0x1', + data: '0x1', + }; + + await expect(web3.eth.accounts.signTransaction(tx, account.privateKey)).rejects.toThrow( + 'gasLimit is too low.', + ); + }); + }); + + describe('privateKeyToAccount', () => { + it('should create account from private key', async () => { + const acc = await createNewAccount(); + const createdAccount: Web3Account = web3.eth.accounts.privateKeyToAccount( + acc.privateKey, + ); + expect(acc.address.toLowerCase()).toBe(createdAccount.address.toLowerCase()); + }); + }); +}); diff --git a/packages/web3/test/integration/main_web3.test.ts b/packages/web3/test/integration/web3.test.ts similarity index 77% rename from packages/web3/test/integration/main_web3.test.ts rename to packages/web3/test/integration/web3.test.ts index 9d44aa7aed6..ae9e1aec977 100644 --- a/packages/web3/test/integration/main_web3.test.ts +++ b/packages/web3/test/integration/web3.test.ts @@ -20,10 +20,8 @@ import WebsocketProvider from 'web3-providers-ws'; import IpcProvider from 'web3-providers-ipc'; import Contract from 'web3-eth-contract'; import { JsonRpcOptionalRequest, Web3BaseProvider } from 'web3-common'; -import HDWalletProvider from '@truffle/hdwallet-provider'; import { SupportedProviders } from 'web3-core'; import { Web3EthExecutionAPI } from 'web3-eth/dist/web3_eth_execution_api'; -import { Web3Account } from 'web3-eth-accounts'; import { BasicAbi } from '../shared_fixtures/Basic'; import { validEncodeParametersData } from '../shared_fixtures/data'; import { @@ -31,33 +29,10 @@ import { describeIf, itIf, getSystemTestAccounts, - createNewAccount, + waitForOpenConnection, } from '../shared_fixtures/system_tests_utils'; import { Web3 } from '../../src/index'; -const waitForOpenConnection = async ( - web3Inst: Web3, - currentAttempt: number, - status = 'connected', -) => { - return new Promise((resolve, reject) => { - const maxNumberOfAttempts = 10; - const intervalTime = 5000; // ms - - const interval = setInterval(() => { - if (currentAttempt > maxNumberOfAttempts - 1) { - clearInterval(interval); - reject(new Error('Maximum number of attempts exceeded')); - } else if ((web3Inst.provider as unknown as Web3BaseProvider).getStatus() === status) { - clearInterval(interval); - resolve(); - } - // eslint-disable-next-line no-plusplus, no-param-reassign - currentAttempt++; - }, intervalTime); - }); -}; - describe('Web3 instance', () => { let clientUrl: string; let accounts: string[]; @@ -306,62 +281,4 @@ describe('Web3 instance', () => { ); }); }); - - describe('Abi requests', () => { - const validData = validEncodeParametersData[0]; - - it('hash correctly', async () => { - web3 = new Web3(clientUrl); - - const encodedParameters = web3.eth.abi.encodeParameters( - validData.input[0], - validData.input[1], - ); - expect(encodedParameters).toEqual(validData.output); - }); - }); - describe('Account module', () => { - it('should create account', async () => { - web3 = new Web3(clientUrl); - const account: Web3Account = web3.eth.accounts.create(); - expect(account).toEqual( - expect.objectContaining({ - address: expect.stringMatching(/0[xX][0-9a-fA-F]+/), - privateKey: expect.stringMatching(/0[xX][0-9a-fA-F]+/), - }), - ); - }); - it('should create account from private key', async () => { - web3 = new Web3(clientUrl); - const acc = await createNewAccount(); - const createdAccount: Web3Account = web3.eth.accounts.privateKeyToAccount( - acc.privateKey, - ); - expect(acc.address.toLowerCase()).toBe(createdAccount.address.toLowerCase()); - }); - }); -}); - -describe('Create Web3 class instance with external providers', () => { - let provider: HDWalletProvider; - let clientUrl: string; - let web3: Web3; - - beforeAll(async () => { - clientUrl = getSystemTestProvider(); - const account = await createNewAccount(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - provider = new HDWalletProvider({ - privateKeys: [account.privateKey], - providerOrUrl: clientUrl, - }); - }); - afterAll(async () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - provider.engine.stop(); - }); - it('should create instance with external wallet provider', async () => { - web3 = new Web3(provider); - expect(web3).toBeInstanceOf(Web3); - }); }); diff --git a/packages/web3/test/integration/web3_external_providers.test.ts b/packages/web3/test/integration/web3_external_providers.test.ts new file mode 100644 index 00000000000..8397a6c1930 --- /dev/null +++ b/packages/web3/test/integration/web3_external_providers.test.ts @@ -0,0 +1,44 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +import HDWalletProvider from '@truffle/hdwallet-provider'; +import { getSystemTestProvider, createNewAccount } from '../shared_fixtures/system_tests_utils'; +import { Web3 } from '../../src/index'; + +describe('Create Web3 class instance with external providers', () => { + let provider: HDWalletProvider; + let clientUrl: string; + let web3: Web3; + + beforeAll(async () => { + clientUrl = getSystemTestProvider(); + const account = await createNewAccount(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + provider = new HDWalletProvider({ + privateKeys: [account.privateKey], + providerOrUrl: clientUrl, + }); + }); + afterAll(async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + provider.engine.stop(); + }); + it('should create instance with external wallet provider', async () => { + web3 = new Web3(provider); + expect(web3).toBeInstanceOf(Web3); + }); +}); diff --git a/scripts/system_tests_utils.ts b/scripts/system_tests_utils.ts index 79108eb5fa9..2a6a8299c6a 100644 --- a/scripts/system_tests_utils.ts +++ b/scripts/system_tests_utils.ts @@ -26,6 +26,11 @@ import { create as createAccount } from 'web3-eth-accounts'; // eslint-disable-next-line import/no-extraneous-dependencies import { Web3Eth } from 'web3-eth'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { Web3Context } from 'web3-core'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import { Web3BaseProvider } from 'web3-common'; let _accounts: string[] = []; @@ -195,3 +200,53 @@ export const itIf = (condition: (() => boolean) | boolean) => export const describeIf = (condition: (() => boolean) | boolean) => (typeof condition === 'function' ? condition() : condition) ? describe : describe.skip; + +const maxNumberOfAttempts = 10; +const intervalTime = 5000; // ms + +export const waitForOpenConnection = async ( + web3Context: Web3Context, + currentAttempt = 1, + status = 'connected', +) => + new Promise((resolve, reject) => { + if (!getSystemTestProvider().startsWith('ws')) { + resolve(); + return; + } + + const interval = setInterval(() => { + if (currentAttempt > maxNumberOfAttempts - 1) { + clearInterval(interval); + reject(new Error('Maximum number of attempts exceeded')); + } else if ( + (web3Context.provider as unknown as Web3BaseProvider).getStatus() === status + ) { + clearInterval(interval); + resolve(); + } + // eslint-disable-next-line no-plusplus, no-param-reassign + currentAttempt++; + }, intervalTime); + }); + +export const closeOpenConnection = async (web3Context: Web3Context) => { + if (!getSystemTestProvider().startsWith('ws')) { + return; + } + + // make sure we try to close the connection after it is established + if ( + web3Context?.provider && + (web3Context.provider as unknown as Web3BaseProvider).getStatus() === 'connecting' + ) { + await waitForOpenConnection(web3Context); + } + + if ( + web3Context?.provider && + 'disconnect' in (web3Context.provider as unknown as Web3BaseProvider) + ) { + (web3Context.provider as unknown as Web3BaseProvider).disconnect(1000, ''); + } +}; From 65f93dcf1db57a0ab3f97c6c41b72435aa4a5647 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 29 Jun 2022 16:38:53 +0200 Subject: [PATCH 02/10] :rotating_light: Fix linter errors --- .../test/integration/web3.accounts.test.ts | 16 +++++++-------- packages/web3/test/integration/web3.test.ts | 20 ++++++++++--------- .../web3_external_providers.test.ts | 4 ++-- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/packages/web3/test/integration/web3.accounts.test.ts b/packages/web3/test/integration/web3.accounts.test.ts index 0be04cab82a..de770d5c01d 100644 --- a/packages/web3/test/integration/web3.accounts.test.ts +++ b/packages/web3/test/integration/web3.accounts.test.ts @@ -29,12 +29,12 @@ const hexRegx = /0[xX][0-9a-fA-F]+/; describe('web3.accounts', () => { let clientUrl: string; - let accounts: string[]; + let nodeAccounts: string[]; let web3: Web3; beforeAll(async () => { clientUrl = getSystemTestProvider(); - accounts = await getSystemTestAccounts(); + nodeAccounts = await getSystemTestAccounts(); web3 = new Web3(clientUrl); await waitForOpenConnection(web3); @@ -61,7 +61,7 @@ describe('web3.accounts', () => { const account: Web3Account = web3.eth.accounts.create(); const tx = { from: account.address, - to: accounts[0], + to: nodeAccounts[0], value: web3.utils.toWei('0.1', 'ether'), gas: '0x5218', data: '0x1', @@ -70,7 +70,7 @@ describe('web3.accounts', () => { // Fund this account with some ether await expect( web3.eth.sendTransaction({ - from: accounts[0], + from: nodeAccounts[0], to: account.address, value: web3.utils.toWei('0.5', 'ether'), }), @@ -103,7 +103,7 @@ describe('web3.accounts', () => { const tx = { from: account.address, - to: accounts[0], + to: nodeAccounts[0], value: web3.utils.toWei('0.1', 'ether'), gas: '0x1', data: '0x1', @@ -120,7 +120,7 @@ describe('web3.accounts', () => { const tx = { from: account.address, - to: accounts[0], + to: nodeAccounts[0], value: web3.utils.toWei('0.1', 'ether'), gas: '0x5218', data: '0x1', @@ -129,7 +129,7 @@ describe('web3.accounts', () => { // Fund this account with some ether await expect( web3.eth.sendTransaction({ - from: accounts[0], + from: nodeAccounts[0], to: account.address, value: web3.utils.toWei('0.5', 'ether'), }), @@ -160,7 +160,7 @@ describe('web3.accounts', () => { const tx = { from: account.address, - to: accounts[0], + to: nodeAccounts[0], value: web3.utils.toWei('0.1', 'ether'), gas: '0x1', data: '0x1', diff --git a/packages/web3/test/integration/web3.test.ts b/packages/web3/test/integration/web3.test.ts index ae9e1aec977..512bfa4efd5 100644 --- a/packages/web3/test/integration/web3.test.ts +++ b/packages/web3/test/integration/web3.test.ts @@ -43,9 +43,11 @@ describe('Web3 instance', () => { clientUrl = getSystemTestProvider(); accounts = await getSystemTestAccounts(); }); - beforeEach(async () => { + + beforeEach(() => { currentAttempt = 0; }); + afterEach(async () => { if (getSystemTestProvider().startsWith('ws')) { // make sure we try to close the connection after it is established @@ -92,7 +94,7 @@ describe('Web3 instance', () => { describeIf(getSystemTestProvider().startsWith('http'))( 'Create Web3 class instance with http string providers', () => { - it('should create instance with string provider', async () => { + it('should create instance with string provider', () => { web3 = new Web3(clientUrl); expect(web3).toBeInstanceOf(Web3); }); @@ -101,7 +103,7 @@ describe('Web3 instance', () => { process.env.INFURA_GOERLI_HTTP ? process.env.INFURA_GOERLI_HTTP.toString().includes('http') : false, - )('should create instance with string of external http provider', async () => { + )('should create instance with string of external http provider', () => { web3 = new Web3(process.env.INFURA_GOERLI_HTTP); // eslint-disable-next-line jest/no-standalone-expect expect(web3).toBeInstanceOf(Web3); @@ -127,7 +129,7 @@ describe('Web3 instance', () => { describeIf(getSystemTestProvider().startsWith('ws'))( 'Create Web3 class instance with ws string providers', () => { - it('should create instance with string of ws provider', async () => { + it('should create instance with string of ws provider', () => { web3 = new Web3(clientUrl); expect(web3).toBeInstanceOf(Web3); }); @@ -136,7 +138,7 @@ describe('Web3 instance', () => { process.env.INFURA_GOERLI_WS ? process.env.INFURA_GOERLI_WS.toString().includes('ws') : false, - )('should create instance with string of external ws provider', async () => { + )('should create instance with string of external ws provider', () => { web3 = new Web3(process.env.INFURA_GOERLI_WS); // eslint-disable-next-line jest/no-standalone-expect expect(web3).toBeInstanceOf(Web3); @@ -156,7 +158,7 @@ describe('Web3 instance', () => { expect(response).toEqual(expect.any(BigInt)); }); - it('should set the provider with `.setProvider`', async () => { + it('should set the provider with `.setProvider`', () => { let newProvider: Web3BaseProvider; web3 = new Web3('http://dummy.com'); if (clientUrl.startsWith('http')) { @@ -202,7 +204,7 @@ describe('Web3 instance', () => { await expect(web3.eth.getChainId()).rejects.toThrow('Provider not available'); }); - it('providers', async () => { + it('providers', () => { const res = Web3.providers; expect(Web3.providers.HttpProvider).toBe(HttpProvider); @@ -210,7 +212,7 @@ describe('Web3 instance', () => { expect(res.IpcProvider).toBe(IpcProvider); }); - it('currentProvider', async () => { + it('currentProvider', () => { web3 = new Web3(clientUrl); let checkWithClass; @@ -224,7 +226,7 @@ describe('Web3 instance', () => { expect(web3.currentProvider).toBeInstanceOf(checkWithClass); }); - it('givenProvider', async () => { + it('givenProvider', () => { const { givenProvider } = web3; expect(givenProvider).toBeUndefined(); }); diff --git a/packages/web3/test/integration/web3_external_providers.test.ts b/packages/web3/test/integration/web3_external_providers.test.ts index 8397a6c1930..44e3fcaebff 100644 --- a/packages/web3/test/integration/web3_external_providers.test.ts +++ b/packages/web3/test/integration/web3_external_providers.test.ts @@ -33,11 +33,11 @@ describe('Create Web3 class instance with external providers', () => { providerOrUrl: clientUrl, }); }); - afterAll(async () => { + afterAll(() => { // eslint-disable-next-line @typescript-eslint/no-unsafe-call provider.engine.stop(); }); - it('should create instance with external wallet provider', async () => { + it('should create instance with external wallet provider', () => { web3 = new Web3(provider); expect(web3).toBeInstanceOf(Web3); }); From 2d1f95f42b3e4c5e97ee6429977428fe88f772e3 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 29 Jun 2022 16:40:49 +0200 Subject: [PATCH 03/10] :white_check_mark: Add a test case for special case --- .../web3/test/integration/web3.accounts.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/web3/test/integration/web3.accounts.test.ts b/packages/web3/test/integration/web3.accounts.test.ts index de770d5c01d..8657284a9b5 100644 --- a/packages/web3/test/integration/web3.accounts.test.ts +++ b/packages/web3/test/integration/web3.accounts.test.ts @@ -111,6 +111,20 @@ describe('web3.accounts', () => { await expect(account.signTransaction(tx)).rejects.toThrow('gasLimit is too low.'); }); + + it.skip('should throw error if signed by private key not associated with "from" field', async () => { + const account: Web3Account = web3.eth.accounts.create(); + + const tx = { + from: nodeAccounts[0], + to: account.address, + value: web3.utils.toWei('0.1', 'ether'), + gas: '0x1', + data: '0x1', + }; + + await expect(account.signTransaction(tx)).rejects.toThrow('Error'); + }); }); }); From a2c7f37f3b0ea632f3353a792340bc2b8aa73cac Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 29 Jun 2022 16:46:40 +0200 Subject: [PATCH 04/10] :art: Fix some type names --- packages/web3-eth-accounts/src/account.ts | 14 ++++---- packages/web3-eth-accounts/src/index.ts | 1 + packages/web3-eth-accounts/src/schemas.ts | 39 +++++++++++++++++++++++ packages/web3-eth-accounts/src/types.ts | 35 ++++---------------- 4 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 packages/web3-eth-accounts/src/schemas.ts diff --git a/packages/web3-eth-accounts/src/account.ts b/packages/web3-eth-accounts/src/account.ts index 551ef3d5748..937bf7e2d9e 100644 --- a/packages/web3-eth-accounts/src/account.ts +++ b/packages/web3-eth-accounts/src/account.ts @@ -48,16 +48,16 @@ import { } from 'web3-utils'; import { validator, isBuffer, isHexString32Bytes, isString, isNullish } from 'web3-validator'; import { - signatureObject, - signResult, - signTransactionResult, + SignatureObject, + SignResult, + SignTransactionResult, KeyStore, ScryptParams, PBKDF2SHA256Params, CipherOptions, - keyStoreSchema, Web3Account, } from './types'; +import { keyStoreSchema } from './schemas'; /** * Hashes the given message. The data will be UTF-8 HEX decoded and enveloped as follows: "\x19Ethereum Signed Message:\n" + message.length + message and hashed using keccak256. @@ -81,7 +81,7 @@ export const hashMessage = (message: string): string => { * @param data * @param privateKey */ -export const sign = (data: string, privateKey: HexString): signResult => { +export const sign = (data: string, privateKey: HexString): SignResult => { const privateKeyParam = privateKey.startsWith('0x') ? privateKey.substring(2) : privateKey; if (!isHexString32Bytes(privateKeyParam, false)) { @@ -120,7 +120,7 @@ export const signTransaction = async ( privateKey: HexString, // To make it compatible with rest of the API, have to keep it async // eslint-disable-next-line @typescript-eslint/require-await -): Promise => { +): Promise => { const signedTx = transaction.sign(Buffer.from(privateKey.substring(2), 'hex')); if (isNullish(signedTx.v) || isNullish(signedTx.r) || isNullish(signedTx.s)) throw new SignerError('Signer Error'); @@ -169,7 +169,7 @@ export const recoverTransaction = (rawTransaction: HexString): Address => { * @param hashed */ export const recover = ( - data: string | signatureObject, + data: string | SignatureObject, signature?: string, hashed?: boolean, ): Address => { diff --git a/packages/web3-eth-accounts/src/index.ts b/packages/web3-eth-accounts/src/index.ts index bc9d2fc7a88..e2da0daafa5 100644 --- a/packages/web3-eth-accounts/src/index.ts +++ b/packages/web3-eth-accounts/src/index.ts @@ -18,3 +18,4 @@ along with web3.js. If not, see . export * from './account'; export * from './types'; export * from './wallet'; +export * from './schemas'; diff --git a/packages/web3-eth-accounts/src/schemas.ts b/packages/web3-eth-accounts/src/schemas.ts new file mode 100644 index 00000000000..3c88da71601 --- /dev/null +++ b/packages/web3-eth-accounts/src/schemas.ts @@ -0,0 +1,39 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +export const keyStoreSchema = { + type: 'object', + required: ['crypto', 'id', 'version', 'address'], + properties: { + crypto: { + type: 'object', + required: ['cipher', 'ciphertext', 'cipherparams', 'kdf', 'kdfparams', 'mac'], + properties: { + cipher: { type: 'string' }, + ciphertext: { type: 'string' }, + cipherparams: { type: 'object' }, + kdf: { type: 'string' }, + kdfparams: { type: 'object' }, + salt: { type: 'string' }, + mac: { type: 'string' }, + }, + }, + id: { type: 'string' }, + version: { type: 'number' }, + address: { type: 'string' }, + }, +}; diff --git a/packages/web3-eth-accounts/src/types.ts b/packages/web3-eth-accounts/src/types.ts index 27fcce67a4c..e658658deac 100644 --- a/packages/web3-eth-accounts/src/types.ts +++ b/packages/web3-eth-accounts/src/types.ts @@ -19,55 +19,32 @@ import { FeeMarketEIP1559TxData, AccessListEIP2930TxData, TxData } from '@ethere import { Web3BaseWalletAccount } from 'web3-common'; import { HexString } from 'web3-utils'; -export type signatureObject = { +export type SignatureObject = { messageHash: string; r: string; s: string; v: string; }; -export const keyStoreSchema = { - type: 'object', - required: ['crypto', 'id', 'version', 'address'], - properties: { - crypto: { - type: 'object', - required: ['cipher', 'ciphertext', 'cipherparams', 'kdf', 'kdfparams', 'mac'], - properties: { - cipher: { type: 'string' }, - ciphertext: { type: 'string' }, - cipherparams: { type: 'object' }, - kdf: { type: 'string' }, - kdfparams: { type: 'object' }, - salt: { type: 'string' }, - mac: { type: 'string' }, - }, - }, - id: { type: 'string' }, - version: { type: 'number' }, - address: { type: 'string' }, - }, -}; - -export type signTransactionResult = signatureObject & { +export type SignTransactionResult = SignatureObject & { rawTransaction: string; transactionHash: string; }; -export type signTransactionFunction = ( +export type SignTransactionFunction = ( transaction: | TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData | Record, -) => signTransactionResult; +) => SignTransactionResult; -export type signResult = signatureObject & { +export type SignResult = SignatureObject & { message?: string; signature: string; }; -export type signFunction = (data: string, privateKey: string) => signResult; +export type SignFunction = (data: string, privateKey: string) => SignResult; // https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition From bf6237f73e9b48435e33b49ccb62aff45b7a5eec Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 29 Jun 2022 17:31:57 +0200 Subject: [PATCH 05/10] :art: Update the usage of promise --- packages/web3-eth/src/rpc_method_wrappers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web3-eth/src/rpc_method_wrappers.ts b/packages/web3-eth/src/rpc_method_wrappers.ts index 34c31c857e3..664d8b05ed7 100644 --- a/packages/web3-eth/src/rpc_method_wrappers.ts +++ b/packages/web3-eth/src/rpc_method_wrappers.ts @@ -535,7 +535,7 @@ export function sendTransaction< } if (wallet) { - const signedTransaction = wallet.signTransaction( + const signedTransaction = await wallet.signTransaction( transactionFormatted as Record, ); From b490090b5b347a16a9e460b9983ec94fee67a288 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 30 Jun 2022 17:46:34 +0200 Subject: [PATCH 06/10] Update packages/web3-eth-accounts/src/account.ts Co-authored-by: Junaid <86780488+jdevcs@users.noreply.github.com> --- packages/web3-eth-accounts/src/account.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web3-eth-accounts/src/account.ts b/packages/web3-eth-accounts/src/account.ts index 937bf7e2d9e..8aba1bc0436 100644 --- a/packages/web3-eth-accounts/src/account.ts +++ b/packages/web3-eth-accounts/src/account.ts @@ -391,7 +391,7 @@ export const privateKeyToAccount = (privateKey: string | Buffer): Web3Account => address: privateKeyToAddress(pKey), privateKey: pKey, signTransaction: (_tx: Record) => { - throw new SignerError('Don not have network access to sign the transaction'); + throw new SignerError('Do not have network access to sign the transaction'); }, sign: (data: Record | string) => sign(typeof data === 'string' ? data : JSON.stringify(data), pKey), From 10fd57316da026de8e89e03570402856d541ab28 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 1 Jul 2022 13:09:30 +0200 Subject: [PATCH 07/10] :art: Update the initialization of web3 properties --- packages/web3-core/src/web3_context.ts | 8 +- packages/web3/src/abi.ts | 41 ++++++++ packages/web3/src/accounts.ts | 103 ++++++++++++++++++++ packages/web3/src/web3.ts | 126 ++++--------------------- 4 files changed, 167 insertions(+), 111 deletions(-) create mode 100644 packages/web3/src/abi.ts create mode 100644 packages/web3/src/accounts.ts diff --git a/packages/web3-core/src/web3_context.ts b/packages/web3-core/src/web3_context.ts index e52855b7d9d..3e5c1c22e7d 100644 --- a/packages/web3-core/src/web3_context.ts +++ b/packages/web3-core/src/web3_context.ts @@ -92,10 +92,10 @@ export class Web3Context< public static readonly providers = Web3RequestManager.providers; public static givenProvider?: SupportedProviders; public readonly providers = Web3RequestManager.providers; - private _requestManager: Web3RequestManager; - private _subscriptionManager?: Web3SubscriptionManager; - private _accountProvider?: Web3AccountProvider; - private _wallet?: Web3BaseWallet; + protected _requestManager: Web3RequestManager; + protected _subscriptionManager?: Web3SubscriptionManager; + protected _accountProvider?: Web3AccountProvider; + protected _wallet?: Web3BaseWallet; public constructor( providerOrContext?: diff --git a/packages/web3/src/abi.ts b/packages/web3/src/abi.ts new file mode 100644 index 00000000000..5abe7536266 --- /dev/null +++ b/packages/web3/src/abi.ts @@ -0,0 +1,41 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +import { + decodeLog, + decodeParameter, + decodeParameters, + encodeEventSignature, + encodeFunctionCall, + encodeFunctionSignature, + encodeParameter, + encodeParameters, +} from 'web3-eth-abi'; + +/** + * The object for `web3.abi` + */ +export default { + encodeEventSignature, + encodeFunctionCall, + encodeFunctionSignature, + encodeParameter, + encodeParameters, + decodeParameter, + decodeParameters, + decodeLog, +}; diff --git a/packages/web3/src/accounts.ts b/packages/web3/src/accounts.ts new file mode 100644 index 00000000000..e4fa7cc285d --- /dev/null +++ b/packages/web3/src/accounts.ts @@ -0,0 +1,103 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +import { EthExecutionAPI, ETH_DATA_FORMAT, format } from 'web3-common'; +import { Web3Context } from 'web3-core'; +import { prepareTransactionForSigning, Transaction } from 'web3-eth'; +import { + create, + decrypt, + encrypt, + hashMessage, + privateKeyToAccount, + recover, + recoverTransaction, + signTransaction, + sign, + Wallet, +} from 'web3-eth-accounts'; +import { Bytes } from 'web3-utils'; + +/** + * Initialize the accounts module for the given context. + * + * To avoid multiple package dependencies for `web3-eth-accounts` we are creating + * this function in `web3` package. In future the actual `web3-eth-accounts` package + * should be converted to context aware. + */ +export const initAccountsForContext = (context: Web3Context) => { + const signTransactionWithContext = async (transaction: Transaction, privateKey: Bytes) => { + const tx = await prepareTransactionForSigning(transaction, context); + + const privateKeyBytes = format({ eth: 'bytes' }, privateKey, ETH_DATA_FORMAT); + + return signTransaction(tx, privateKeyBytes); + }; + + const privateKeyToAccountWithContext = (privateKey: Buffer | string) => { + const account = privateKeyToAccount(privateKey); + + return { + ...account, + signTransaction: async (transaction: Transaction) => + signTransactionWithContext(transaction, account.privateKey), + }; + }; + + const decryptWithContext = async ( + keystore: string, + password: string, + options?: Record, + ) => { + const account = await decrypt(keystore, password, (options?.nonStrict as boolean) ?? true); + + return { + ...account, + signTransaction: async (transaction: Transaction) => + signTransactionWithContext(transaction, account.privateKey), + }; + }; + + const createWithContext = () => { + const account = create(); + + return { + ...account, + signTransaction: async (transaction: Transaction) => + signTransactionWithContext(transaction, account.privateKey), + }; + }; + + const wallet = new Wallet({ + create: createWithContext, + privateKeyToAccount: privateKeyToAccountWithContext, + decrypt: decryptWithContext, + }); + + return { + signTransaction: signTransactionWithContext, + create: createWithContext, + privateKeyToAccount: privateKeyToAccountWithContext, + decrypt: decryptWithContext, + recoverTransaction, + hashMessage, + sign, + recover, + encrypt, + wallet, + }; +}; diff --git a/packages/web3/src/web3.ts b/packages/web3/src/web3.ts index 59439a7a9a0..e6b148922c3 100644 --- a/packages/web3/src/web3.ts +++ b/packages/web3/src/web3.ts @@ -15,41 +15,21 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ // eslint-disable-next-line max-classes-per-file -import { EthExecutionAPI, ETH_DATA_FORMAT, format } from 'web3-common'; +import { EthExecutionAPI } from 'web3-common'; import { SupportedProviders, Web3Context } from 'web3-core'; -import Web3Eth, { prepareTransactionForSigning, Transaction } from 'web3-eth'; -import Iban from 'web3-eth-iban'; -import Net from 'web3-net'; +import Web3Eth from 'web3-eth'; +import { ContractAbi } from 'web3-eth-abi'; +import Contract, { ContractInitOptions } from 'web3-eth-contract'; import { ENS, registryAddresses } from 'web3-eth-ens'; +import Iban from 'web3-eth-iban'; import Personal from 'web3-eth-personal'; -import { - ContractAbi, - encodeFunctionCall, - encodeParameter, - encodeParameters, - decodeParameter, - decodeParameters, - encodeFunctionSignature, - encodeEventSignature, - decodeLog, -} from 'web3-eth-abi'; -import Contract, { ContractInitOptions } from 'web3-eth-contract'; -import { - create, - privateKeyToAccount, - signTransaction, - recoverTransaction, - hashMessage, - sign, - recover, - encrypt, - decrypt, - Wallet, -} from 'web3-eth-accounts'; +import Net from 'web3-net'; import * as utils from 'web3-utils'; -import { Address, Bytes } from 'web3-utils'; -import { Web3EthInterface } from './types'; +import { Address } from 'web3-utils'; import packageJson from '../package.json'; +import abi from './abi'; +import { initAccountsForContext } from './accounts'; +import { Web3EthInterface } from './types'; export class Web3 extends Web3Context { public static version = packageJson.version; @@ -67,61 +47,13 @@ export class Web3 extends Web3Context { public eth: Web3EthInterface; public constructor(provider?: SupportedProviders | string) { - const signTransactionWithState = async (transaction: Transaction, privateKey: Bytes) => { - const tx = await prepareTransactionForSigning(transaction, this); - - const privateKeyBytes = format({ eth: 'bytes' }, privateKey, ETH_DATA_FORMAT); - - return signTransaction(tx, privateKeyBytes); - }; - - const privateKeyToAccountWithState = (privateKey: Buffer | string) => { - const account = privateKeyToAccount(privateKey); - - return { - ...account, - signTransaction: async (transaction: Transaction) => - signTransactionWithState(transaction, account.privateKey), - }; - }; - - const decryptWithState = async ( - keystore: string, - password: string, - options?: Record, - ) => { - const account = await decrypt( - keystore, - password, - (options?.nonStrict as boolean) ?? true, - ); - - return { - ...account, - signTransaction: async (transaction: Transaction) => - signTransactionWithState(transaction, account.privateKey), - }; - }; - - const createWithState = () => { - const account = create(); - - return { - ...account, - signTransaction: async (transaction: Transaction) => - signTransactionWithState(transaction, account.privateKey), - }; - }; - - const accountProvider = { - create: createWithState, - privateKeyToAccount: privateKeyToAccountWithState, - decrypt: decryptWithState, - }; - - const wallet = new Wallet(accountProvider); - - super({ provider, wallet, accountProvider }); + super({ provider }); + + const accounts = initAccountsForContext(this); + + // Init protected properties + this._wallet = accounts.wallet; + this._accountProvider = accounts; this.utils = utils; @@ -167,30 +99,10 @@ export class Web3 extends Web3Context { Contract: ContractBuilder, // ABI Helpers - abi: { - encodeEventSignature, - encodeFunctionCall, - encodeFunctionSignature, - encodeParameter, - encodeParameters, - decodeParameter, - decodeParameters, - decodeLog, - }, + abi, // Accounts helper - accounts: { - create: createWithState, - privateKeyToAccount: privateKeyToAccountWithState, - signTransaction: signTransactionWithState, - recoverTransaction, - hashMessage, - sign, - recover, - encrypt, - decrypt: decryptWithState, - wallet, - }, + accounts, }); } } From c0d977cf547a82e4fa38a1704064f584a1f6ca50 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 1 Jul 2022 13:13:27 +0200 Subject: [PATCH 08/10] :arrow_down: Update the docs for accounts module --- packages/web3-eth-accounts/src/account.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/web3-eth-accounts/src/account.ts b/packages/web3-eth-accounts/src/account.ts index 8aba1bc0436..eb40324e0b6 100644 --- a/packages/web3-eth-accounts/src/account.ts +++ b/packages/web3-eth-accounts/src/account.ts @@ -112,8 +112,9 @@ export const sign = (data: string, privateKey: HexString): SignResult => { /** * Signs an Ethereum transaction with a given private key. * - * @param transaction - * @param privateKey + * This function is not stateful here. We need network access to get the account `nonce` and `chainId` to sign the transaction. + * This function will rely on user to provide the full transaction to be signed. If you want to sign a partial transaction object + * Use {@link Web3.eth.accounts.signTransaction} instead. */ export const signTransaction = async ( transaction: TypedTransaction, @@ -382,7 +383,8 @@ export const encrypt = async ( /** * Get account from private key * - * @param privateKey + * The `Web3Account.signTransaction` is not stateful here. We need network access to get the account `nonce` and `chainId` to sign the transaction. + * Use {@link Web3.eth.accounts.signTransaction} instead. */ export const privateKeyToAccount = (privateKey: string | Buffer): Web3Account => { const pKey = Buffer.isBuffer(privateKey) ? Buffer.from(privateKey).toString('hex') : privateKey; From a4f1b535371383b30251733241352edf2e642ae7 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 1 Jul 2022 13:30:57 +0200 Subject: [PATCH 09/10] :art: Update a code comment. --- packages/web3/test/integration/web3.accounts.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web3/test/integration/web3.accounts.test.ts b/packages/web3/test/integration/web3.accounts.test.ts index 8657284a9b5..278f029df9e 100644 --- a/packages/web3/test/integration/web3.accounts.test.ts +++ b/packages/web3/test/integration/web3.accounts.test.ts @@ -112,6 +112,7 @@ describe('web3.accounts', () => { await expect(account.signTransaction(tx)).rejects.toThrow('gasLimit is too low.'); }); + // This test should fail, but it's not. Need to debug further to figure out why. it.skip('should throw error if signed by private key not associated with "from" field', async () => { const account: Web3Account = web3.eth.accounts.create(); From c6cd862128a780efb50286638c0372c43a67b345 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 1 Jul 2022 14:05:41 +0200 Subject: [PATCH 10/10] :white_check_mark: Fix a failing test --- packages/web3/test/integration/web3.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web3/test/integration/web3.test.ts b/packages/web3/test/integration/web3.test.ts index 93c7bfb7e13..084db03598a 100644 --- a/packages/web3/test/integration/web3.test.ts +++ b/packages/web3/test/integration/web3.test.ts @@ -30,6 +30,7 @@ import { getSystemTestAccounts, getSystemTestProvider, isHttp, + isIpc, isWs, waitForOpenConnection, } from '../shared_fixtures/system_tests_utils';