diff --git a/packages/web3-core/CHANGELOG.md b/packages/web3-core/CHANGELOG.md index 6c2a9bd708a..c07e7d02cd1 100644 --- a/packages/web3-core/CHANGELOG.md +++ b/packages/web3-core/CHANGELOG.md @@ -193,8 +193,12 @@ Documentation: ## [4.3.1] +### Fixed + +- Fix `Web3Config` to properly update within other web3 packages when `setConfig` is used (#6555) + ### Added - Added `isMetaMaskProvider` function to check if provider is metamask (#6534) -## [Unreleased] \ No newline at end of file +## [Unreleased] diff --git a/packages/web3-core/src/web3_config.ts b/packages/web3-core/src/web3_config.ts index a8dffebab03..f6ac722bc0e 100644 --- a/packages/web3-core/src/web3_config.ts +++ b/packages/web3-core/src/web3_config.ts @@ -97,12 +97,15 @@ export abstract class Web3Config public constructor(options?: Partial) { super(); - this.setConfig(options ?? {}); } public setConfig(options: Partial) { // TODO: Improve and add key check + const keys = Object.keys(options) as (keyof Web3ConfigOptions)[]; + for (const key of keys) { + this._triggerConfigChange(key, options[key]) + } Object.assign(this.config, options); } diff --git a/packages/web3-core/src/web3_context.ts b/packages/web3-core/src/web3_context.ts index f653b581456..7861c6a816a 100644 --- a/packages/web3-core/src/web3_context.ts +++ b/packages/web3-core/src/web3_context.ts @@ -108,7 +108,7 @@ export class Web3Context< | Web3ContextInitOptions, ) { super(); - + // If "providerOrContext" is provided as "string" or an objects matching "SupportedProviders" interface if ( isNullish(providerOrContext) || diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts index 6290ab20140..198b8d4c8a0 100644 --- a/packages/web3-eth-contract/src/contract.ts +++ b/packages/web3-eth-contract/src/contract.ts @@ -241,7 +241,6 @@ export class Contract * RPC provider when using contract methods. * Default is `input` */ - private readonly _dataInputFill?: 'data' | 'input' | 'both'; private context?: Web3Context; /** @@ -369,17 +368,11 @@ export class Contract : isDataFormat(optionsOrContextOrReturnFormat) ? optionsOrContextOrReturnFormat : returnFormat ?? DEFAULT_RETURN_FORMAT; - const address = typeof addressOrOptionsOrContext === 'string' ? addressOrOptionsOrContext : undefined; - - if (this.config.contractDataInputFill === 'both') { - this._dataInputFill = this.config.contractDataInputFill; - } else { - this._dataInputFill = + this.config.contractDataInputFill = (options as ContractInitOptions)?.dataInputFill ?? this.config.contractDataInputFill; - } this._parseAndSetJsonInterface(jsonInterface, returnDataFormat); if (!isNullish(address)) { @@ -409,6 +402,13 @@ export class Contract set: (value: ContractAbi) => this._parseAndSetJsonInterface(value, returnDataFormat), get: () => this._jsonInterface, }); + + if (contractContext instanceof Web3Context) { + contractContext.on(Web3ConfigEvent.CONFIG_CHANGE, event => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + this.setConfig({ [event.name]: event.newValue }); + }); + } } /** @@ -501,7 +501,7 @@ export class Contract data: this.options.data, provider: this.currentProvider, syncWithContext: this.syncWithContext, - dataInputFill: this._dataInputFill, + dataInputFill: this.config.contractDataInputFill, }, this.getContextObject(), ); @@ -516,7 +516,7 @@ export class Contract data: this.options.data, provider: this.currentProvider, syncWithContext: this.syncWithContext, - dataInputFill: this._dataInputFill, + dataInputFill: this.config.contractDataInputFill, }, this.getContextObject(), ); @@ -1014,7 +1014,7 @@ export class Contract params, options: { ...options, - dataInputFill: this._dataInputFill, + dataInputFill: this.config.contractDataInputFill, }, contractOptions: { ...this.options, @@ -1088,7 +1088,7 @@ export class Contract checkRevertBeforeSending: false, contractAbi: this._jsonInterface, }); - + // eslint-disable-next-line no-void void transactionToSend.on('error', (error: unknown) => { if (error instanceof ContractExecutionError) { diff --git a/packages/web3-eth-contract/src/utils.ts b/packages/web3-eth-contract/src/utils.ts index 2cbe4a49715..f3d45528ede 100644 --- a/packages/web3-eth-contract/src/utils.ts +++ b/packages/web3-eth-contract/src/utils.ts @@ -25,10 +25,9 @@ import { Address, NonPayableCallOptions, PayableCallOptions, - ContractInitOptions, ContractOptions, } from 'web3-types'; -import { isNullish, mergeDeep } from 'web3-utils'; +import { isNullish, mergeDeep, isContractInitOptions } from 'web3-utils'; import { encodeMethodABI } from './encoding.js'; import { Web3ContractContext } from './types.js'; @@ -165,24 +164,11 @@ export const getEstimateGasParams = ({ return txParams as TransactionWithSenderAPI; }; -export const isContractInitOptions = (options: unknown): options is ContractInitOptions => - typeof options === 'object' && - !isNullish(options) && - [ - 'input', - 'data', - 'from', - 'gas', - 'gasPrice', - 'gasLimit', - 'address', - 'jsonInterface', - 'syncWithContext', - 'dataInputFill', - ].some(key => key in options); +export { isContractInitOptions } from 'web3-utils'; export const isWeb3ContractContext = (options: unknown): options is Web3ContractContext => - typeof options === 'object' && !isNullish(options) && !isContractInitOptions(options); + typeof options === 'object' && !isNullish(options) && + Object.keys(options).length !== 0 && !isContractInitOptions(options); export const getCreateAccessListParams = ({ abi, diff --git a/packages/web3-eth/src/rpc_method_wrappers.ts b/packages/web3-eth/src/rpc_method_wrappers.ts index 7aad8d33a81..4dd01805603 100644 --- a/packages/web3-eth/src/rpc_method_wrappers.ts +++ b/packages/web3-eth/src/rpc_method_wrappers.ts @@ -507,7 +507,7 @@ export function sendTransaction< }, ETH_DATA_FORMAT, ); - + try { transactionFormatted = await sendTxHelper.populateGasPrice({ transaction, 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 42072d2d9a9..607994d05a4 100644 --- a/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts +++ b/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts @@ -141,7 +141,7 @@ export const prepareTransactionForSigning = async ( validateTransactionForSigning( formattedTransaction as unknown as FormatType, ); - + return TransactionFactory.fromTxData( getEthereumjsTxDataFromTransaction(formattedTransaction), getEthereumjsTransactionOptions(formattedTransaction, web3Context), diff --git a/packages/web3-eth/src/web3_eth.ts b/packages/web3-eth/src/web3_eth.ts index c4b3b6a92f8..28f161413d1 100644 --- a/packages/web3-eth/src/web3_eth.ts +++ b/packages/web3-eth/src/web3_eth.ts @@ -95,7 +95,7 @@ export class Web3Eth extends Web3Context + typeof options === 'object' && + !isNullishValidator(options) && + Object.keys(options).length !== 0 && + [ + 'input', + 'data', + 'from', + 'gas', + 'gasPrice', + 'gasLimit', + 'address', + 'jsonInterface', + 'syncWithContext', + 'dataInputFill', + ].some(key => key in options); + export const isNullish = isNullishValidator; diff --git a/packages/web3-utils/test/fixtures/validation.ts b/packages/web3-utils/test/fixtures/validation.ts index ec88e4fcb12..3c216d3ff62 100644 --- a/packages/web3-utils/test/fixtures/validation.ts +++ b/packages/web3-utils/test/fixtures/validation.ts @@ -15,7 +15,7 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -import { Numbers } from 'web3-types'; +import { Numbers, ContractInitOptions } from 'web3-types'; import { InvalidBlockError } from 'web3-errors'; export const compareBlockNumbersValidData: [[Numbers, Numbers], number][] = [ @@ -77,3 +77,18 @@ export const isBloomValidData: [any, true][] = [ true, ], ]; + +export const isContractInitValidData: ContractInitOptions[] = [ + {dataInputFill: "data"}, + {syncWithContext: true}, + {gas: "100000", + syncWithContext: true, + dataInputFill: "data", + }, +]; + +export const isContractInitInvalidData: unknown[] = [ + "", + 12, + {} +]; \ No newline at end of file diff --git a/packages/web3-utils/test/unit/validation.test.ts b/packages/web3-utils/test/unit/validation.test.ts index 84e9280bf8e..9d087e3a2eb 100644 --- a/packages/web3-utils/test/unit/validation.test.ts +++ b/packages/web3-utils/test/unit/validation.test.ts @@ -18,10 +18,12 @@ along with web3.js. If not, see . /* eslint-disable jest/no-conditional-expect */ import { InvalidBlockError } from 'web3-errors'; -import { compareBlockNumbers } from '../../src/validation'; +import { compareBlockNumbers, isContractInitOptions } from '../../src/validation'; import { compareBlockNumbersInvalidData, compareBlockNumbersValidData, + isContractInitValidData, + isContractInitInvalidData } from '../fixtures/validation'; describe('validation', () => { @@ -37,4 +39,20 @@ describe('validation', () => { }, ); }); + describe('isContractInit', () => { + describe('should return true', () => { + it.each([...isContractInitValidData])( + '%s', (input) => { + expect(isContractInitOptions(input)).toBe(true); + } + ) + }); + describe('should return false', () => { + it.each([...isContractInitInvalidData])( + '%s', (input) => { + expect(isContractInitOptions(input)).toBe(false); + } + ) + }); + }); }); diff --git a/packages/web3/src/web3.ts b/packages/web3/src/web3.ts index 2b97963121b..e6d52e0da50 100644 --- a/packages/web3/src/web3.ts +++ b/packages/web3/src/web3.ts @@ -20,7 +20,7 @@ import { Web3ContextInitOptions, Web3ContextObject, Web3SubscriptionConstructor, - isSupportedProvider, + isSupportedProvider } from 'web3-core'; import { Web3Eth, RegisteredSubscription, registeredSubscriptions } from 'web3-eth'; import Contract from 'web3-eth-contract'; @@ -29,13 +29,15 @@ import { Iban } from 'web3-eth-iban'; import { Personal } from 'web3-eth-personal'; import { Net } from 'web3-net'; import * as utils from 'web3-utils'; -import { isNullish } from 'web3-utils'; +import { isNullish, isDataFormat, isContractInitOptions } from 'web3-utils'; import { Address, ContractAbi, ContractInitOptions, EthExecutionAPI, SupportedProviders, + DataFormat, + DEFAULT_RETURN_FORMAT } from 'web3-types'; import { InvalidMethodParamsError } from 'web3-errors'; import abi from './abi.js'; @@ -117,38 +119,76 @@ export class Web3< class ContractBuilder extends Contract { public constructor(jsonInterface: Abi); - public constructor(jsonInterface: Abi, address: Address); - public constructor(jsonInterface: Abi, options: ContractInitOptions); - public constructor(jsonInterface: Abi, address: Address, options: ContractInitOptions); + public constructor(jsonInterface: Abi, + addressOrOptionsOrContext?: Address | ContractInitOptions | Web3Context, + ); public constructor( jsonInterface: Abi, - addressOrOptions?: Address | ContractInitOptions, - options?: ContractInitOptions, - ) { - if (typeof addressOrOptions === 'object' && typeof options === 'object') { + addressOrOptionsOrContext?: Address | ContractInitOptions | Web3Context, + optionsOrContextOrReturnFormat?: ContractInitOptions | Web3Context | DataFormat, + ); + public constructor(jsonInterface: Abi, + addressOrOptionsOrContext?: Address | ContractInitOptions, + optionsOrContextOrReturnFormat?: ContractInitOptions, + contextOrReturnFormat?: Web3Context | DataFormat, + ); + public constructor(jsonInterface: Abi, + addressOrOptionsOrContext?: Address | ContractInitOptions, + optionsOrContextOrReturnFormat?: ContractInitOptions, + contextOrReturnFormat?: Web3Context | DataFormat, + ); + public constructor(jsonInterface: Abi, + addressOrOptionsOrContext?: Address | ContractInitOptions, + optionsOrContextOrReturnFormat?: ContractInitOptions, + contextOrReturnFormat?: Web3Context | DataFormat, + returnFormat?: DataFormat + ) + { + if (isContractInitOptions(addressOrOptionsOrContext) && isContractInitOptions(optionsOrContextOrReturnFormat)) { throw new InvalidMethodParamsError( 'Should not provide options at both 2nd and 3rd parameters', ); } - if (isNullish(addressOrOptions)) { - super(jsonInterface, options, self.getContextObject() as Web3ContextObject); - } else if (typeof addressOrOptions === 'object') { - super( - jsonInterface, - addressOrOptions, - self.getContextObject() as Web3ContextObject, - ); - } else if (typeof addressOrOptions === 'string') { - super( - jsonInterface, - addressOrOptions, - options ?? {}, - self.getContextObject() as Web3ContextObject, - ); - } else { + let address: string | undefined; + let options: object = {}; + let context: Web3ContextObject; + let dataFormat: DataFormat = DEFAULT_RETURN_FORMAT; + + // add validation so its not a breaking change + if (!isNullish(addressOrOptionsOrContext) && typeof addressOrOptionsOrContext !== 'object' && typeof addressOrOptionsOrContext !== 'string') { throw new InvalidMethodParamsError(); } + if (typeof addressOrOptionsOrContext === 'string') { + address = addressOrOptionsOrContext; + } + if (isContractInitOptions(addressOrOptionsOrContext)){ + options = addressOrOptionsOrContext as object; + } else if (isContractInitOptions(optionsOrContextOrReturnFormat)) { + options = optionsOrContextOrReturnFormat as object; + } else { + options = {} + } + + if (addressOrOptionsOrContext instanceof Web3Context) { + context = addressOrOptionsOrContext; + } else if (optionsOrContextOrReturnFormat instanceof Web3Context) { + context = optionsOrContextOrReturnFormat; + } else if (contextOrReturnFormat instanceof Web3Context) { + context = contextOrReturnFormat; + } else { + context = self.getContextObject() as Web3ContextObject; + } + + if (returnFormat){ + dataFormat = returnFormat; + } else if (isDataFormat(optionsOrContextOrReturnFormat)) { + dataFormat = optionsOrContextOrReturnFormat as DataFormat; + } else if (isDataFormat(contextOrReturnFormat)) { + dataFormat = contextOrReturnFormat; + } + + super(jsonInterface,address, options, context, dataFormat) super.subscribeToContextEvents(self); } } diff --git a/packages/web3/test/integration/web3.config.test.ts b/packages/web3/test/integration/web3.config.test.ts new file mode 100644 index 00000000000..abf115b7276 --- /dev/null +++ b/packages/web3/test/integration/web3.config.test.ts @@ -0,0 +1,83 @@ +/* +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 { SupportedProviders, Web3BaseProvider } from 'web3-types'; +import { + closeOpenConnection, + createTempAccount, + getSystemTestProvider, + isWs, + waitForOpenConnection, +} from '../shared_fixtures/system_tests_utils'; +import Web3 from '../../src/index'; + +describe('Web3 instance', () => { + let provider: string | SupportedProviders; + let accounts: string[]; + let web3: Web3; + let currentAttempt = 0; + + beforeEach(() => { + currentAttempt = 0; + }); + + beforeAll(async () => { + provider = getSystemTestProvider(); + const acc1 = await createTempAccount(); + const acc2 = await createTempAccount(); + accounts = [acc1.address, acc2.address]; + web3 = new Web3(provider); + }); + afterAll(async () => { + try { + await closeOpenConnection(web3); + } catch (e) { + console.warn("Failed to close open con", e) + } + }); + + afterEach(async () => { + if (isWs) { + // make sure we try to close the connection after it is established + if ( + web3?.provider && + (web3.provider as unknown as Web3BaseProvider).getStatus() === 'connecting' + ) { + await waitForOpenConnection(web3, currentAttempt); + } + + if (web3?.provider) { + (web3.provider as unknown as Web3BaseProvider).disconnect(1000, ''); + } + } + }); + + it('should be send transaction, change for defaultTransactionType and sucesfully send transaction with different type', async () => { + const transaction = { + from: accounts[0], + to: accounts[0], + value: 100000, + } + + const receipt = await web3.eth.sendTransaction(transaction); + expect(receipt.type).toEqual(BigInt(2)) + + web3.setConfig({defaultTransactionType: "0x0"}); + + const receipt2 = await web3.eth.sendTransaction(transaction); + expect(receipt2.type).toEqual(BigInt(0)) + }) +}) \ No newline at end of file diff --git a/packages/web3/test/unit/web3.config.test.ts b/packages/web3/test/unit/web3.config.test.ts new file mode 100644 index 00000000000..dc7217dff7d --- /dev/null +++ b/packages/web3/test/unit/web3.config.test.ts @@ -0,0 +1,152 @@ +/* +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 { ETH_DATA_FORMAT } from 'web3-types'; +import {Web3Context} from 'web3-core'; +import { Web3 } from '../../src'; + +describe('web3config web3 tests', () => { + + describe('web3config contract', () => { + + it('create web3context with configs and should set it for web3', async () => { + const context = new Web3Context("http://127.0.0.1:8545"); + context.setConfig({defaultTransactionType: "0x0"}); + const web3 = new Web3(context); + expect(web3.getContextObject().config.defaultTransactionType).toBe("0x0"); + expect(web3.config.defaultTransactionType).toBe("0x0"); + expect(web3.eth.getContextObject().config.defaultTransactionType).toBe("0x0") + }); + + it('should be able to create web3 and setconfig for contracts', async () => { + const web3 = new Web3("http://127.0.0.1:8545"); + web3.setConfig({defaultTransactionType: "0x0"}); + expect(web3.getContextObject().config.defaultTransactionType).toBe("0x0"); + expect(web3.config.defaultTransactionType).toBe("0x0"); + expect(web3.eth.getContextObject().config.defaultTransactionType).toBe("0x0") + + const contract = new web3.eth.Contract([], ""); + expect(contract.config.defaultTransactionType).toBe("0x0") + expect(contract.getContextObject().config.defaultTransactionType).toBe("0x0"); + + contract.setConfig({contractDataInputFill:"both"}); + expect(contract.getContextObject().config.contractDataInputFill).toBe("both"); + + // web3 config shouldn't change + expect(web3.getContextObject().config.contractDataInputFill).toBe("input"); + expect(web3.config.contractDataInputFill).toBe("input"); + expect(web3.eth.getContextObject().config.contractDataInputFill).toBe("input") + }); + it('should change web3 config context but not contract config context', async () => { + const web3 = new Web3("http://127.0.0.1:8545"); + const contract = new web3.eth.Contract([]) + web3.setConfig({defaultTransactionType:"0x0"}); + expect(contract.getContextObject().config.defaultTransactionType).toBe("0x2") + expect(web3.getContextObject().config.defaultTransactionType).toBe("0x0"); + }); + + it('should not change web3config when changing contract config context', async () => { + const web3 = new Web3("http://127.0.0.1:8545"); + const contract = new web3.eth.Contract([]) + contract.setConfig({defaultTransactionType:"0x0"}); + expect(contract.getContextObject().config.defaultTransactionType).toBe("0x0") + expect(web3.getContextObject().config.defaultTransactionType).toBe("0x2"); + }); + + it('should create two contracts with different configs', () => { + const web3 = new Web3('http://127.0.0.1:8545'); + + web3.setConfig({ contractDataInputFill: "data" }); + const c1 = new web3.eth.Contract([], '') + + const c2 = new web3.eth.Contract([], new Web3Context({config: {contractDataInputFill: "input"}})) + + const c3 = new web3.eth.Contract([], {dataInputFill: "input"}) + expect(web3.config.contractDataInputFill).toBe("data") + expect(c1.config.contractDataInputFill).toBe("data") + expect(c2.config.contractDataInputFill).toBe("input"); + expect(c3.config.contractDataInputFill).toBe("input"); + + }); + + it('should create a contract with context and returnFormat properly with different parameters', () => { + const web3 = new Web3('http://127.0.0.1:8545'); + web3.setConfig({ contractDataInputFill: "data" }); + + // create contract with context in second param + const c1 = new web3.eth.Contract([], new Web3Context({config: {contractDataInputFill: "input"}})) + + // create contract with context in third param + const c2 = new web3.eth.Contract([], "", new Web3Context({config: {contractDataInputFill: "both"}})) + + // create contract with context in fourth param + const c3 = new web3.eth.Contract([], "", {gas: "gas"}, new Web3Context({config: {contractDataInputFill: "both"}})) + + expect(c1.config.contractDataInputFill).toBe("input") + expect(c1.getContextObject().config.contractDataInputFill).toBe("input"); + + expect(c2.config.contractDataInputFill).toBe("both") + expect(c2.getContextObject().config.contractDataInputFill).toBe("both"); + + expect(c3.config.contractDataInputFill).toBe("both") + expect(c3.getContextObject().config.contractDataInputFill).toBe("both"); + + // create contract with returnFormat in fourth param + const c4 = new web3.eth.Contract([], "", {gas: "gas"}, ETH_DATA_FORMAT); + + // create contract with returnFormat in fifth param + const c5 = new web3.eth.Contract([], "", {gas: "gas"}, new Web3Context({config: {contractDataInputFill: "data"}}), ETH_DATA_FORMAT) + + expect(c4.config.contractDataInputFill).toBe("data") + expect(c4.getContextObject().config.contractDataInputFill).toBe("data"); + + expect(c5.config.contractDataInputFill).toBe("data") + expect(c5.getContextObject().config.contractDataInputFill).toBe("data"); + }) + + it('should create contracts with different ways to configure', () => { + const web3 = new Web3('http://127.0.0.1:8545'); + web3.setConfig({ contractDataInputFill: "data" }); + + const c1 = new web3.eth.Contract([], '') + + const c2 = new web3.eth.Contract([], new Web3Context({config: {contractDataInputFill: "input"}})) + + const c3 = new web3.eth.Contract([], {dataInputFill: "input"}) + + expect(web3.config.contractDataInputFill).toBe("data") + expect(c1.config.contractDataInputFill).toBe("data") + expect(c2.config.contractDataInputFill).toBe("input"); + expect(c3.config.contractDataInputFill).toBe("input"); + + }); + + it('should populate dataInputFill properly', () => { + const web3 = new Web3("http://127.0.0.1:8545"); + // create a contract with options as second parameter + const c1 = new web3.eth.Contract([], {dataInputFill: "both"}); + expect((c1.config.contractDataInputFill)).toBe("both"); + + // create a contract with options as third parameter + const c2 = new web3.eth.Contract([], "", {dataInputFill: "both"}); + expect((c2.config.contractDataInputFill)).toBe("both"); + + }); + }) + // TODO: finish config unit tests + +});