From 65d7fa4a57de087b88ff1903bfca289a11f0c02b Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 7 Sep 2023 20:14:30 -0400 Subject: [PATCH 01/13] refactor send transaction and add events to receipt object --- packages/web3-eth-contract/src/constants.ts | 26 - packages/web3-eth-contract/src/contract.ts | 29 +- packages/web3-eth-contract/src/encoding.ts | 76 +-- .../web3-eth-contract/src/log_subscription.ts | 15 +- packages/web3-eth-contract/src/types.ts | 94 --- packages/web3-eth-contract/src/utils.ts | 3 +- .../test/integration/contract_deploy.test.ts | 2 +- .../test/integration/contract_events.test.ts | 3 +- .../contract_filter_events.test.ts | 2 +- .../local_account/contract_erc721.test.ts | 3 +- .../contract_overloaded_methods.test.ts | 3 +- .../test/unit/contract.test.ts | 30 +- .../test/unit/encode_event_abi.test.ts | 4 +- packages/web3-eth/src/constants.ts | 10 +- packages/web3-eth/src/decoding.ts | 95 +++ packages/web3-eth/src/index.ts | 2 + packages/web3-eth/src/rpc_method_wrappers.ts | 585 ++++++++++-------- packages/web3-eth/src/types.ts | 29 +- packages/web3-types/src/eth_contract_types.ts | 72 +++ packages/web3-types/src/eth_types.ts | 18 + 20 files changed, 608 insertions(+), 493 deletions(-) delete mode 100644 packages/web3-eth-contract/src/constants.ts create mode 100644 packages/web3-eth/src/decoding.ts diff --git a/packages/web3-eth-contract/src/constants.ts b/packages/web3-eth-contract/src/constants.ts deleted file mode 100644 index a0420e2534a..00000000000 --- a/packages/web3-eth-contract/src/constants.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* -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 { AbiEventFragment } from 'web3-types'; - -export const ALL_EVENTS = 'ALLEVENTS'; -export const ALL_EVENTS_ABI = { - name: ALL_EVENTS, - signature: '', - type: 'event', - inputs: [], -} as AbiEventFragment & { signature: string }; diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts index 65f271c8b38..384a3dc5527 100644 --- a/packages/web3-eth-contract/src/contract.ts +++ b/packages/web3-eth-contract/src/contract.ts @@ -36,6 +36,9 @@ import { NewHeadsSubscription, sendTransaction, SendTransactionEvents, + ALL_EVENTS, + ALL_EVENTS_ABI, + decodeEventABI, } from 'web3-eth'; import { encodeEventSignature, @@ -76,6 +79,9 @@ import { DEFAULT_RETURN_FORMAT, Numbers, Web3ValidationErrorObject, + EventLog, + ContractAbiWithSignature, + ContractOptions, } from 'web3-types'; import { format, isDataFormat, keccak256, toChecksumAddress } from 'web3-utils'; import { @@ -85,14 +91,10 @@ import { ValidationSchemaInput, Web3ValidatorError, } from 'web3-validator'; -import { ALL_EVENTS, ALL_EVENTS_ABI } from './constants.js'; -import { decodeEventABI, decodeMethodReturn, encodeEventABI, encodeMethodABI } from './encoding.js'; +import { decodeMethodReturn, encodeEventABI, encodeMethodABI } from './encoding.js'; import { LogsSubscription } from './log_subscription.js'; import { - ContractAbiWithSignature, ContractEventOptions, - ContractOptions, - EventLog, NonPayableMethodObject, NonPayableTxOptions, PayableMethodObject, @@ -683,7 +685,7 @@ export class Contract returnFormat?: ReturnFormat, ): Promise<(string | EventLog)[]>; public async getPastEvents( - eventName: keyof ContractEvents | 'allEvents', + eventName: keyof ContractEvents | 'allEvents' | 'ALLEVENTS', returnFormat?: ReturnFormat, ): Promise<(string | EventLog)[]>; public async getPastEvents( @@ -691,16 +693,21 @@ export class Contract returnFormat?: ReturnFormat, ): Promise<(string | EventLog)[]>; public async getPastEvents( - eventName: keyof ContractEvents | 'allEvents', + eventName: keyof ContractEvents | 'allEvents' | 'ALLEVENTS', filter: Omit, returnFormat?: ReturnFormat, ): Promise<(string | EventLog)[]>; public async getPastEvents( - param1?: keyof ContractEvents | 'allEvents' | Omit | ReturnFormat, + param1?: + | keyof ContractEvents + | 'allEvents' + | 'ALLEVENTS' + | Omit + | ReturnFormat, param2?: Omit | ReturnFormat, param3?: ReturnFormat, ): Promise<(string | EventLog)[]> { - const eventName = typeof param1 === 'string' ? param1 : 'allEvents'; + const eventName = typeof param1 === 'string' ? param1 : ALL_EVENTS; const options = // eslint-disable-next-line no-nested-ternary @@ -727,11 +734,13 @@ export class Contract if (!abi) { throw new Web3ContractError(`Event ${eventName} not found.`); } + const { fromBlock, toBlock, topics, address } = encodeEventABI( this.options, abi, options ?? {}, ); + const logs = await getLogs(this, { fromBlock, toBlock, topics, address }, returnFormat); const decodedLogs = logs.map(log => typeof log === 'string' @@ -1052,6 +1061,7 @@ export class Contract const transactionToSend = sendTransaction(this, tx, DEFAULT_RETURN_FORMAT, { // TODO Should make this configurable by the user checkRevertBeforeSending: false, + contractAbi: this._jsonInterface, }); // eslint-disable-next-line no-void @@ -1096,6 +1106,7 @@ export class Contract newContract.options.address = receipt.contractAddress; return newContract; }, + contractAbi: this._jsonInterface, // TODO Should make this configurable by the user checkRevertBeforeSending: false, }); diff --git a/packages/web3-eth-contract/src/encoding.ts b/packages/web3-eth-contract/src/encoding.ts index 662b926231c..c64c45beb7d 100644 --- a/packages/web3-eth-contract/src/encoding.ts +++ b/packages/web3-eth-contract/src/encoding.ts @@ -21,18 +21,15 @@ import { AbiConstructorFragment, AbiEventFragment, AbiFunctionFragment, - LogsInput, Filter, HexString, Topic, FMT_NUMBER, FMT_BYTES, - DataFormat, - DEFAULT_RETURN_FORMAT, + ContractOptions, } from 'web3-types'; import { - decodeLog, decodeParameters, encodeEventSignature, encodeFunctionSignature, @@ -42,13 +39,10 @@ import { jsonInterfaceMethodToString, } from 'web3-eth-abi'; -import { blockSchema, logSchema } from 'web3-eth'; +import { blockSchema, decodeEventABI as _decodeEventABI, ALL_EVENTS } from 'web3-eth'; import { Web3ContractError } from 'web3-errors'; -// eslint-disable-next-line import/no-cycle -import { ContractOptions, ContractAbiWithSignature, EventLog } from './types.js'; - type Writeable = { -readonly [P in keyof T]: T[P] }; export const encodeEventABI = ( { address }: ContractOptions, @@ -77,14 +71,14 @@ export const encodeEventABI = ( } else { opts.topics = []; // add event signature - if (event && !event.anonymous && event.name !== 'ALLEVENTS') { + if (event && !event.anonymous && ![ALL_EVENTS, 'allEvents'].includes(event.name)) { opts.topics.push( event.signature ?? encodeEventSignature(jsonInterfaceMethodToString(event)), ); } // add event topics (indexed arguments) - if (event.name !== 'ALLEVENTS' && event.inputs) { + if (![ALL_EVENTS, 'allEvents'].includes(event.name) && event.inputs) { for (const input of event.inputs) { if (!input.indexed) { continue; @@ -119,67 +113,7 @@ export const encodeEventABI = ( return opts; }; -export const decodeEventABI = ( - event: AbiEventFragment & { signature: string }, - data: LogsInput, - jsonInterface: ContractAbiWithSignature, - returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, -): EventLog => { - let modifiedEvent = { ...event }; - - const result = format(logSchema, data, returnFormat); - - // if allEvents get the right event - if (modifiedEvent.name === 'ALLEVENTS') { - const matchedEvent = jsonInterface.find(j => j.signature === data.topics[0]); - if (matchedEvent) { - modifiedEvent = matchedEvent as AbiEventFragment & { signature: string }; - } else { - modifiedEvent = { anonymous: true } as unknown as AbiEventFragment & { - signature: string; - }; - } - } - - // create empty inputs if none are present (e.g. anonymous events on allEvents) - modifiedEvent.inputs = modifiedEvent.inputs ?? event.inputs ?? []; - - // Handle case where an event signature shadows the current ABI with non-identical - // arg indexing. If # of topics doesn't match, event is anon. - if (!modifiedEvent.anonymous) { - let indexedInputs = 0; - (modifiedEvent.inputs ?? []).forEach(input => { - if (input.indexed) { - indexedInputs += 1; - } - }); - - if (indexedInputs > 0 && data?.topics && data?.topics.length !== indexedInputs + 1) { - // checks if event is anonymous - modifiedEvent = { - ...modifiedEvent, - anonymous: true, - inputs: [], - }; - } - } - - const argTopics = modifiedEvent.anonymous ? data.topics : (data.topics ?? []).slice(1); - return { - ...result, - returnValues: decodeLog([...(modifiedEvent.inputs ?? [])], data.data, argTopics), - event: modifiedEvent.name, - signature: - modifiedEvent.anonymous || !data.topics || data.topics.length === 0 || !data.topics[0] - ? undefined - : data.topics[0], - - raw: { - data: data.data, - topics: data.topics, - }, - }; -}; +export const decodeEventABI = _decodeEventABI; export const encodeMethodABI = ( abi: AbiFunctionFragment | AbiConstructorFragment, diff --git a/packages/web3-eth-contract/src/log_subscription.ts b/packages/web3-eth-contract/src/log_subscription.ts index 48504e9a86f..6ace1e17478 100644 --- a/packages/web3-eth-contract/src/log_subscription.ts +++ b/packages/web3-eth-contract/src/log_subscription.ts @@ -15,12 +15,17 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -import { AbiEventFragment, LogsInput, HexString, Topic, DataFormat } from 'web3-types'; +import { + AbiEventFragment, + LogsInput, + HexString, + Topic, + DataFormat, + EventLog, + ContractAbiWithSignature, +} from 'web3-types'; import { Web3RequestManager, Web3Subscription, Web3SubscriptionManager } from 'web3-core'; -// eslint-disable-next-line import/no-cycle -import { decodeEventABI } from './encoding.js'; -// eslint-disable-next-line import/no-cycle -import { EventLog, ContractAbiWithSignature } from './types.js'; +import { decodeEventABI } from 'web3-eth'; /** * LogSubscription to be used to subscribe to events logs. diff --git a/packages/web3-eth-contract/src/types.ts b/packages/web3-eth-contract/src/types.ts index e237fb53707..f044b745596 100644 --- a/packages/web3-eth-contract/src/types.ts +++ b/packages/web3-eth-contract/src/types.ts @@ -29,12 +29,6 @@ import { DataFormat, DEFAULT_RETURN_FORMAT, FormatType, - AbiFragment, - Address, - Bytes, - ContractAbi, - HexString32Bytes, - Uint, } from 'web3-types'; // eslint-disable-next-line import/no-cycle import { LogsSubscription } from './log_subscription.js'; @@ -42,24 +36,6 @@ import { LogsSubscription } from './log_subscription.js'; export type NonPayableTxOptions = NonPayableCallOptions; export type PayableTxOptions = PayableCallOptions; -export type ContractAbiWithSignature = ReadonlyArray; - -export interface EventLog { - readonly event: string; - readonly id?: string; - readonly logIndex?: bigint | number | string; - readonly transactionIndex?: bigint | number | string; - readonly transactionHash?: HexString32Bytes; - readonly blockHash?: HexString32Bytes; - readonly blockNumber?: bigint | number | string; - readonly address: string; - readonly topics: HexString[]; - readonly data: HexString; - readonly raw?: { data: string; topics: unknown[] }; - readonly returnValues: Record; - readonly signature?: HexString; -} - export interface ContractEventOptions { /** * Let you filter events by indexed parameters, e.g. `{filter: {myNumber: [12,13]}}` means all events where `myNumber` is `12` or `13`. @@ -75,76 +51,6 @@ export interface ContractEventOptions { topics?: string[]; } -export interface ContractOptions { - /** - * The maximum gas provided for a transaction (gas limit). - */ - readonly gas?: Uint; - /** - * The gas price in wei to use for transactions. - */ - readonly gasPrice?: Uint; - /** - * The address transactions should be made from. - */ - readonly from?: Address; - /** - * The byte code of the contract. Used when the contract gets {@link Contract.deploy | deployed} - */ - readonly input?: Bytes; - /** - * The {@doclink glossary/json_interface | json interface} object derived from the [ABI](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) of this contract. - * - * Re-setting this will regenerate the methods and events of the contract instance. - * - * ```ts - * myContract.options.jsonInterface; - * > [{ - * "type":"function", - * "name":"foo", - * "inputs": [{"name":"a","type":"uint256"}], - * "outputs": [{"name":"b","type":"address"}], - * "signature": "0x...", - * },{ - * "type":"event", - * "name":"Event", - * "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], - * "signature": "0x...", - * }] - * - * // Set a new ABI interface - * // Note: the "signature" of every function and event's ABI is not needed to be provided when assigning. - * // It will be calculated and set automatically inside the setter. - * myContract.options.jsonInterface = [...]; - * ``` - */ - get jsonInterface(): ContractAbiWithSignature; - set jsonInterface(value: ContractAbi); - - /** - * The address used for this contract instance. All transactions generated by web3.js from this contract will contain this address as the `to`. - * - * The address will be stored in lowercase. - * - * ```ts - * myContract.options.address; - * > '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae' - * - * // set a new address - * myContract.options.address = '0x1234FFDD...'; - * ``` - */ - address?: Address; // All transactions generated by web3.js from this contract will contain this address as the "to". - /** - * The max priority fee per gas to use for transactions. - */ - maxPriorityFeePerGas?: Uint; - /** - * The max fee per gas to use for transactions. - */ - maxFeePerGas?: Uint; -} - export interface NonPayableMethodObject { arguments: Inputs; /** diff --git a/packages/web3-eth-contract/src/utils.ts b/packages/web3-eth-contract/src/utils.ts index 3f46f87d075..5243dfa1b96 100644 --- a/packages/web3-eth-contract/src/utils.ts +++ b/packages/web3-eth-contract/src/utils.ts @@ -26,10 +26,11 @@ import { NonPayableCallOptions, PayableCallOptions, ContractInitOptions, + ContractOptions, } from 'web3-types'; import { isNullish, mergeDeep, toHex } from 'web3-utils'; import { encodeMethodABI } from './encoding.js'; -import { ContractOptions, Web3ContractContext } from './types.js'; +import { Web3ContractContext } from './types.js'; export const getSendTxParams = ({ abi, diff --git a/packages/web3-eth-contract/test/integration/contract_deploy.test.ts b/packages/web3-eth-contract/test/integration/contract_deploy.test.ts index 5c6c666212c..0fbafac4d0f 100644 --- a/packages/web3-eth-contract/test/integration/contract_deploy.test.ts +++ b/packages/web3-eth-contract/test/integration/contract_deploy.test.ts @@ -40,7 +40,7 @@ describe('contract', () => { let pkAccount: { address: string; privateKey: string }; let web3Eth: Web3Eth; - beforeAll(async () => { + beforeAll(() => { web3Eth = new Web3Eth(getSystemTestProvider()); deployOptions = { data: GreeterBytecode, diff --git a/packages/web3-eth-contract/test/integration/contract_events.test.ts b/packages/web3-eth-contract/test/integration/contract_events.test.ts index 0813e16f3f1..b84a875d173 100644 --- a/packages/web3-eth-contract/test/integration/contract_events.test.ts +++ b/packages/web3-eth-contract/test/integration/contract_events.test.ts @@ -15,7 +15,8 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ -import { Contract, EventLog } from '../../src'; +import { EventLog } from 'web3-types'; +import { Contract } from '../../src'; import { BasicAbi, BasicBytecode } from '../shared_fixtures/build/Basic'; import { processAsync } from '../shared_fixtures/utils'; import { diff --git a/packages/web3-eth-contract/test/integration/contract_filter_events.test.ts b/packages/web3-eth-contract/test/integration/contract_filter_events.test.ts index e0c26a3b453..338cd448c12 100644 --- a/packages/web3-eth-contract/test/integration/contract_filter_events.test.ts +++ b/packages/web3-eth-contract/test/integration/contract_filter_events.test.ts @@ -16,6 +16,7 @@ along with web3.js. If not, see . */ import { toBigInt } from 'web3-utils'; +import { EventLog } from 'web3-types'; import { Contract } from '../../src'; import { ERC20TokenAbi, ERC20TokenBytecode } from '../shared_fixtures/build/ERC20Token'; import { BasicAbi, BasicBytecode } from '../shared_fixtures/build/Basic'; @@ -24,7 +25,6 @@ import { createTempAccount, createNewAccount, } from '../fixtures/system_test_utils'; -import { EventLog } from '../../src/types'; const initialSupply = BigInt('5000000000'); diff --git a/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts b/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts index b9ad5b2ac0d..6741b15e034 100644 --- a/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts +++ b/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts @@ -19,7 +19,8 @@ along with web3.js. If not, see . import Web3 from 'web3'; // eslint-disable-next-line import/no-extraneous-dependencies import { Web3Account } from 'web3-eth-accounts'; -import { Contract, EventLog } from '../../../src'; +import { EventLog } from 'web3-types'; +import { Contract } from '../../../src'; import { ERC721TokenAbi, ERC721TokenBytecode } from '../../shared_fixtures/build/ERC721Token'; import { getSystemTestProvider, createLocalAccount } from '../../fixtures/system_test_utils'; import { toUpperCaseHex } from '../../shared_fixtures/utils'; diff --git a/packages/web3-eth-contract/test/integration/local_account/contract_overloaded_methods.test.ts b/packages/web3-eth-contract/test/integration/local_account/contract_overloaded_methods.test.ts index 75e32107d35..3e10c3268e4 100644 --- a/packages/web3-eth-contract/test/integration/local_account/contract_overloaded_methods.test.ts +++ b/packages/web3-eth-contract/test/integration/local_account/contract_overloaded_methods.test.ts @@ -20,7 +20,8 @@ import Web3 from 'web3'; // eslint-disable-next-line import/no-extraneous-dependencies import { Web3Account } from 'web3-eth-accounts'; import { utf8ToHex } from 'web3-utils'; -import { Contract, EventLog } from '../../../src'; +import { EventLog } from 'web3-types'; +import { Contract } from '../../../src'; import { ERC721TokenAbi, ERC721TokenBytecode } from '../../shared_fixtures/build/ERC721Token'; import { getSystemTestProvider, createLocalAccount } from '../../fixtures/system_test_utils'; import { toUpperCaseHex } from '../../shared_fixtures/utils'; diff --git a/packages/web3-eth-contract/test/unit/contract.test.ts b/packages/web3-eth-contract/test/unit/contract.test.ts index 4ee28bc254f..95ada93d41d 100644 --- a/packages/web3-eth-contract/test/unit/contract.test.ts +++ b/packages/web3-eth-contract/test/unit/contract.test.ts @@ -29,7 +29,17 @@ import { erc721Abi } from '../fixtures/erc721'; import { ERC20TokenAbi } from '../shared_fixtures/build/ERC20Token'; import { processAsync } from '../shared_fixtures/utils'; -jest.mock('web3-eth'); +jest.mock('web3-eth', () => { + const allAutoMocked = jest.createMockFromModule('web3-eth'); + const actual = jest.requireActual('web3-eth'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return { + __esModules: true, + // @ts-expect-error ignore allAutoMocked type + ...allAutoMocked, + decodeEventABI: actual.decodeEventABI, + }; +}); describe('Contract', () => { describe('constructor', () => { @@ -560,15 +570,13 @@ describe('Contract', () => { { config: { defaultAccount: '0x00000000219ab540356cBB839Cbe05303d7705Fa' } }, ); - const spyEthCall = jest - .spyOn(eth, 'call') - .mockImplementation(async (_objInstance, _tx) => { - expect(_tx.to).toBe('0x1230B93ffd14F2F022039675fA3fc3A46eE4C701'); - expect(_tx.input).toBe( - '0x095ea7b300000000000000000000000000000000219ab540356cbb839cbe05303d7705fa0000000000000000000000000000000000000000000000000000000000000001', - ); - return '0x00'; - }); + const spyEthCall = jest.spyOn(eth, 'call').mockImplementation((_objInstance, _tx) => { + expect(_tx.to).toBe('0x1230B93ffd14F2F022039675fA3fc3A46eE4C701'); + expect(_tx.input).toBe( + '0x095ea7b300000000000000000000000000000000219ab540356cbb839cbe05303d7705fa0000000000000000000000000000000000000000000000000000000000000001', + ); + return '0x00'; + }); await expect( contract.methods.approve('0x00000000219ab540356cBB839Cbe05303d7705Fa', 1).call(), @@ -824,7 +832,7 @@ describe('Contract', () => { .send(sendOptions); await expect( - processAsync(async (resolve, reject) => { + processAsync((resolve, reject) => { const event = deployedContract.events.allEvents({ fromBlock: 'earliest' }); event.on('error', reject); diff --git a/packages/web3-eth-contract/test/unit/encode_event_abi.test.ts b/packages/web3-eth-contract/test/unit/encode_event_abi.test.ts index 6837cbc204f..c4353806ba9 100644 --- a/packages/web3-eth-contract/test/unit/encode_event_abi.test.ts +++ b/packages/web3-eth-contract/test/unit/encode_event_abi.test.ts @@ -14,8 +14,8 @@ 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 { AbiEventFragment } from 'web3-types'; -import { ContractOptions, encodeEventABI } from '../../src'; +import { AbiEventFragment, ContractOptions } from 'web3-types'; +import { encodeEventABI } from '../../src'; const contractOptions: ContractOptions = { address: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe', diff --git a/packages/web3-eth/src/constants.ts b/packages/web3-eth/src/constants.ts index 428c7204170..13a485719a0 100644 --- a/packages/web3-eth/src/constants.ts +++ b/packages/web3-eth/src/constants.ts @@ -14,6 +14,14 @@ 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 { FMT_BYTES, FMT_NUMBER } from 'web3-types'; +import { AbiEventFragment, FMT_BYTES, FMT_NUMBER } from 'web3-types'; + +export const ALL_EVENTS = 'ALLEVENTS'; +export const ALL_EVENTS_ABI = { + name: ALL_EVENTS, + signature: '', + type: 'event', + inputs: [], +} as AbiEventFragment & { signature: string }; export const NUMBER_DATA_FORMAT = { bytes: FMT_BYTES.HEX, number: FMT_NUMBER.NUMBER } as const; diff --git a/packages/web3-eth/src/decoding.ts b/packages/web3-eth/src/decoding.ts new file mode 100644 index 00000000000..bf4a52a688c --- /dev/null +++ b/packages/web3-eth/src/decoding.ts @@ -0,0 +1,95 @@ +/* +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 { format } from 'web3-utils'; + +import { + AbiEventFragment, + LogsInput, + DataFormat, + DEFAULT_RETURN_FORMAT, + EventLog, + ContractAbiWithSignature, +} from 'web3-types'; + +import { decodeLog } from 'web3-eth-abi'; + +import { logSchema } from './schemas.js'; +import { ALL_EVENTS } from './constants.js'; + +export const decodeEventABI = ( + event: AbiEventFragment & { signature: string }, + data: LogsInput, + jsonInterface: ContractAbiWithSignature, + returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, +): EventLog => { + let modifiedEvent = { ...event }; + + const result = format(logSchema, data, returnFormat); + + // if allEvents get the right event + if ([ALL_EVENTS, 'allEvents'].includes(modifiedEvent.name)) { + const matchedEvent = jsonInterface.find(j => j.signature === data.topics[0]); + if (matchedEvent) { + modifiedEvent = matchedEvent as AbiEventFragment & { signature: string }; + } else { + modifiedEvent = { anonymous: true } as unknown as AbiEventFragment & { + signature: string; + }; + } + } + + // create empty inputs if none are present (e.g. anonymous events on allEvents) + modifiedEvent.inputs = modifiedEvent.inputs ?? event.inputs ?? []; + + // Handle case where an event signature shadows the current ABI with non-identical + // arg indexing. If # of topics doesn't match, event is anon. + if (!modifiedEvent.anonymous) { + let indexedInputs = 0; + (modifiedEvent.inputs ?? []).forEach(input => { + if (input.indexed) { + indexedInputs += 1; + } + }); + + if (indexedInputs > 0 && data?.topics && data?.topics.length !== indexedInputs + 1) { + // checks if event is anonymous + modifiedEvent = { + ...modifiedEvent, + anonymous: true, + inputs: [], + }; + } + } + + const argTopics = modifiedEvent.anonymous ? data.topics : (data.topics ?? []).slice(1); + + return { + ...result, + returnValues: decodeLog([...(modifiedEvent.inputs ?? [])], data.data, argTopics), + event: modifiedEvent.name, + signature: + modifiedEvent.anonymous || !data.topics || data.topics.length === 0 || !data.topics[0] + ? undefined + : data.topics[0], + + raw: { + data: data.data, + topics: data.topics, + }, + }; +}; diff --git a/packages/web3-eth/src/index.ts b/packages/web3-eth/src/index.ts index d5160727be0..d8238892e6e 100644 --- a/packages/web3-eth/src/index.ts +++ b/packages/web3-eth/src/index.ts @@ -53,7 +53,9 @@ import 'setimmediate'; import { Web3Eth } from './web3_eth.js'; export * from './web3_eth.js'; +export * from './decoding.js'; export * from './schemas.js'; +export * from './constants.js'; export * from './types.js'; export * from './validation.js'; export * from './rpc_method_wrappers.js'; diff --git a/packages/web3-eth/src/rpc_method_wrappers.ts b/packages/web3-eth/src/rpc_method_wrappers.ts index e12cc5ed366..8e27a8f433a 100644 --- a/packages/web3-eth/src/rpc_method_wrappers.ts +++ b/packages/web3-eth/src/rpc_method_wrappers.ts @@ -48,8 +48,11 @@ import { TransactionForAccessList, AccessListResult, Eip712TypedData, + LogsInput, + AbiFragment, + TransactionHash, } from 'web3-types'; -import { Web3Context, Web3PromiEvent } from 'web3-core'; +import { Web3Context, Web3EventEmitter, Web3PromiEvent } from 'web3-core'; import { format, hexToBytes, bytesToUint8Array, numberToHex } from 'web3-utils'; import { TransactionFactory } from 'web3-eth-accounts'; import { isBlockTag, isBytes, isNullish, isString } from 'web3-validator'; @@ -89,11 +92,14 @@ import { trySendTransaction } from './utils/try_send_transaction.js'; // eslint-disable-next-line import/no-cycle import { waitForTransactionReceipt } from './utils/wait_for_transaction_receipt.js'; import { watchTransactionForConfirmations } from './utils/watch_transaction_for_confirmations.js'; -import { NUMBER_DATA_FORMAT } from './constants.js'; +import { ALL_EVENTS_ABI, NUMBER_DATA_FORMAT } from './constants.js'; // eslint-disable-next-line import/no-cycle import { getTransactionError } from './utils/get_transaction_error.js'; // eslint-disable-next-line import/no-cycle import { getRevertReason } from './utils/get_revert_reason.js'; +import { decodeEventABI } from './decoding.js'; + +type ContractAbiWithSignature = ReadonlyArray; /** * View additional documentations here: {@link Web3Eth.getProtocolVersion} @@ -479,6 +485,256 @@ export async function getTransactionCount( return format({ format: 'uint' }, response as Numbers, returnFormat); } +class SendTxHelper< + ReturnFormat extends DataFormat, + ResolveType = FormatType, + TxType = + | Transaction + | TransactionWithFromLocalWalletIndex + | TransactionWithToLocalWalletIndex + | TransactionWithFromAndToLocalWalletIndex, +> { + private readonly web3Context: Web3Context; + private readonly promiEvent: Web3PromiEvent< + ResolveType, + SendSignedTransactionEvents | SendTransactionEvents + >; + private readonly options: SendTransactionOptions = { + checkRevertBeforeSending: true, + }; + private readonly returnFormat: ReturnFormat; + private readonly resolve: (data: ResolveType) => void; + private readonly reject: (reason: unknown) => void; + public constructor({ + options, + web3Context, + promiEvent, + reject, + resolve, + returnFormat, + }: { + web3Context: Web3Context; + options: SendTransactionOptions; + promiEvent: Web3PromiEvent< + ResolveType, + SendSignedTransactionEvents | SendTransactionEvents + >; + returnFormat: ReturnFormat; + resolve: (data: ResolveType) => void; + reject: (reason: unknown) => void; + }) { + this.options = options; + this.web3Context = web3Context; + this.promiEvent = promiEvent; + this.reject = reject; + this.resolve = resolve; + this.returnFormat = returnFormat; + } + + public getReceiptWithEvents(data: TransactionReceipt): ResolveType { + const result = { ...(data ?? {}) }; + if (this.options?.contractAbi && result.logs && result.logs.length > 0) { + result.events = {}; + for (const log of result.logs) { + const event = decodeEventABI( + ALL_EVENTS_ABI, + log as LogsInput, + this.options?.contractAbi as ContractAbiWithSignature, + this.returnFormat, + ); + if (event.event) { + result.events[event.event] = event; + } + } + } + + return result as unknown as ResolveType; + } + + public async checkRevertBeforeSending(tx: TransactionCall) { + if (this.options.checkRevertBeforeSending !== false) { + const reason = await getRevertReason(this.web3Context, tx, this.options.contractAbi); + if (reason !== undefined) { + const error = await getTransactionError( + this.web3Context, + tx, + undefined, + undefined, + this.options.contractAbi, + reason, + ); + + if (this.promiEvent.listenerCount('error') > 0) { + this.promiEvent.emit('error', error); + } + + this.reject(error); + } + } + } + + public emitSending(tx: TxType | HexString) { + if (this.promiEvent.listenerCount('sending') > 0) { + this.promiEvent.emit('sending', tx); + } + } + + public async populateGasPrice({ + transactionFormatted, + transaction, + }: { + transactionFormatted: TxType; + transaction: TxType; + }): Promise { + if ( + !this.options?.ignoreGasPricing && + isNullish((transactionFormatted as Transaction).gasPrice) && + (isNullish((transaction as Transaction).maxPriorityFeePerGas) || + isNullish((transaction as Transaction).maxFeePerGas)) + ) { + return { + ...transactionFormatted, + // TODO gasPrice, maxPriorityFeePerGas, maxFeePerGas + // should not be included if undefined, but currently are + ...(await getTransactionGasPricing( + transactionFormatted, + this.web3Context, + ETH_DATA_FORMAT, + )), + }; + } + return transactionFormatted; + } + + public async signAndSend({ + wallet, + tx, + }: { + wallet: Web3BaseWalletAccount | undefined; + tx: TxType; + }) { + if (wallet) { + const signedTransaction = await wallet.signTransaction(tx); + + return trySendTransaction( + this.web3Context, + async (): Promise => + ethRpcMethods.sendRawTransaction( + this.web3Context.requestManager, + signedTransaction.rawTransaction, + ), + signedTransaction.transactionHash, + ); + } + return trySendTransaction( + this.web3Context, + async (): Promise => + ethRpcMethods.sendTransaction( + this.web3Context.requestManager, + tx as Partial, + ), + ); + } + + public emitSent(tx: TxType | HexString) { + if (this.promiEvent.listenerCount('sent') > 0) { + this.promiEvent.emit('sent', tx); + } + } + public emitTransactionHash(hash: string & Uint8Array) { + if (this.promiEvent.listenerCount('transactionHash') > 0) { + this.promiEvent.emit('transactionHash', hash); + } + } + + public emitReceipt(receipt: ResolveType) { + if (this.promiEvent.listenerCount('receipt') > 0) { + ( + this.promiEvent as Web3EventEmitter< + SendTransactionEvents | SendSignedTransactionEvents + > + ).emit( + 'receipt', + // @ts-expect-error unknown type fix + receipt, + ); + } + } + + public async handleError({ error, tx }: { error: unknown; tx: TransactionCall }) { + let _error = error; + + if (_error instanceof ContractExecutionError && this.web3Context.handleRevert) { + _error = await getTransactionError( + this.web3Context, + tx, + undefined, + undefined, + this.options?.contractAbi, + ); + } + + if ( + (_error instanceof InvalidResponseError || + _error instanceof ContractExecutionError || + _error instanceof TransactionRevertWithCustomError || + _error instanceof TransactionRevertedWithoutReasonError || + _error instanceof TransactionRevertInstructionError) && + this.promiEvent.listenerCount('error') > 0 + ) { + this.promiEvent.emit('error', _error); + } + + this.reject(_error); + } + + public emitConfirmation({ + receipt, + transactionHash, + }: { + receipt: ResolveType; + transactionHash: TransactionHash; + }) { + if (this.promiEvent.listenerCount('confirmation') > 0) { + watchTransactionForConfirmations< + ReturnFormat, + SendSignedTransactionEvents | SendTransactionEvents, + ResolveType + >( + this.web3Context, + this.promiEvent, + receipt as unknown as TransactionReceipt, + transactionHash, + this.returnFormat, + ); + } + } + + public async handleResolve({ receipt, tx }: { receipt: ResolveType; tx: TransactionCall }) { + if (this.options?.transactionResolver) { + this.resolve( + this.options?.transactionResolver(receipt as unknown as TransactionReceipt), + ); + } else if ((receipt as unknown as TransactionReceipt).status === BigInt(0)) { + const error = await getTransactionError( + this.web3Context, + tx, + // @ts-expect-error unknown type fix + receipt, + undefined, + this.options?.contractAbi, + ); + + if (this.promiEvent.listenerCount('error') > 0) { + this.promiEvent.emit('error', error); + } + + this.reject(error); + } else { + this.resolve(receipt); + } + } +} /** * View additional documentations here: {@link Web3Eth.sendTransaction} @@ -501,7 +757,20 @@ export function sendTransaction< (resolve, reject) => { setImmediate(() => { (async () => { - let transactionFormatted = formatTransaction( + const sendTxHelper = new SendTxHelper({ + web3Context, + promiEvent, + options, + reject, + resolve, + returnFormat, + }); + + let transactionFormatted: + | Transaction + | TransactionWithFromLocalWalletIndex + | TransactionWithToLocalWalletIndex + | TransactionWithFromAndToLocalWalletIndex = formatTransaction( { ...transaction, from: getTransactionFromOrToAttr('from', web3Context, transaction), @@ -510,99 +779,40 @@ export function sendTransaction< ETH_DATA_FORMAT, ); - if ( - !options?.ignoreGasPricing && - isNullish(transactionFormatted.gasPrice) && - (isNullish(transaction.maxPriorityFeePerGas) || - isNullish(transaction.maxFeePerGas)) - ) { - transactionFormatted = { - ...transactionFormatted, - // TODO gasPrice, maxPriorityFeePerGas, maxFeePerGas - // should not be included if undefined, but currently are - ...(await getTransactionGasPricing( - transactionFormatted, - web3Context, - ETH_DATA_FORMAT, - )), - }; - } + transactionFormatted = await sendTxHelper.populateGasPrice({ + transaction, + transactionFormatted, + }); try { - if (options.checkRevertBeforeSending !== false) { - const reason = await getRevertReason( - web3Context, - transactionFormatted as TransactionCall, - options.contractAbi, - ); - if (reason !== undefined) { - const error = await getTransactionError( - web3Context, - transactionFormatted as TransactionCall, - undefined, - undefined, - options.contractAbi, - reason, - ); - - if (promiEvent.listenerCount('error') > 0) { - promiEvent.emit('error', error); - } - - reject(error); - return; - } - } + await sendTxHelper.checkRevertBeforeSending( + transactionFormatted as TransactionCall, + ); - if (promiEvent.listenerCount('sending') > 0) { - promiEvent.emit('sending', transactionFormatted); - } + sendTxHelper.emitSending(transactionFormatted); - let transactionHash: HexString; let wallet: Web3BaseWalletAccount | undefined; if (web3Context.wallet && !isNullish(transactionFormatted.from)) { - wallet = web3Context.wallet.get(transactionFormatted.from); - } - - if (wallet) { - const signedTransaction = await wallet.signTransaction( - transactionFormatted, - ); - - transactionHash = await trySendTransaction( - web3Context, - async (): Promise => - ethRpcMethods.sendRawTransaction( - web3Context.requestManager, - signedTransaction.rawTransaction, - ), - signedTransaction.transactionHash, - ); - } else { - transactionHash = await trySendTransaction( - web3Context, - async (): Promise => - ethRpcMethods.sendTransaction( - web3Context.requestManager, - transactionFormatted as Partial, - ), + wallet = web3Context.wallet.get( + (transactionFormatted as Transaction).from as string, ); } + const transactionHash: HexString = await sendTxHelper.signAndSend({ + wallet, + tx: transactionFormatted, + }); + const transactionHashFormatted = format( { format: 'bytes32' }, transactionHash as Bytes, returnFormat, ); - - if (promiEvent.listenerCount('sent') > 0) { - promiEvent.emit('sent', transactionFormatted); - } - - if (promiEvent.listenerCount('transactionHash') > 0) { - promiEvent.emit('transactionHash', transactionHashFormatted); - } + sendTxHelper.emitSent(transactionFormatted); + sendTxHelper.emitTransactionHash( + transactionHashFormatted as string & Uint8Array, + ); const transactionReceipt = await waitForTransactionReceipt( web3Context, @@ -610,78 +820,26 @@ export function sendTransaction< returnFormat, ); - const transactionReceiptFormatted = format( - transactionReceiptSchema, - transactionReceipt, - returnFormat, + const transactionReceiptFormatted = sendTxHelper.getReceiptWithEvents( + format(transactionReceiptSchema, transactionReceipt, returnFormat), ); - if (promiEvent.listenerCount('receipt') > 0) { - promiEvent.emit('receipt', transactionReceiptFormatted); - } + sendTxHelper.emitReceipt(transactionReceiptFormatted); - if (options?.transactionResolver) { - resolve( - options?.transactionResolver( - transactionReceiptFormatted, - ) as unknown as ResolveType, - ); - } else if (transactionReceipt.status === BigInt(0)) { - const error = await getTransactionError( - web3Context, - transactionFormatted as TransactionCall, - transactionReceiptFormatted, - undefined, - options?.contractAbi, - ); - - if (promiEvent.listenerCount('error') > 0) { - promiEvent.emit('error', error); - } + await sendTxHelper.handleResolve({ + receipt: transactionReceiptFormatted, + tx: transactionFormatted as TransactionCall, + }); - reject(error); - } else { - resolve(transactionReceiptFormatted as unknown as ResolveType); - } - - if (promiEvent.listenerCount('confirmation') > 0) { - watchTransactionForConfirmations< - ReturnFormat, - SendTransactionEvents, - ResolveType - >( - web3Context, - promiEvent, - transactionReceiptFormatted as TransactionReceipt, - transactionHash, - returnFormat, - ); - } + sendTxHelper.emitConfirmation({ + receipt: transactionReceiptFormatted, + transactionHash, + }); } catch (error) { - let _error = error; - - if (_error instanceof ContractExecutionError && web3Context.handleRevert) { - _error = await getTransactionError( - web3Context, - transactionFormatted as TransactionCall, - undefined, - undefined, - options?.contractAbi, - ); - } - - if ( - (_error instanceof InvalidResponseError || - _error instanceof ContractExecutionError || - _error instanceof TransactionRevertWithCustomError || - _error instanceof TransactionRevertedWithoutReasonError || - _error instanceof TransactionRevertInstructionError) && - promiEvent.listenerCount('error') > 0 - ) { - promiEvent.emit('error', _error); - } - - reject(_error); + await sendTxHelper.handleError({ + error, + tx: transactionFormatted as TransactionCall, + }); } })() as unknown; }); @@ -710,6 +868,14 @@ export function sendSignedTransaction< (resolve, reject) => { setImmediate(() => { (async () => { + const sendTxHelper = new SendTxHelper({ + web3Context, + promiEvent, + options, + reject, + resolve, + returnFormat, + }); // Formatting signedTransaction to be send to RPC endpoint const signedTransactionFormattedHex = format( { format: 'bytes' }, @@ -730,34 +896,11 @@ export function sendSignedTransaction< }; try { - if (options.checkRevertBeforeSending !== false) { - const reason = await getRevertReason( - web3Context, - unSerializedTransactionWithFrom as TransactionCall, - options.contractAbi, - ); - if (reason !== undefined) { - const error = await getTransactionError( - web3Context, - unSerializedTransactionWithFrom as TransactionCall, - undefined, - undefined, - options.contractAbi, - reason, - ); - - if (promiEvent.listenerCount('error') > 0) { - promiEvent.emit('error', error); - } - - reject(error); - return; - } - } + await sendTxHelper.checkRevertBeforeSending( + unSerializedTransactionWithFrom as TransactionCall, + ); - if (promiEvent.listenerCount('sending') > 0) { - promiEvent.emit('sending', signedTransactionFormattedHex); - } + sendTxHelper.emitSending(signedTransactionFormattedHex); const transactionHash = await trySendTransaction( web3Context, @@ -768,9 +911,7 @@ export function sendSignedTransaction< ), ); - if (promiEvent.listenerCount('sent') > 0) { - promiEvent.emit('sent', signedTransactionFormattedHex); - } + sendTxHelper.emitSent(signedTransactionFormattedHex); const transactionHashFormatted = format( { format: 'bytes32' }, @@ -778,9 +919,9 @@ export function sendSignedTransaction< returnFormat, ); - if (promiEvent.listenerCount('transactionHash') > 0) { - promiEvent.emit('transactionHash', transactionHashFormatted); - } + sendTxHelper.emitTransactionHash( + transactionHashFormatted as string & Uint8Array, + ); const transactionReceipt = await waitForTransactionReceipt( web3Context, @@ -788,78 +929,26 @@ export function sendSignedTransaction< returnFormat, ); - const transactionReceiptFormatted = format( - transactionReceiptSchema, - transactionReceipt, - returnFormat, + const transactionReceiptFormatted = sendTxHelper.getReceiptWithEvents( + format(transactionReceiptSchema, transactionReceipt, returnFormat), ); - if (promiEvent.listenerCount('receipt') > 0) { - promiEvent.emit('receipt', transactionReceiptFormatted); - } + sendTxHelper.emitReceipt(transactionReceiptFormatted); - if (options?.transactionResolver) { - resolve( - options?.transactionResolver( - transactionReceiptFormatted, - ) as unknown as ResolveType, - ); - } else if (transactionReceipt.status === BigInt(0)) { - const error = await getTransactionError( - web3Context, - unSerializedTransactionWithFrom as TransactionCall, - transactionReceiptFormatted, - undefined, - options?.contractAbi, - ); - - if (promiEvent.listenerCount('error') > 0) { - promiEvent.emit('error', error); - } - - reject(error); - } else { - resolve(transactionReceiptFormatted as unknown as ResolveType); - } + await sendTxHelper.handleResolve({ + receipt: transactionReceiptFormatted, + tx: unSerializedTransactionWithFrom as TransactionCall, + }); - if (promiEvent.listenerCount('confirmation') > 0) { - watchTransactionForConfirmations< - ReturnFormat, - SendSignedTransactionEvents, - ResolveType - >( - web3Context, - promiEvent, - transactionReceiptFormatted as TransactionReceipt, - transactionHash, - returnFormat, - ); - } + sendTxHelper.emitConfirmation({ + receipt: transactionReceiptFormatted, + transactionHash, + }); } catch (error) { - let _error = error; - - if (_error instanceof ContractExecutionError && web3Context.handleRevert) { - _error = await getTransactionError( - web3Context, - unSerializedTransactionWithFrom as TransactionCall, - undefined, - undefined, - options?.contractAbi, - ); - } - - if ( - (_error instanceof InvalidResponseError || - _error instanceof ContractExecutionError || - _error instanceof TransactionRevertWithCustomError || - _error instanceof TransactionRevertedWithoutReasonError || - _error instanceof TransactionRevertInstructionError) && - promiEvent.listenerCount('error') > 0 - ) { - promiEvent.emit('error', _error); - } - - reject(_error); + await sendTxHelper.handleError({ + error, + tx: unSerializedTransactionWithFrom as TransactionCall, + }); } })() as unknown; }); diff --git a/packages/web3-eth/src/types.ts b/packages/web3-eth/src/types.ts index 103a2829c8a..ae97084ea72 100644 --- a/packages/web3-eth/src/types.ts +++ b/packages/web3-eth/src/types.ts @@ -36,9 +36,9 @@ import { export type InternalTransaction = FormatType; -export type SendTransactionEvents = { - sending: FormatType; - sent: FormatType; +export type SendTransactionEventsBase = { + sending: FormatType; + sent: FormatType; transactionHash: FormatType; receipt: FormatType; confirmation: { @@ -54,23 +54,12 @@ export type SendTransactionEvents = { | ContractExecutionError; }; -export type SendSignedTransactionEvents = { - sending: FormatType; - sent: FormatType; - transactionHash: FormatType; - receipt: FormatType; - confirmation: { - confirmations: FormatType; - receipt: FormatType; - latestBlockHash: FormatType; - }; - error: - | TransactionRevertedWithoutReasonError> - | TransactionRevertInstructionError> - | TransactionRevertWithCustomError> - | InvalidResponseError - | ContractExecutionError; -}; +export type SendTransactionEvents = SendTransactionEventsBase< + ReturnFormat, + Transaction +>; +export type SendSignedTransactionEvents = + SendTransactionEventsBase; export interface SendTransactionOptions { ignoreGasPricing?: boolean; diff --git a/packages/web3-types/src/eth_contract_types.ts b/packages/web3-types/src/eth_contract_types.ts index 5b14b6f0fce..d34da2e78e8 100644 --- a/packages/web3-types/src/eth_contract_types.ts +++ b/packages/web3-types/src/eth_contract_types.ts @@ -19,6 +19,7 @@ import { Address, Uint } from './eth_types.js'; import { SupportedProviders } from './web3_base_provider.js'; import { Bytes, HexString } from './primitives_types.js'; import { EthExecutionAPI } from './apis/eth_execution_api.js'; +import { AbiFragment, ContractAbi } from './eth_abi_types.js'; export interface ContractInitOptions { /** @@ -73,3 +74,74 @@ export interface PayableCallOptions extends NonPayableCallOptions { */ value?: string; } + +export type ContractAbiWithSignature = ReadonlyArray; +export interface ContractOptions { + /** + * The maximum gas provided for a transaction (gas limit). + */ + readonly gas?: Uint; + /** + * The gas price in wei to use for transactions. + */ + readonly gasPrice?: Uint; + /** + * The address transactions should be made from. + */ + readonly from?: Address; + /** + * The byte code of the contract. Used when the contract gets {@link Contract.deploy | deployed} + */ + readonly input?: Bytes; + /** + * The {@doclink glossary/json_interface | json interface} object derived from the [ABI](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) of this contract. + * + * Re-setting this will regenerate the methods and events of the contract instance. + * + * ```ts + * myContract.options.jsonInterface; + * > [{ + * "type":"function", + * "name":"foo", + * "inputs": [{"name":"a","type":"uint256"}], + * "outputs": [{"name":"b","type":"address"}], + * "signature": "0x...", + * },{ + * "type":"event", + * "name":"Event", + * "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], + * "signature": "0x...", + * }] + * + * // Set a new ABI interface + * // Note: the "signature" of every function and event's ABI is not needed to be provided when assigning. + * // It will be calculated and set automatically inside the setter. + * myContract.options.jsonInterface = [...]; + * ``` + */ + get jsonInterface(): ContractAbiWithSignature; + set jsonInterface(value: ContractAbi); + + /** + * The address used for this contract instance. All transactions generated by web3.js from this contract will contain this address as the `to`. + * + * The address will be stored in lowercase. + * + * ```ts + * myContract.options.address; + * > '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae' + * + * // set a new address + * myContract.options.address = '0x1234FFDD...'; + * ``` + */ + address?: Address; // All transactions generated by web3.js from this contract will contain this address as the "to". + /** + * The max priority fee per gas to use for transactions. + */ + maxPriorityFeePerGas?: Uint; + /** + * The max fee per gas to use for transactions. + */ + maxFeePerGas?: Uint; +} diff --git a/packages/web3-types/src/eth_types.ts b/packages/web3-types/src/eth_types.ts index f1fa62dd743..8c59752e20a 100644 --- a/packages/web3-types/src/eth_types.ts +++ b/packages/web3-types/src/eth_types.ts @@ -312,6 +312,23 @@ export interface LogBase { export interface Log extends LogBase { readonly id?: string; } + +export interface EventLog { + readonly event: string; + readonly id?: string; + readonly logIndex?: bigint | number | string; + readonly transactionIndex?: bigint | number | string; + readonly transactionHash?: HexString32Bytes; + readonly blockHash?: HexString32Bytes; + readonly blockNumber?: bigint | number | string; + readonly address: string; + readonly topics: HexString[]; + readonly data: HexString; + readonly raw?: { data: string; topics: unknown[] }; + readonly returnValues: Record; + readonly signature?: HexString; +} + export interface TransactionReceiptBase { readonly transactionHash: hashByteType; readonly transactionIndex: numberType; @@ -328,6 +345,7 @@ export interface TransactionReceiptBase; From 6291c8001f5cf57ca0724954b5c03ac8281ba06f Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 7 Sep 2023 20:52:20 -0400 Subject: [PATCH 02/13] fix unit tests --- packages/web3-eth-contract/test/unit/contract.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web3-eth-contract/test/unit/contract.test.ts b/packages/web3-eth-contract/test/unit/contract.test.ts index 95ada93d41d..7ad3723809c 100644 --- a/packages/web3-eth-contract/test/unit/contract.test.ts +++ b/packages/web3-eth-contract/test/unit/contract.test.ts @@ -570,6 +570,7 @@ describe('Contract', () => { { config: { defaultAccount: '0x00000000219ab540356cBB839Cbe05303d7705Fa' } }, ); + // @ts-expect-error fix-types const spyEthCall = jest.spyOn(eth, 'call').mockImplementation((_objInstance, _tx) => { expect(_tx.to).toBe('0x1230B93ffd14F2F022039675fA3fc3A46eE4C701'); expect(_tx.input).toBe( From 50949865823c6550ea06880cf7bd1022fcd902a8 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Wed, 13 Sep 2023 19:12:07 -0400 Subject: [PATCH 03/13] fix --- .../test/unit/get_encoded_eip712_data.test.ts | 2 +- packages/web3-eth-contract/src/contract.ts | 6 +- packages/web3-eth-contract/src/types.ts | 76 +---- packages/web3-eth/src/index.ts | 2 +- packages/web3-eth/src/rpc_method_wrappers.ts | 279 +--------------- packages/web3-eth/src/utils/decoding.ts | 4 +- packages/web3-eth/src/utils/send_tx_helper.ts | 316 ++++++++++++++++++ packages/web3-types/src/eth_contract_types.ts | 4 + 8 files changed, 332 insertions(+), 357 deletions(-) diff --git a/packages/web3-eth-abi/test/unit/get_encoded_eip712_data.test.ts b/packages/web3-eth-abi/test/unit/get_encoded_eip712_data.test.ts index d40c5f25511..f20d725010f 100644 --- a/packages/web3-eth-abi/test/unit/get_encoded_eip712_data.test.ts +++ b/packages/web3-eth-abi/test/unit/get_encoded_eip712_data.test.ts @@ -24,6 +24,6 @@ describe('getEncodedEip712Data', () => { }); it.each(erroneousTestData)('%s', (_, typedData, hashEncodedData, expectedError) => { - expect(() => getEncodedEip712Data(typedData, hashEncodedData)).toThrowError(expectedError); + expect(() => getEncodedEip712Data(typedData, hashEncodedData)).toThrow(expectedError); }); }); diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts index 0d23b7c6c02..dcf726b07e5 100644 --- a/packages/web3-eth-contract/src/contract.ts +++ b/packages/web3-eth-contract/src/contract.ts @@ -33,12 +33,12 @@ import { call, estimateGas, getLogs, - NewHeadsSubscription, sendTransaction, - SendTransactionEvents, + decodeEventABI, + NewHeadsSubscription, ALL_EVENTS, ALL_EVENTS_ABI, - decodeEventABI, + SendTransactionEvents, } from 'web3-eth'; import { encodeEventSignature, diff --git a/packages/web3-eth-contract/src/types.ts b/packages/web3-eth-contract/src/types.ts index 4f5b15a501e..31670235cb9 100644 --- a/packages/web3-eth-contract/src/types.ts +++ b/packages/web3-eth-contract/src/types.ts @@ -16,7 +16,6 @@ along with web3.js. If not, see . */ import { Web3ContextInitOptions, Web3PromiEvent } from 'web3-core'; -import { NewHeadsSubscription, SendTransactionEvents } from 'web3-eth'; import { AccessListResult, BlockNumberOrTag, @@ -31,6 +30,7 @@ import { FormatType, } from 'web3-types'; // eslint-disable-next-line import/no-cycle +import { NewHeadsSubscription, SendTransactionEvents } from 'web3-eth'; import { LogsSubscription } from './log_subscription.js'; export type NonPayableTxOptions = NonPayableCallOptions; @@ -51,80 +51,6 @@ export interface ContractEventOptions { topics?: string[]; } -export interface ContractOptions { - /** - * The maximum gas provided for a transaction (gas limit). - */ - readonly gas?: Uint; - /** - * The gas price in wei to use for transactions. - */ - readonly gasPrice?: Uint; - /** - * The address transactions should be made from. - */ - readonly from?: Address; - /** - * The byte code of the contract. Used when the contract gets {@link Contract.deploy | deployed} - */ - readonly input?: Bytes; - /** - * The byte code of the contract. Used when the contract gets {@link Contract.deploy | deployed} - */ - readonly data?: Bytes; - /** - * The {@doclink glossary/json_interface | json interface} object derived from the [ABI](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) of this contract. - * - * Re-setting this will regenerate the methods and events of the contract instance. - * - * ```ts - * myContract.options.jsonInterface; - * > [{ - * "type":"function", - * "name":"foo", - * "inputs": [{"name":"a","type":"uint256"}], - * "outputs": [{"name":"b","type":"address"}], - * "signature": "0x...", - * },{ - * "type":"event", - * "name":"Event", - * "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}], - * "signature": "0x...", - * }] - * - * // Set a new ABI interface - * // Note: the "signature" of every function and event's ABI is not needed to be provided when assigning. - * // It will be calculated and set automatically inside the setter. - * myContract.options.jsonInterface = [...]; - * ``` - */ - get jsonInterface(): ContractAbiWithSignature; - set jsonInterface(value: ContractAbi); - - /** - * The address used for this contract instance. All transactions generated by web3.js from this contract will contain this address as the `to`. - * - * The address will be stored in lowercase. - * - * ```ts - * myContract.options.address; - * > '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae' - * - * // set a new address - * myContract.options.address = '0x1234FFDD...'; - * ``` - */ - address?: Address; // All transactions generated by web3.js from this contract will contain this address as the "to". - /** - * The max priority fee per gas to use for transactions. - */ - maxPriorityFeePerGas?: Uint; - /** - * The max fee per gas to use for transactions. - */ - maxFeePerGas?: Uint; -} - export interface NonPayableMethodObject { arguments: Inputs; /** diff --git a/packages/web3-eth/src/index.ts b/packages/web3-eth/src/index.ts index d8238892e6e..d1f1d326832 100644 --- a/packages/web3-eth/src/index.ts +++ b/packages/web3-eth/src/index.ts @@ -53,7 +53,7 @@ import 'setimmediate'; import { Web3Eth } from './web3_eth.js'; export * from './web3_eth.js'; -export * from './decoding.js'; +export * from './utils/decoding.js'; export * from './schemas.js'; export * from './constants.js'; export * from './types.js'; diff --git a/packages/web3-eth/src/rpc_method_wrappers.ts b/packages/web3-eth/src/rpc_method_wrappers.ts index 5e5b870e487..2d01969be90 100644 --- a/packages/web3-eth/src/rpc_method_wrappers.ts +++ b/packages/web3-eth/src/rpc_method_wrappers.ts @@ -23,7 +23,6 @@ import { DataFormat, DEFAULT_RETURN_FORMAT, EthExecutionAPI, - TransactionWithSenderAPI, SignedTransactionInfoAPI, Web3BaseWalletAccount, Address, @@ -48,22 +47,12 @@ import { TransactionForAccessList, AccessListResult, Eip712TypedData, - LogsInput, - AbiFragment, - TransactionHash, } from 'web3-types'; -import { Web3Context, Web3EventEmitter, Web3PromiEvent } from 'web3-core'; +import { Web3Context, Web3PromiEvent } from 'web3-core'; import { format, hexToBytes, bytesToUint8Array, numberToHex } from 'web3-utils'; import { TransactionFactory } from 'web3-eth-accounts'; import { isBlockTag, isBytes, isNullish, isString } from 'web3-validator'; -import { - ContractExecutionError, - InvalidResponseError, - SignatureError, - TransactionRevertedWithoutReasonError, - TransactionRevertInstructionError, - TransactionRevertWithCustomError, -} from 'web3-errors'; +import { SignatureError } from 'web3-errors'; import { ethRpcMethods } from 'web3-rpc-methods'; import { decodeSignedTransaction } from './utils/decode_signed_transaction.js'; @@ -86,20 +75,12 @@ import { import { getTransactionFromOrToAttr } from './utils/transaction_builder.js'; import { formatTransaction } from './utils/format_transaction.js'; // eslint-disable-next-line import/no-cycle -import { getTransactionGasPricing } from './utils/get_transaction_gas_pricing.js'; -// eslint-disable-next-line import/no-cycle import { trySendTransaction } from './utils/try_send_transaction.js'; // eslint-disable-next-line import/no-cycle import { waitForTransactionReceipt } from './utils/wait_for_transaction_receipt.js'; -import { watchTransactionForConfirmations } from './utils/watch_transaction_for_confirmations.js'; -import { ALL_EVENTS_ABI, NUMBER_DATA_FORMAT } from './constants.js'; +import { NUMBER_DATA_FORMAT } from './constants.js'; // eslint-disable-next-line import/no-cycle -import { getTransactionError } from './utils/get_transaction_error.js'; -// eslint-disable-next-line import/no-cycle -import { getRevertReason } from './utils/get_revert_reason.js'; -import { decodeEventABI } from './decoding.js'; - -type ContractAbiWithSignature = ReadonlyArray; +import { SendTxHelper } from './utils/send_tx_helper.js'; /** * View additional documentations here: {@link Web3Eth.getProtocolVersion} @@ -485,257 +466,6 @@ export async function getTransactionCount( return format({ format: 'uint' }, response as Numbers, returnFormat); } -class SendTxHelper< - ReturnFormat extends DataFormat, - ResolveType = FormatType, - TxType = - | Transaction - | TransactionWithFromLocalWalletIndex - | TransactionWithToLocalWalletIndex - | TransactionWithFromAndToLocalWalletIndex, -> { - private readonly web3Context: Web3Context; - private readonly promiEvent: Web3PromiEvent< - ResolveType, - SendSignedTransactionEvents | SendTransactionEvents - >; - private readonly options: SendTransactionOptions = { - checkRevertBeforeSending: true, - }; - private readonly returnFormat: ReturnFormat; - private readonly resolve: (data: ResolveType) => void; - private readonly reject: (reason: unknown) => void; - public constructor({ - options, - web3Context, - promiEvent, - reject, - resolve, - returnFormat, - }: { - web3Context: Web3Context; - options: SendTransactionOptions; - promiEvent: Web3PromiEvent< - ResolveType, - SendSignedTransactionEvents | SendTransactionEvents - >; - returnFormat: ReturnFormat; - resolve: (data: ResolveType) => void; - reject: (reason: unknown) => void; - }) { - this.options = options; - this.web3Context = web3Context; - this.promiEvent = promiEvent; - this.reject = reject; - this.resolve = resolve; - this.returnFormat = returnFormat; - } - - public getReceiptWithEvents(data: TransactionReceipt): ResolveType { - const result = { ...(data ?? {}) }; - if (this.options?.contractAbi && result.logs && result.logs.length > 0) { - result.events = {}; - for (const log of result.logs) { - const event = decodeEventABI( - ALL_EVENTS_ABI, - log as LogsInput, - this.options?.contractAbi as ContractAbiWithSignature, - this.returnFormat, - ); - if (event.event) { - result.events[event.event] = event; - } - } - } - - return result as unknown as ResolveType; - } - - public async checkRevertBeforeSending(tx: TransactionCall) { - if (this.options.checkRevertBeforeSending !== false) { - const reason = await getRevertReason(this.web3Context, tx, this.options.contractAbi); - if (reason !== undefined) { - const error = await getTransactionError( - this.web3Context, - tx, - undefined, - undefined, - this.options.contractAbi, - reason, - ); - - if (this.promiEvent.listenerCount('error') > 0) { - this.promiEvent.emit('error', error); - } - - this.reject(error); - } - } - } - - public emitSending(tx: TxType | HexString) { - if (this.promiEvent.listenerCount('sending') > 0) { - this.promiEvent.emit('sending', tx); - } - } - - public async populateGasPrice({ - transactionFormatted, - transaction, - }: { - transactionFormatted: TxType; - transaction: TxType; - }): Promise { - if ( - !thisюoptions?.ignoreGasPricing && - isNullish(transactionFormatted.gasPrice) && - (isNullish(transaction.maxPriorityFeePerGas) || - isNullish(transaction.maxFeePerGas)) - ) { - transactionFormatted = { - ...transactionFormatted, - // TODO gasPrice, maxPriorityFeePerGas, maxFeePerGas - // should not be included if undefined, but currently are - ...(await getTransactionGasPricing( - transactionFormatted, - web3Context, - ETH_DATA_FORMAT, - )), - }; - } - - return transactionFormatted; - } - - public async signAndSend({ - wallet, - tx, - }: { - wallet: Web3BaseWalletAccount | undefined; - tx: TxType; - }) { - if (wallet) { - const signedTransaction = await wallet.signTransaction(tx); - - return trySendTransaction( - this.web3Context, - async (): Promise => - ethRpcMethods.sendRawTransaction( - this.web3Context.requestManager, - signedTransaction.rawTransaction, - ), - signedTransaction.transactionHash, - ); - } - return trySendTransaction( - this.web3Context, - async (): Promise => - ethRpcMethods.sendTransaction( - this.web3Context.requestManager, - tx as Partial, - ), - ); - } - - public emitSent(tx: TxType | HexString) { - if (this.promiEvent.listenerCount('sent') > 0) { - this.promiEvent.emit('sent', tx); - } - } - public emitTransactionHash(hash: string & Uint8Array) { - if (this.promiEvent.listenerCount('transactionHash') > 0) { - this.promiEvent.emit('transactionHash', hash); - } - } - - public emitReceipt(receipt: ResolveType) { - if (this.promiEvent.listenerCount('receipt') > 0) { - ( - this.promiEvent as Web3EventEmitter< - SendTransactionEvents | SendSignedTransactionEvents - > - ).emit( - 'receipt', - // @ts-expect-error unknown type fix - receipt, - ); - } - } - - public async handleError({ error, tx }: { error: unknown; tx: TransactionCall }) { - let _error = error; - - if (_error instanceof ContractExecutionError && this.web3Context.handleRevert) { - _error = await getTransactionError( - this.web3Context, - tx, - undefined, - undefined, - this.options?.contractAbi, - ); - } - - if ( - (_error instanceof InvalidResponseError || - _error instanceof ContractExecutionError || - _error instanceof TransactionRevertWithCustomError || - _error instanceof TransactionRevertedWithoutReasonError || - _error instanceof TransactionRevertInstructionError) && - this.promiEvent.listenerCount('error') > 0 - ) { - this.promiEvent.emit('error', _error); - } - - this.reject(_error); - } - - public emitConfirmation({ - receipt, - transactionHash, - }: { - receipt: ResolveType; - transactionHash: TransactionHash; - }) { - if (this.promiEvent.listenerCount('confirmation') > 0) { - watchTransactionForConfirmations< - ReturnFormat, - SendSignedTransactionEvents | SendTransactionEvents, - ResolveType - >( - this.web3Context, - this.promiEvent, - receipt as unknown as TransactionReceipt, - transactionHash, - this.returnFormat, - ); - } - } - - public async handleResolve({ receipt, tx }: { receipt: ResolveType; tx: TransactionCall }) { - if (this.options?.transactionResolver) { - this.resolve( - this.options?.transactionResolver(receipt as unknown as TransactionReceipt), - ); - } else if ((receipt as unknown as TransactionReceipt).status === BigInt(0)) { - const error = await getTransactionError( - this.web3Context, - tx, - // @ts-expect-error unknown type fix - receipt, - undefined, - this.options?.contractAbi, - ); - - if (this.promiEvent.listenerCount('error') > 0) { - this.promiEvent.emit('error', error); - } - - this.reject(error); - } else { - this.resolve(receipt); - } - } -} /** * View additional documentations here: {@link Web3Eth.sendTransaction} @@ -780,7 +510,6 @@ export function sendTransaction< ETH_DATA_FORMAT, ); - transactionFormatted = await sendTxHelper.populateGasPrice({ transaction, transactionFormatted, diff --git a/packages/web3-eth/src/utils/decoding.ts b/packages/web3-eth/src/utils/decoding.ts index bf4a52a688c..f4b61c0a5ac 100644 --- a/packages/web3-eth/src/utils/decoding.ts +++ b/packages/web3-eth/src/utils/decoding.ts @@ -28,8 +28,8 @@ import { import { decodeLog } from 'web3-eth-abi'; -import { logSchema } from './schemas.js'; -import { ALL_EVENTS } from './constants.js'; +import { logSchema } from '../schemas.js'; +import { ALL_EVENTS } from '../constants.js'; export const decodeEventABI = ( event: AbiEventFragment & { signature: string }, diff --git a/packages/web3-eth/src/utils/send_tx_helper.ts b/packages/web3-eth/src/utils/send_tx_helper.ts index e69de29bb2d..d639b7d519f 100644 --- a/packages/web3-eth/src/utils/send_tx_helper.ts +++ b/packages/web3-eth/src/utils/send_tx_helper.ts @@ -0,0 +1,316 @@ +/* +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, + FormatType, + DataFormat, + EthExecutionAPI, + TransactionWithSenderAPI, + Web3BaseWalletAccount, + HexString, + TransactionReceipt, + Transaction, + TransactionCall, + TransactionWithFromLocalWalletIndex, + TransactionWithToLocalWalletIndex, + TransactionWithFromAndToLocalWalletIndex, + LogsInput, + AbiFragment, + TransactionHash, +} from 'web3-types'; +import { Web3Context, Web3EventEmitter, Web3PromiEvent } from 'web3-core'; +import { isNullish } from 'web3-validator'; +import { + ContractExecutionError, + InvalidResponseError, + TransactionRevertedWithoutReasonError, + TransactionRevertInstructionError, + TransactionRevertWithCustomError, +} from 'web3-errors'; +import { ethRpcMethods } from 'web3-rpc-methods'; + +import { + SendSignedTransactionEvents, + SendTransactionEvents, + SendTransactionOptions, +} from '../types.js'; +// eslint-disable-next-line import/no-cycle +import { getTransactionGasPricing } from './get_transaction_gas_pricing.js'; +// eslint-disable-next-line import/no-cycle +import { trySendTransaction } from './try_send_transaction.js'; +// eslint-disable-next-line import/no-cycle +import { watchTransactionForConfirmations } from './watch_transaction_for_confirmations.js'; +import { ALL_EVENTS_ABI } from '../constants.js'; +// eslint-disable-next-line import/no-cycle +import { getTransactionError } from './get_transaction_error.js'; +// eslint-disable-next-line import/no-cycle +import { getRevertReason } from './get_revert_reason.js'; +import { decodeEventABI } from './decoding.js'; + +type ContractAbiWithSignature = ReadonlyArray; +export class SendTxHelper< + ReturnFormat extends DataFormat, + ResolveType = FormatType, + TxType = + | Transaction + | TransactionWithFromLocalWalletIndex + | TransactionWithToLocalWalletIndex + | TransactionWithFromAndToLocalWalletIndex, +> { + private readonly web3Context: Web3Context; + private readonly promiEvent: Web3PromiEvent< + ResolveType, + SendSignedTransactionEvents | SendTransactionEvents + >; + private readonly options: SendTransactionOptions = { + checkRevertBeforeSending: true, + }; + private readonly returnFormat: ReturnFormat; + private readonly resolve: (data: ResolveType) => void; + private readonly reject: (reason: unknown) => void; + public constructor({ + options, + web3Context, + promiEvent, + reject, + resolve, + returnFormat, + }: { + web3Context: Web3Context; + options: SendTransactionOptions; + promiEvent: Web3PromiEvent< + ResolveType, + SendSignedTransactionEvents | SendTransactionEvents + >; + returnFormat: ReturnFormat; + resolve: (data: ResolveType) => void; + reject: (reason: unknown) => void; + }) { + this.options = options; + this.web3Context = web3Context; + this.promiEvent = promiEvent; + this.reject = reject; + this.resolve = resolve; + this.returnFormat = returnFormat; + } + + public getReceiptWithEvents(data: TransactionReceipt): ResolveType { + const result = { ...(data ?? {}) }; + if (this.options?.contractAbi && result.logs && result.logs.length > 0) { + result.events = {}; + for (const log of result.logs) { + const event = decodeEventABI( + ALL_EVENTS_ABI, + log as LogsInput, + this.options?.contractAbi as ContractAbiWithSignature, + this.returnFormat, + ); + if (event.event) { + result.events[event.event] = event; + } + } + } + + return result as unknown as ResolveType; + } + + public async checkRevertBeforeSending(tx: TransactionCall) { + if (this.options.checkRevertBeforeSending !== false) { + const reason = await getRevertReason(this.web3Context, tx, this.options.contractAbi); + if (reason !== undefined) { + const error = await getTransactionError( + this.web3Context, + tx, + undefined, + undefined, + this.options.contractAbi, + reason, + ); + + if (this.promiEvent.listenerCount('error') > 0) { + this.promiEvent.emit('error', error); + } + + this.reject(error); + } + } + } + + public emitSending(tx: TxType | HexString) { + if (this.promiEvent.listenerCount('sending') > 0) { + this.promiEvent.emit('sending', tx); + } + } + + public async populateGasPrice({ + transactionFormatted, + transaction, + }: { + transactionFormatted: TxType; + transaction: TxType; + }): Promise { + let result = transactionFormatted; + if ( + !this.options?.ignoreGasPricing && + isNullish((transactionFormatted as Transaction).gasPrice) && + (isNullish((transaction as Transaction).maxPriorityFeePerGas) || + isNullish((transaction as Transaction).maxFeePerGas)) + ) { + result = { + ...transactionFormatted, + // TODO gasPrice, maxPriorityFeePerGas, maxFeePerGas + // should not be included if undefined, but currently are + ...(await getTransactionGasPricing( + transactionFormatted, + this.web3Context, + ETH_DATA_FORMAT, + )), + }; + } + + return result; + } + + public async signAndSend({ + wallet, + tx, + }: { + wallet: Web3BaseWalletAccount | undefined; + tx: TxType; + }) { + if (wallet) { + const signedTransaction = await wallet.signTransaction(tx); + + return trySendTransaction( + this.web3Context, + async (): Promise => + ethRpcMethods.sendRawTransaction( + this.web3Context.requestManager, + signedTransaction.rawTransaction, + ), + signedTransaction.transactionHash, + ); + } + return trySendTransaction( + this.web3Context, + async (): Promise => + ethRpcMethods.sendTransaction( + this.web3Context.requestManager, + tx as Partial, + ), + ); + } + + public emitSent(tx: TxType | HexString) { + if (this.promiEvent.listenerCount('sent') > 0) { + this.promiEvent.emit('sent', tx); + } + } + public emitTransactionHash(hash: string & Uint8Array) { + if (this.promiEvent.listenerCount('transactionHash') > 0) { + this.promiEvent.emit('transactionHash', hash); + } + } + + public emitReceipt(receipt: ResolveType) { + if (this.promiEvent.listenerCount('receipt') > 0) { + ( + this.promiEvent as Web3EventEmitter< + SendTransactionEvents | SendSignedTransactionEvents + > + ).emit( + 'receipt', + // @ts-expect-error unknown type fix + receipt, + ); + } + } + + public async handleError({ error, tx }: { error: unknown; tx: TransactionCall }) { + let _error = error; + + if (_error instanceof ContractExecutionError && this.web3Context.handleRevert) { + _error = await getTransactionError( + this.web3Context, + tx, + undefined, + undefined, + this.options?.contractAbi, + ); + } + + if ( + (_error instanceof InvalidResponseError || + _error instanceof ContractExecutionError || + _error instanceof TransactionRevertWithCustomError || + _error instanceof TransactionRevertedWithoutReasonError || + _error instanceof TransactionRevertInstructionError) && + this.promiEvent.listenerCount('error') > 0 + ) { + this.promiEvent.emit('error', _error); + } + + this.reject(_error); + } + + public emitConfirmation({ + receipt, + transactionHash, + }: { + receipt: ResolveType; + transactionHash: TransactionHash; + }) { + if (this.promiEvent.listenerCount('confirmation') > 0) { + watchTransactionForConfirmations< + ReturnFormat, + SendSignedTransactionEvents | SendTransactionEvents, + ResolveType + >( + this.web3Context, + this.promiEvent, + receipt as unknown as TransactionReceipt, + transactionHash, + this.returnFormat, + ); + } + } + + public async handleResolve({ receipt, tx }: { receipt: ResolveType; tx: TransactionCall }) { + if (this.options?.transactionResolver) { + this.resolve( + this.options?.transactionResolver(receipt as unknown as TransactionReceipt), + ); + } else if ((receipt as unknown as TransactionReceipt).status === BigInt(0)) { + const error = await getTransactionError( + this.web3Context, + tx, + // @ts-expect-error unknown type fix + receipt, + undefined, + this.options?.contractAbi, + ); + + if (this.promiEvent.listenerCount('error') > 0) { + this.promiEvent.emit('error', error); + } + + this.reject(error); + } else { + this.resolve(receipt); + } + } +} diff --git a/packages/web3-types/src/eth_contract_types.ts b/packages/web3-types/src/eth_contract_types.ts index bc20791573d..15d23b36470 100644 --- a/packages/web3-types/src/eth_contract_types.ts +++ b/packages/web3-types/src/eth_contract_types.ts @@ -100,6 +100,10 @@ export interface ContractOptions { * The byte code of the contract. Used when the contract gets {@link Contract.deploy | deployed} */ readonly input?: Bytes; + /** + * The byte code of the contract. Used when the contract gets {@link Contract.deploy | deployed} + */ + readonly data?: Bytes; /** * The {@doclink glossary/json_interface | json interface} object derived from the [ABI](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) of this contract. * From 2a851811b2e10a9d2b2413d4ac5f5a29ec47f71e Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Wed, 13 Sep 2023 19:16:47 -0400 Subject: [PATCH 04/13] move type --- packages/web3-eth-contract/src/constant.ts | 17 +++++++++++++++++ packages/web3-eth-contract/src/utils.ts | 1 + packages/web3-eth/src/utils/send_tx_helper.ts | 3 +-- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 packages/web3-eth-contract/src/constant.ts diff --git a/packages/web3-eth-contract/src/constant.ts b/packages/web3-eth-contract/src/constant.ts new file mode 100644 index 00000000000..6be67ffb159 --- /dev/null +++ b/packages/web3-eth-contract/src/constant.ts @@ -0,0 +1,17 @@ +/* +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 { ALL_EVENTS, ALL_EVENTS_ABI } from 'web3-eth'; diff --git a/packages/web3-eth-contract/src/utils.ts b/packages/web3-eth-contract/src/utils.ts index 2cbe4a49715..85aef3a37e3 100644 --- a/packages/web3-eth-contract/src/utils.ts +++ b/packages/web3-eth-contract/src/utils.ts @@ -32,6 +32,7 @@ import { isNullish, mergeDeep } from 'web3-utils'; import { encodeMethodABI } from './encoding.js'; import { Web3ContractContext } from './types.js'; +export { ContractOptions } from 'web3-types'; const dataInputEncodeMethodHelper = ( txParams: TransactionCall | TransactionForAccessList, abi: AbiFunctionFragment, diff --git a/packages/web3-eth/src/utils/send_tx_helper.ts b/packages/web3-eth/src/utils/send_tx_helper.ts index d639b7d519f..dafd8a78cc7 100644 --- a/packages/web3-eth/src/utils/send_tx_helper.ts +++ b/packages/web3-eth/src/utils/send_tx_helper.ts @@ -29,8 +29,8 @@ import { TransactionWithToLocalWalletIndex, TransactionWithFromAndToLocalWalletIndex, LogsInput, - AbiFragment, TransactionHash, + ContractAbiWithSignature, } from 'web3-types'; import { Web3Context, Web3EventEmitter, Web3PromiEvent } from 'web3-core'; import { isNullish } from 'web3-validator'; @@ -61,7 +61,6 @@ import { getTransactionError } from './get_transaction_error.js'; import { getRevertReason } from './get_revert_reason.js'; import { decodeEventABI } from './decoding.js'; -type ContractAbiWithSignature = ReadonlyArray; export class SendTxHelper< ReturnFormat extends DataFormat, ResolveType = FormatType, From 5c6a05df390f44ab3b7f0eee98b26c2577ae2eaf Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Wed, 13 Sep 2023 19:26:10 -0400 Subject: [PATCH 05/13] fix exports --- packages/web3-eth-contract/src/encoding.ts | 7 +++---- packages/web3-eth-contract/src/types.ts | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/web3-eth-contract/src/encoding.ts b/packages/web3-eth-contract/src/encoding.ts index 225082e8d97..aa06db45ae8 100644 --- a/packages/web3-eth-contract/src/encoding.ts +++ b/packages/web3-eth-contract/src/encoding.ts @@ -39,10 +39,11 @@ import { jsonInterfaceMethodToString, } from 'web3-eth-abi'; -import { blockSchema, decodeEventABI as _decodeEventABI, ALL_EVENTS } from 'web3-eth'; - +import { blockSchema, ALL_EVENTS } from 'web3-eth'; import { Web3ContractError } from 'web3-errors'; +export { decodeEventABI } from 'web3-eth'; + type Writeable = { -readonly [P in keyof T]: T[P] }; export const encodeEventABI = ( { address }: ContractOptions, @@ -113,8 +114,6 @@ export const encodeEventABI = ( return opts; }; -export const decodeEventABI = _decodeEventABI; - export const encodeMethodABI = ( abi: AbiFunctionFragment | AbiConstructorFragment, args: unknown[], diff --git a/packages/web3-eth-contract/src/types.ts b/packages/web3-eth-contract/src/types.ts index 31670235cb9..fc66826b2e6 100644 --- a/packages/web3-eth-contract/src/types.ts +++ b/packages/web3-eth-contract/src/types.ts @@ -29,12 +29,12 @@ import { DEFAULT_RETURN_FORMAT, FormatType, } from 'web3-types'; -// eslint-disable-next-line import/no-cycle import { NewHeadsSubscription, SendTransactionEvents } from 'web3-eth'; import { LogsSubscription } from './log_subscription.js'; export type NonPayableTxOptions = NonPayableCallOptions; export type PayableTxOptions = PayableCallOptions; +export { ContractAbiWithSignature, EventLog, ContractOptions } from 'web3-types'; export interface ContractEventOptions { /** From 6ab3a241b9c3cbdd3a09d3ddcce03200083e44f3 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Wed, 13 Sep 2023 21:05:21 -0400 Subject: [PATCH 06/13] fix integration test --- packages/web3-eth/src/rpc_method_wrappers.ts | 54 ++++++++++--------- packages/web3-eth/src/utils/send_tx_helper.ts | 30 +++-------- 2 files changed, 36 insertions(+), 48 deletions(-) diff --git a/packages/web3-eth/src/rpc_method_wrappers.ts b/packages/web3-eth/src/rpc_method_wrappers.ts index 2d01969be90..542b1b36d88 100644 --- a/packages/web3-eth/src/rpc_method_wrappers.ts +++ b/packages/web3-eth/src/rpc_method_wrappers.ts @@ -492,8 +492,6 @@ export function sendTransaction< web3Context, promiEvent, options, - reject, - resolve, returnFormat, }); @@ -510,12 +508,12 @@ export function sendTransaction< ETH_DATA_FORMAT, ); - transactionFormatted = await sendTxHelper.populateGasPrice({ - transaction, - transactionFormatted, - }); - try { + transactionFormatted = await sendTxHelper.populateGasPrice({ + transaction, + transactionFormatted, + }); + await sendTxHelper.checkRevertBeforeSending( transactionFormatted as TransactionCall, ); @@ -557,20 +555,24 @@ export function sendTransaction< sendTxHelper.emitReceipt(transactionReceiptFormatted); - await sendTxHelper.handleResolve({ - receipt: transactionReceiptFormatted, - tx: transactionFormatted as TransactionCall, - }); + resolve( + await sendTxHelper.handleResolve({ + receipt: transactionReceiptFormatted, + tx: transactionFormatted as TransactionCall, + }), + ); sendTxHelper.emitConfirmation({ receipt: transactionReceiptFormatted, transactionHash, }); } catch (error) { - await sendTxHelper.handleError({ - error, - tx: transactionFormatted as TransactionCall, - }); + reject( + await sendTxHelper.handleError({ + error, + tx: transactionFormatted as TransactionCall, + }), + ); } })() as unknown; }); @@ -603,8 +605,6 @@ export function sendSignedTransaction< web3Context, promiEvent, options, - reject, - resolve, returnFormat, }); // Formatting signedTransaction to be send to RPC endpoint @@ -666,20 +666,24 @@ export function sendSignedTransaction< sendTxHelper.emitReceipt(transactionReceiptFormatted); - await sendTxHelper.handleResolve({ - receipt: transactionReceiptFormatted, - tx: unSerializedTransactionWithFrom as TransactionCall, - }); + resolve( + await sendTxHelper.handleResolve({ + receipt: transactionReceiptFormatted, + tx: unSerializedTransactionWithFrom as TransactionCall, + }), + ); sendTxHelper.emitConfirmation({ receipt: transactionReceiptFormatted, transactionHash, }); } catch (error) { - await sendTxHelper.handleError({ - error, - tx: unSerializedTransactionWithFrom as TransactionCall, - }); + reject( + await sendTxHelper.handleError({ + error, + tx: unSerializedTransactionWithFrom as TransactionCall, + }), + ); } })() as unknown; }); diff --git a/packages/web3-eth/src/utils/send_tx_helper.ts b/packages/web3-eth/src/utils/send_tx_helper.ts index dafd8a78cc7..3cc767449b6 100644 --- a/packages/web3-eth/src/utils/send_tx_helper.ts +++ b/packages/web3-eth/src/utils/send_tx_helper.ts @@ -79,14 +79,10 @@ export class SendTxHelper< checkRevertBeforeSending: true, }; private readonly returnFormat: ReturnFormat; - private readonly resolve: (data: ResolveType) => void; - private readonly reject: (reason: unknown) => void; public constructor({ options, web3Context, promiEvent, - reject, - resolve, returnFormat, }: { web3Context: Web3Context; @@ -96,14 +92,10 @@ export class SendTxHelper< SendSignedTransactionEvents | SendTransactionEvents >; returnFormat: ReturnFormat; - resolve: (data: ResolveType) => void; - reject: (reason: unknown) => void; }) { this.options = options; this.web3Context = web3Context; this.promiEvent = promiEvent; - this.reject = reject; - this.resolve = resolve; this.returnFormat = returnFormat; } @@ -131,7 +123,7 @@ export class SendTxHelper< if (this.options.checkRevertBeforeSending !== false) { const reason = await getRevertReason(this.web3Context, tx, this.options.contractAbi); if (reason !== undefined) { - const error = await getTransactionError( + throw await getTransactionError( this.web3Context, tx, undefined, @@ -139,12 +131,6 @@ export class SendTxHelper< this.options.contractAbi, reason, ); - - if (this.promiEvent.listenerCount('error') > 0) { - this.promiEvent.emit('error', error); - } - - this.reject(error); } } } @@ -263,7 +249,7 @@ export class SendTxHelper< this.promiEvent.emit('error', _error); } - this.reject(_error); + return _error; } public emitConfirmation({ @@ -290,10 +276,9 @@ export class SendTxHelper< public async handleResolve({ receipt, tx }: { receipt: ResolveType; tx: TransactionCall }) { if (this.options?.transactionResolver) { - this.resolve( - this.options?.transactionResolver(receipt as unknown as TransactionReceipt), - ); - } else if ((receipt as unknown as TransactionReceipt).status === BigInt(0)) { + return this.options?.transactionResolver(receipt as unknown as TransactionReceipt); + } + if ((receipt as unknown as TransactionReceipt).status === BigInt(0)) { const error = await getTransactionError( this.web3Context, tx, @@ -302,14 +287,13 @@ export class SendTxHelper< undefined, this.options?.contractAbi, ); - if (this.promiEvent.listenerCount('error') > 0) { this.promiEvent.emit('error', error); } - this.reject(error); + throw error; } else { - this.resolve(receipt); + return receipt; } } } From c6295df6bf0839900337f5333409be7742011553 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 14 Sep 2023 17:44:52 -0400 Subject: [PATCH 07/13] add change logs --- CHANGELOG.md | 24 ++++++++++++++++++++++++ packages/web3-eth-contract/src/utils.ts | 1 - packages/web3-eth/CHANGELOG.md | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 076e7024189..03c48c699a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1994,3 +1994,27 @@ If there are any bugs, improvements, optimizations or any new feature proposal f - Dependencies updated ## [Unreleased] + +### Added + +#### web3-eth + +- Added to `Web3Config` property `contractDataInputFill` allowing users to have the choice using property `data`, `input` or `both` for contract methods to be sent to the RPC provider when creating contracts. (#6377) (#6400) +- Added `ALL_EVENTS` and `ALL_EVENTS_ABI` constants, `SendTransactionEventsBase` type, `decodeEventABI` method (#6410) + +#### web3-eth-contract + +- Added `dataInputFill` as a ContractInitOption, allowing users to have the choice using property `data`, `input` or `both` for contract methods to be sent to the RPC provider. (#6355) +- Added to `Web3Config` property `contractDataInputFill` allowing users to have the choice using property `data`, `input` or `both` for contract methods to be sent to the RPC provider when creating contracts. (#6377) + +#### web3-types + +- add `asEIP1193Provider` to `Web3BaseProvider` so every inherited class can have the returned value of `request` method, fully compatible with EIP-1193. (#6407) + +### Fixed + +#### web3-eth-accounts + +- Fixed "The `r` and `s` returned by `sign` to does not always consist of 64 characters" (#6411) + +#### web3-eth-contract diff --git a/packages/web3-eth-contract/src/utils.ts b/packages/web3-eth-contract/src/utils.ts index 85aef3a37e3..2cbe4a49715 100644 --- a/packages/web3-eth-contract/src/utils.ts +++ b/packages/web3-eth-contract/src/utils.ts @@ -32,7 +32,6 @@ import { isNullish, mergeDeep } from 'web3-utils'; import { encodeMethodABI } from './encoding.js'; import { Web3ContractContext } from './types.js'; -export { ContractOptions } from 'web3-types'; const dataInputEncodeMethodHelper = ( txParams: TransactionCall | TransactionForAccessList, abi: AbiFunctionFragment, diff --git a/packages/web3-eth/CHANGELOG.md b/packages/web3-eth/CHANGELOG.md index b30e008202c..a5c0af9bfe7 100644 --- a/packages/web3-eth/CHANGELOG.md +++ b/packages/web3-eth/CHANGELOG.md @@ -190,3 +190,4 @@ Documentation: ### Added - Added to `Web3Config` property `contractDataInputFill` allowing users to have the choice using property `data`, `input` or `both` for contract methods to be sent to the RPC provider when creating contracts. (#6377) (#6400) +- Added `ALL_EVENTS` and `ALL_EVENTS_ABI` constants, `SendTransactionEventsBase` type, `decodeEventABI` method (#6410) From 0669fc0ac40061622c2d5087553cd813ce006217 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Fri, 15 Sep 2023 16:08:51 -0400 Subject: [PATCH 08/13] add decoding test --- .../test/fixtures/decoding.ts} | 129 +++++++++++++++--- .../test/unit/decoding.test.ts} | 14 +- 2 files changed, 123 insertions(+), 20 deletions(-) rename packages/{web3-eth-contract/test/fixtures/encoding.ts => web3-eth/test/fixtures/decoding.ts} (60%) rename packages/{web3-eth-contract/test/unit/encoding.ts => web3-eth/test/unit/decoding.test.ts} (73%) diff --git a/packages/web3-eth-contract/test/fixtures/encoding.ts b/packages/web3-eth/test/fixtures/decoding.ts similarity index 60% rename from packages/web3-eth-contract/test/fixtures/encoding.ts rename to packages/web3-eth/test/fixtures/decoding.ts index 7bcd63d9873..e548f33acc6 100644 --- a/packages/web3-eth-contract/test/fixtures/encoding.ts +++ b/packages/web3-eth/test/fixtures/decoding.ts @@ -17,6 +17,38 @@ along with web3.js. If not, see . import { AbiEventFragment, LogsInput } from 'web3-types'; export const decodeEventABIData: [AbiEventFragment & { signature: string }, LogsInput, any][] = [ + [ + { + // unindexed event with some indexed + type: 'event', + inputs: [ + { name: 'a', type: 'string', indexed: true }, + { name: 'b', type: 'uint', indexed: false }, + { name: 'a', type: 'string', indexed: false }, + ], + name: 'EventNotAnonymous', + signature: '0x7bbee60e68739c7319c204bae2f54caab4114edf476c64bfc5be98af25f446f5', + }, + { + address: '', + topics: ['0x7bbee60e68739c7319c204bae2f54caab4114edf476c64bfc5be98af25f446f5'], + data: '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000', + }, + { + address: '', + topics: ['0x7bbee60e68739c7319c204bae2f54caab4114edf476c64bfc5be98af25f446f5'], + data: '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000', + returnValues: { + __length__: 0, + }, + event: 'EventNotAnonymous', + signature: undefined, + raw: { + data: '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000', + topics: ['0x7bbee60e68739c7319c204bae2f54caab4114edf476c64bfc5be98af25f446f5'], + }, + }, + ], [ { // unindexed event @@ -38,8 +70,14 @@ export const decodeEventABIData: [AbiEventFragment & { signature: string }, Logs address: '', topics: ['0x7bbee60e68739c7319c204bae2f54caab4114edf476c64bfc5be98af25f446f5'], data: '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000', - id: undefined, - returnValues: { '0': 'a', '1': '24', '2': 'c', __length__: 3, a: 'c', b: '24' }, + returnValues: { + '0': 'a', + '1': BigInt(24), + '2': 'c', + __length__: 3, + a: 'c', + b: BigInt(24), + }, event: 'EventNotAnonymous', signature: '0x7bbee60e68739c7319c204bae2f54caab4114edf476c64bfc5be98af25f446f5', raw: { @@ -48,6 +86,33 @@ export const decodeEventABIData: [AbiEventFragment & { signature: string }, Logs }, }, ], + [ + { + // all events + type: 'event', + name: 'allEvents', + signature: '', + }, + { + address: '', + topics: ['0x7bbee60e68739c7319c204bae2f54caab4114edf476c64bfc5be98af25f446f5'], + data: '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000', + }, + { + address: '', + topics: ['0x7bbee60e68739c7319c204bae2f54caab4114edf476c64bfc5be98af25f446f5'], + data: '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000', + returnValues: { + __length__: 0, + }, + event: undefined, + signature: undefined, + raw: { + data: '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016300000000000000000000000000000000000000000000000000000000000000', + topics: ['0x7bbee60e68739c7319c204bae2f54caab4114edf476c64bfc5be98af25f446f5'], + }, + }, + ], [ { // indexed event @@ -70,13 +135,47 @@ export const decodeEventABIData: [AbiEventFragment & { signature: string }, Logs '0xdd64d7f331676de21d95ea9f7eb8585b688f72afec29a51ff4502fd5a6ae19e7', '0x000000000000000000000000000000000000000000000000000000000000007b', ], - data: '0x', - id: undefined, - returnValues: { '0': '123', __length__: 1, a: '123' }, + data: '', + returnValues: { '0': BigInt(123), __length__: 1, a: BigInt(123) }, event: 'EventIndexed', signature: '0xdd64d7f331676de21d95ea9f7eb8585b688f72afec29a51ff4502fd5a6ae19e7', raw: { - data: '0x', + data: '', + topics: [ + '0xdd64d7f331676de21d95ea9f7eb8585b688f72afec29a51ff4502fd5a6ae19e7', + '0x000000000000000000000000000000000000000000000000000000000000007b', + ], + }, + }, + ], + [ + { + // indexed all events + type: 'event', + inputs: [{ name: 'a', type: 'uint256', indexed: true }], + name: 'allEvents', + signature: '0xdd64d7f331676de21d95ea9f7eb8585b688f72afec29a51ff4502fd5a6ae19e7', + }, + { + address: '', + topics: [ + '0xdd64d7f331676de21d95ea9f7eb8585b688f72afec29a51ff4502fd5a6ae19e7', + '0x000000000000000000000000000000000000000000000000000000000000007b', + ], + data: '', + }, + { + address: '', + topics: [ + '0xdd64d7f331676de21d95ea9f7eb8585b688f72afec29a51ff4502fd5a6ae19e7', + '0x000000000000000000000000000000000000000000000000000000000000007b', + ], + data: '', + returnValues: { '0': BigInt(123), __length__: 1, a: BigInt(123) }, + event: undefined, + signature: '0xdd64d7f331676de21d95ea9f7eb8585b688f72afec29a51ff4502fd5a6ae19e7', + raw: { + data: '', topics: [ '0xdd64d7f331676de21d95ea9f7eb8585b688f72afec29a51ff4502fd5a6ae19e7', '0x000000000000000000000000000000000000000000000000000000000000007b', @@ -106,15 +205,14 @@ export const decodeEventABIData: [AbiEventFragment & { signature: string }, Logs address: '', topics: [], data: '0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000007d0000000000000000000000000000000000000000000000000000000000000002307800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016200000000000000000000000000000000000000000000000000000000000000', - id: undefined, returnValues: { '0': '0x', - '1': '12', - '2': '192', + '1': BigInt(12), + '2': BigInt(192), __length__: 3, a: '0x', - b: '12', - c: '192', + b: BigInt(12), + c: BigInt(192), }, event: '', signature: undefined, @@ -145,15 +243,14 @@ export const decodeEventABIData: [AbiEventFragment & { signature: string }, Logs address: '', topics: [], data: '0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000007d0000000000000000000000000000000000000000000000000000000000000002307800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016200000000000000000000000000000000000000000000000000000000000000', - id: undefined, returnValues: { '0': '0x', - '1': '12', - '2': '192', + '1': BigInt(12), + '2': BigInt(192), __length__: 3, a: '0x', - b: '12', - c: '192', + b: BigInt(12), + c: BigInt(192), }, event: '', signature: undefined, diff --git a/packages/web3-eth-contract/test/unit/encoding.ts b/packages/web3-eth/test/unit/decoding.test.ts similarity index 73% rename from packages/web3-eth-contract/test/unit/encoding.ts rename to packages/web3-eth/test/unit/decoding.test.ts index 293e55cccfc..94627d84575 100644 --- a/packages/web3-eth-contract/test/unit/encoding.ts +++ b/packages/web3-eth/test/unit/decoding.test.ts @@ -15,16 +15,22 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ import { AbiEventFragment, LogsInput } from 'web3-types'; -import { decodeEventABI } from '../../src/encoding'; -import { decodeEventABIData } from '../fixtures/encoding'; +import { decodeEventABI } from '../../src'; +import { decodeEventABIData } from '../fixtures/decoding'; -describe('encoding decoding functions', () => { +describe('decoding functions', () => { describe('decode', () => { describe('decodeEventABI', () => { it.each(decodeEventABIData)( '%s', (event: AbiEventFragment & { signature: string }, inputs: LogsInput, output) => { - expect(decodeEventABI(event, inputs, [])).toBe(output); + expect( + decodeEventABI(event, inputs, [ + { signature: event.signature } as unknown as AbiEventFragment & { + signature: string; + }, + ]), + ).toStrictEqual(output); }, ); }); From fbc4ebc2f97c2309ab63dad7449f3d498e571537 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 18 Sep 2023 10:21:23 -0400 Subject: [PATCH 09/13] cover send_tx_helper --- packages/web3-eth/test/fixtures/erc20.ts | 175 ++++++++++++ .../web3-eth/test/unit/send_tx_helper.test.ts | 248 ++++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100644 packages/web3-eth/test/fixtures/erc20.ts create mode 100644 packages/web3-eth/test/unit/send_tx_helper.test.ts diff --git a/packages/web3-eth/test/fixtures/erc20.ts b/packages/web3-eth/test/fixtures/erc20.ts new file mode 100644 index 00000000000..17ce176c7d8 --- /dev/null +++ b/packages/web3-eth/test/fixtures/erc20.ts @@ -0,0 +1,175 @@ +/* +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 ERC20TokenAbi = [ + { + inputs: [{ internalType: 'uint256', name: 'initialSupply', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'constructor', + signature: '', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, + { indexed: true, internalType: 'address', name: 'spender', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, + ], + name: 'Approval', + type: 'event', + signature: '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, + ], + name: 'Transfer', + type: 'event', + signature: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + }, + { + inputs: [ + { internalType: 'address', name: 'owner', type: 'address' }, + { internalType: 'address', name: 'spender', type: 'address' }, + ], + name: 'allowance', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + signature: '0xdd62ed3e', + constant: true, + payable: false, + }, + { + inputs: [ + { internalType: 'address', name: 'spender', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'approve', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + signature: '0x095ea7b3', + constant: false, + payable: false, + }, + { + inputs: [{ internalType: 'address', name: 'account', type: 'address' }], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + signature: '0x70a08231', + constant: true, + payable: false, + }, + { + inputs: [], + name: 'decimals', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + signature: '0x313ce567', + constant: true, + payable: false, + }, + { + inputs: [ + { internalType: 'address', name: 'spender', type: 'address' }, + { internalType: 'uint256', name: 'subtractedValue', type: 'uint256' }, + ], + name: 'decreaseAllowance', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + signature: '0xa457c2d7', + constant: false, + payable: false, + }, + { + inputs: [ + { internalType: 'address', name: 'spender', type: 'address' }, + { internalType: 'uint256', name: 'addedValue', type: 'uint256' }, + ], + name: 'increaseAllowance', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + signature: '0x39509351', + constant: false, + payable: false, + }, + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + signature: '0x06fdde03', + constant: true, + payable: false, + }, + { + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + signature: '0x95d89b41', + constant: true, + payable: false, + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + signature: '0x18160ddd', + constant: true, + payable: false, + }, + { + inputs: [ + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'transfer', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + signature: '0xa9059cbb', + constant: false, + payable: false, + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + signature: '0x23b872dd', + constant: false, + payable: false, + }, +] as const; diff --git a/packages/web3-eth/test/unit/send_tx_helper.test.ts b/packages/web3-eth/test/unit/send_tx_helper.test.ts new file mode 100644 index 00000000000..55a482395b8 --- /dev/null +++ b/packages/web3-eth/test/unit/send_tx_helper.test.ts @@ -0,0 +1,248 @@ +/* +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 { + DataFormat, + DEFAULT_RETURN_FORMAT, + EthExecutionAPI, + JsonRpcResponse, + TransactionReceipt, + Web3BaseWalletAccount, +} from 'web3-types'; +import { Web3Context, Web3EventMap, Web3PromiEvent } from 'web3-core'; +import { + ContractExecutionError, + InvalidResponseError, + TransactionRevertInstructionError, +} from 'web3-errors'; +import { SendTxHelper } from '../../src/utils/send_tx_helper'; +import { getTransactionError } from '../../src/utils/get_transaction_error'; +import { getTransactionGasPricing } from '../../src/utils/get_transaction_gas_pricing'; +import { getRevertReason } from '../../src/utils/get_revert_reason'; +import { trySendTransaction } from '../../src/utils/try_send_transaction'; +import { ERC20TokenAbi } from '../fixtures/erc20'; +import { SendSignedTransactionEvents, SendTransactionEvents } from '../../src'; + +const utils = { + getTransactionError, + getRevertReason, + trySendTransaction, + getTransactionGasPricing, +}; +jest.mock('../../src/utils/get_transaction_gas_pricing'); +jest.mock('../../src/utils/try_send_transaction'); +jest.mock('../../src/utils/get_transaction_error'); +jest.mock('../../src/utils/get_revert_reason'); + +type PromiEvent = Web3PromiEvent< + TransactionReceipt, + SendSignedTransactionEvents | SendTransactionEvents +>; +const receipt = { + transactionHash: '0x559e12c4d679f66ff234ad2075a0953793692bdd3a9d9f12def5edc5d7cc2eec', + transactionIndex: BigInt(0), + blockNumber: BigInt(38), + blockHash: '0xc238b3b27edd12846afc824e4f36ebd7e6dcf35914af631f181ebc05127dd553', + from: '0x53a179dfe130c7b4054f7e6e7f1928777d7e7bbd', + to: '0xead2356c468ce5443bd7cbb2caaeb48266b7f31f', + cumulativeGasUsed: BigInt(47521), + gasUsed: BigInt(47521), + logs: [ + { + address: '0xead2356c468ce5443bd7cbb2caaeb48266b7f31f', + blockHash: '0xc238b3b27edd12846afc824e4f36ebd7e6dcf35914af631f181ebc05127dd553', + blockNumber: BigInt(38), + data: '0x000000000000000000000000000000000000000000000000000000000000000a', + logIndex: BigInt(0), + removed: false, + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x00000000000000000000000051623651024932936d00d36a93594db5684fbbb3', + '0x00000000000000000000000003095dc4857bb26f3a4550c5651df8b7f6b6b1ef', + ], + transactionHash: '0x559e12c4d679f66ff234ad2075a0953793692bdd3a9d9f12def5edc5d7cc2eec', + transactionIndex: BigInt(0), + }, + ], + logsBloom: + '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000800000000000008000000000000000000020000001000000002000000000000000000000000000200000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000008000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000', + status: BigInt(1), + effectiveGasPrice: BigInt(2506532645), + type: BigInt(2), +}; + +describe('sendTxHelper class', () => { + let sendTxHelper: SendTxHelper; + let promiEvent: Web3PromiEvent; + let web3Context: Web3Context; + beforeAll(() => { + web3Context = new Web3Context(); + promiEvent = new Web3PromiEvent(resolve => { + resolve({} as unknown as TransactionReceipt); + }); + sendTxHelper = new SendTxHelper({ + web3Context, + promiEvent: promiEvent as PromiEvent, + options: { + contractAbi: ERC20TokenAbi, + }, + returnFormat: DEFAULT_RETURN_FORMAT, + }); + }); + it('constructor', () => { + expect(sendTxHelper).toBeDefined(); + // @ts-expect-error get private property + expect(sendTxHelper.promiEvent).toBe(promiEvent); + // @ts-expect-error get private property + expect(sendTxHelper.web3Context).toBe(web3Context); + // @ts-expect-error get private property + expect(sendTxHelper.returnFormat).toBe(DEFAULT_RETURN_FORMAT); + }); + it('getReceiptWithEvents', () => { + const res = sendTxHelper.getReceiptWithEvents(receipt as unknown as TransactionReceipt); + expect(res?.events?.Transfer.address).toBeDefined(); + }); + it('emit sending', async () => { + const f = jest.fn(); + await promiEvent.on('sending', f); + sendTxHelper.emitSending(receipt); + expect(f).toHaveBeenCalledWith(receipt); + promiEvent.off('sending', f); + }); + it('emit emitSent', async () => { + const f = jest.fn(); + await promiEvent.on('sent', f); + sendTxHelper.emitSent(receipt); + expect(f).toHaveBeenCalledWith(receipt); + promiEvent.off('sent', f); + }); + it('emit emitTransactionHash', async () => { + const f = jest.fn(); + await promiEvent.on('transactionHash', f); + sendTxHelper.emitTransactionHash(receipt.transactionHash as string & Uint8Array); + expect(f).toHaveBeenCalledWith(receipt.transactionHash); + promiEvent.off('transactionHash', f); + }); + it('emit emitReceipt', async () => { + const f = jest.fn(); + await promiEvent.on('receipt', f); + sendTxHelper.emitReceipt(receipt as TransactionReceipt); + expect(f).toHaveBeenCalledWith(receipt); + promiEvent.off('receipt', f); + }); + it('emit handleError', async () => { + const f = jest.fn(); + await promiEvent.on('error', f); + const error = new InvalidResponseError({} as JsonRpcResponse); + await sendTxHelper.handleError({ error, tx: receipt }); + expect(f).toHaveBeenCalledWith(error); + promiEvent.off('error', f); + }); + it('emit handleError with handleRevert', async () => { + const error = new ContractExecutionError({ code: 1, message: 'error' }); + web3Context.handleRevert = true; + jest.spyOn(utils, 'getTransactionError').mockResolvedValue( + error as unknown as TransactionRevertInstructionError, + ); + await sendTxHelper.handleError({ error, tx: receipt }); + expect(utils.getTransactionError).toHaveBeenCalled(); + }); + it('emit handleResolve', async () => { + const f = jest.fn(); + const error = new TransactionRevertInstructionError('error'); + jest.spyOn(utils, 'getTransactionError').mockResolvedValue(error); + await promiEvent.on('error', f); + + await expect(async () => { + await sendTxHelper.handleResolve({ + receipt: { ...receipt, status: BigInt(0) } as TransactionReceipt, + tx: receipt, + }); + expect(utils.getTransactionError).toHaveBeenCalled(); + expect(f).toHaveBeenCalledWith(error); + promiEvent.off('error', f); + }).rejects.toThrow(); + }); + it('emit checkRevertBeforeSending', async () => { + const _sendTxHelper = new SendTxHelper({ + web3Context, + promiEvent: promiEvent as PromiEvent, + options: { + checkRevertBeforeSending: true, + }, + returnFormat: DEFAULT_RETURN_FORMAT, + }); + const error = new TransactionRevertInstructionError('error'); + jest.spyOn(utils, 'getRevertReason').mockResolvedValue(error); + await expect(_sendTxHelper.checkRevertBeforeSending(receipt)).rejects.toThrow(); + expect(utils.getRevertReason).toHaveBeenCalled(); + }); + it('emit handleResolve with transactionResolver', async () => { + const f = jest.fn(); + + const _sendTxHelper = new SendTxHelper({ + web3Context, + promiEvent: promiEvent as PromiEvent, + options: { + transactionResolver: f, + }, + returnFormat: DEFAULT_RETURN_FORMAT, + }); + + await _sendTxHelper.handleResolve({ receipt: receipt as TransactionReceipt, tx: receipt }); + expect(f).toHaveBeenCalledWith(receipt); + }); + it('emit populateGasPrice', async () => { + const _sendTxHelper = new SendTxHelper({ + web3Context, + promiEvent: promiEvent as PromiEvent, + options: { + ignoreGasPricing: false, + }, + returnFormat: DEFAULT_RETURN_FORMAT, + }); + const receiptWithoutGas = { + ...receipt, + gasPrice: undefined, + maxPriorityFeePerGas: undefined, + maxFeePerGas: undefined, + }; + const populatedReceipt = { ...receiptWithoutGas, gasPrice: 1 }; + jest.spyOn(utils, 'getTransactionGasPricing').mockResolvedValue(populatedReceipt); + const result = await _sendTxHelper.populateGasPrice({ + transaction: receiptWithoutGas, + transactionFormatted: receiptWithoutGas, + }); + expect(result).toStrictEqual(populatedReceipt); + expect(utils.getTransactionGasPricing).toHaveBeenCalled(); + }); + it('emit signAndSend', async () => { + jest.spyOn(utils, 'trySendTransaction').mockResolvedValue('success'); + const wallet = { + signTransaction: jest.fn(() => ({ + transactionHash: receipt.transactionHash, + rawTransaction: receipt, + })), + }; + const result = await sendTxHelper.signAndSend({ + tx: receipt, + wallet: wallet as unknown as Web3BaseWalletAccount, + }); + expect(result).toBe('success'); + expect(utils.trySendTransaction).toHaveBeenCalled(); + expect(wallet.signTransaction).toHaveBeenCalledWith(receipt); + }); +}); From 3d6113232018f61d35ff32e81706cb22aa2bfd6c Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Mon, 18 Sep 2023 18:28:28 -0400 Subject: [PATCH 10/13] add receipt events checks into integration tests --- .../test/integration/contract_erc20.test.ts | 8 +- .../test/integration/contract_erc721.test.ts | 12 ++- .../test/integration/contract_methods.test.ts | 1 + .../local_account/contract_erc20.test.ts | 30 +++++-- .../local_account/contract_erc721.test.ts | 81 +++++++++++++------ .../web3_eth/send_transaction.test.ts | 17 +++- 6 files changed, 114 insertions(+), 35 deletions(-) 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 9c3c92a8043..930fb551844 100644 --- a/packages/web3-eth-contract/test/integration/contract_erc20.test.ts +++ b/packages/web3-eth-contract/test/integration/contract_erc20.test.ts @@ -94,7 +94,13 @@ describe('contract', () => { it('should transfer tokens', async () => { const acc2 = await createTempAccount(); const value = BigInt(10); - await contractDeployed.methods.transfer(acc2.address, value).send(sendOptions); + const receipt = await contractDeployed.methods + .transfer(acc2.address, value) + .send(sendOptions); + + expect(receipt.events).toBeDefined(); + expect(receipt.events?.Transfer).toBeDefined(); + expect(receipt.events?.Transfer.event).toBe('Transfer'); expect(await contractDeployed.methods.balanceOf(acc2.address).call()).toBe( value, diff --git a/packages/web3-eth-contract/test/integration/contract_erc721.test.ts b/packages/web3-eth-contract/test/integration/contract_erc721.test.ts index 21c31e5e8cb..704fdada972 100644 --- a/packages/web3-eth-contract/test/integration/contract_erc721.test.ts +++ b/packages/web3-eth-contract/test/integration/contract_erc721.test.ts @@ -81,10 +81,14 @@ describe('contract', () => { it('should award item', async () => { const tempAccount = await createTempAccount(); - await contractDeployed.methods + const receipt = await contractDeployed.methods .awardItem(tempAccount.address, 'http://my-nft-uri') .send(sendOptions); + expect(receipt.events).toBeDefined(); + expect(receipt.events?.Transfer).toBeDefined(); + expect(receipt.events?.Transfer.event).toBe('Transfer'); + const tokenId = toBigInt(0); expect( toUpperCaseHex( @@ -289,9 +293,13 @@ describe('contract', () => { }); }); - await contractDeployed.methods + const receipt = await contractDeployed.methods .awardItem(acc2.address, 'http://my-nft-uri') .send(sendOptions); + + expect(receipt.events).toBeDefined(); + expect(receipt.events?.Transfer).toBeDefined(); + expect(receipt.events?.Transfer.event).toBe('Transfer'); }), ).resolves.toEqual({ from: '0x0000000000000000000000000000000000000000', diff --git a/packages/web3-eth-contract/test/integration/contract_methods.test.ts b/packages/web3-eth-contract/test/integration/contract_methods.test.ts index f63923cdfbe..7f072d5a358 100644 --- a/packages/web3-eth-contract/test/integration/contract_methods.test.ts +++ b/packages/web3-eth-contract/test/integration/contract_methods.test.ts @@ -92,6 +92,7 @@ describe('contract', () => { .setValues(1, 'string value', true) .send(sendOptions); + expect(receipt.events).toBeUndefined(); expect(receipt).toEqual( expect.objectContaining({ // status: BigInt(1), diff --git a/packages/web3-eth-contract/test/integration/local_account/contract_erc20.test.ts b/packages/web3-eth-contract/test/integration/local_account/contract_erc20.test.ts index bd379e5f647..2346b198244 100644 --- a/packages/web3-eth-contract/test/integration/local_account/contract_erc20.test.ts +++ b/packages/web3-eth-contract/test/integration/local_account/contract_erc20.test.ts @@ -61,11 +61,13 @@ describe('contract', () => { const acc = web3.eth.accounts.create(); const value = BigInt(10); - await contractDeployed.methods.transfer(acc.address, value).send({ + const receipt = await contractDeployed.methods.transfer(acc.address, value).send({ ...sendOptions, type, }); - + expect(receipt.events).toBeDefined(); + expect(receipt.events?.Transfer).toBeDefined(); + expect(receipt.events?.Transfer.event).toBe('Transfer'); expect(await contractDeployed.methods.balanceOf(acc.address).call()).toBe(value); }); @@ -74,14 +76,21 @@ describe('contract', () => { const transferFromValue = BigInt(4); const tempAccount = await createLocalAccount(web3); // approve - await contractDeployed.methods + const approvalReceipt = await contractDeployed.methods .approve(tempAccount.address, value) .send({ ...sendOptions, type }); - + expect(approvalReceipt.events).toBeDefined(); + expect(approvalReceipt.events?.Approval).toBeDefined(); + expect(approvalReceipt.events?.Approval.event).toBe('Approval'); // transferFrom - await contractDeployed.methods + const transferFromReceipt = await contractDeployed.methods .transferFrom(localAccount.address, tempAccount.address, transferFromValue) .send({ ...sendOptions, from: tempAccount.address, type }); + expect(transferFromReceipt.events).toBeDefined(); + expect(transferFromReceipt.events?.Approval).toBeDefined(); + expect(transferFromReceipt.events?.Approval.event).toBe('Approval'); + expect(transferFromReceipt.events?.Transfer).toBeDefined(); + expect(transferFromReceipt.events?.Transfer.event).toBe('Transfer'); expect(await contractDeployed.methods.balanceOf(tempAccount.address).call()).toBe( transferFromValue, @@ -101,10 +110,12 @@ describe('contract', () => { const tempAccount = await createLocalAccount(web3); // approve - await contractDeployed.methods + const approvalReceipt = await contractDeployed.methods .approve(tempAccount.address, value) .send({ ...sendOptions, type }); - + expect(approvalReceipt.events).toBeDefined(); + expect(approvalReceipt.events?.Approval).toBeDefined(); + expect(approvalReceipt.events?.Approval.event).toBe('Approval'); // allowance expect( await contractDeployed.methods @@ -113,10 +124,13 @@ describe('contract', () => { ).toBe(value); // increaseAllowance - await contractDeployed.methods + const increaseAllowanceReceipt = await contractDeployed.methods .increaseAllowance(tempAccount.address, extraAmount) .send({ ...sendOptions, from: localAccount.address, type, gas: '2000000' }); + expect(increaseAllowanceReceipt.events).toBeDefined(); + expect(increaseAllowanceReceipt.events?.Approval).toBeDefined(); + expect(increaseAllowanceReceipt.events?.Approval.event).toBe('Approval'); // check allowance expect( await contractDeployed.methods diff --git a/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts b/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts index 6741b15e034..2923b0e6417 100644 --- a/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts +++ b/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts @@ -60,9 +60,12 @@ describe('contract', () => { it.each(['0x1', '0x2'])('should award item %p', async type => { const tempAccount = web3.eth.accounts.create(); - await contractDeployed.methods + const awardReceipt = await contractDeployed.methods .awardItem(tempAccount.address, 'http://my-nft-uri') .send({ ...sendOptions, type }); + expect(awardReceipt.events).toBeDefined(); + expect(awardReceipt.events?.Transfer).toBeDefined(); + expect(awardReceipt.events?.Transfer.event).toBe('Transfer'); const logs = await contractDeployed.getPastEvents('Transfer'); const tokenId = (logs[0] as EventLog)?.returnValues?.tokenId as string; @@ -77,20 +80,27 @@ describe('contract', () => { it.each(['0x1', '0x2'])('should transferFrom item %p', async type => { const tempAccount = await createLocalAccount(web3); const toAccount = await createLocalAccount(web3); - await contractDeployed.methods + const awardReceipt = await contractDeployed.methods .awardItem(tempAccount.address, 'http://my-nft-award') .send({ ...sendOptions, type }); + expect(awardReceipt.events).toBeDefined(); + expect(awardReceipt.events?.Transfer).toBeDefined(); + expect(awardReceipt.events?.Transfer.event).toBe('Transfer'); const logs = await contractDeployed.getPastEvents('Transfer'); const tokenId = (logs[0] as EventLog)?.returnValues?.tokenId as string; - await contractDeployed.methods + const transferFromReceipt = await contractDeployed.methods .transferFrom(tempAccount.address, toAccount.address, tokenId) .send({ ...sendOptions, type, from: tempAccount.address, }); - + expect(transferFromReceipt.events).toBeDefined(); + expect(transferFromReceipt.events?.Transfer).toBeDefined(); + expect(transferFromReceipt.events?.Transfer.event).toBe('Transfer'); + expect(transferFromReceipt.events?.Approval).toBeDefined(); + expect(transferFromReceipt.events?.Approval.event).toBe('Approval'); expect( toUpperCaseHex( (await contractDeployed.methods.ownerOf(tokenId).call()) as unknown as string, @@ -101,22 +111,31 @@ describe('contract', () => { it.each(['0x1', '0x2'])('should approve and then transferFrom item %p', async type => { const tempAccount = await createLocalAccount(web3); const toAccount = await createLocalAccount(web3); - await contractDeployed.methods + const awardReceipt = await contractDeployed.methods .awardItem(tempAccount.address, 'http://my-nft-award') .send({ ...sendOptions, type }); + expect(awardReceipt.events).toBeDefined(); + expect(awardReceipt.events?.Transfer).toBeDefined(); + expect(awardReceipt.events?.Transfer.event).toBe('Transfer'); const logs = await contractDeployed.getPastEvents('Transfer'); const tokenId = (logs[0] as EventLog)?.returnValues?.tokenId as string; - await contractDeployed.methods.approve(toAccount.address, tokenId).send({ - ...sendOptions, - type, - from: tempAccount.address, - }); + const approveReceipt = await contractDeployed.methods + .approve(toAccount.address, tokenId) + .send({ + ...sendOptions, + type, + from: tempAccount.address, + }); + expect(approveReceipt.events).toBeDefined(); + expect(approveReceipt.events?.Approval).toBeDefined(); + expect(approveReceipt.events?.Approval.event).toBe('Approval'); + const res = await contractDeployed.methods.getApproved(tokenId).call(); expect(res.toString().toUpperCase()).toBe(toAccount.address.toUpperCase()); - await contractDeployed.methods + const safeTransferFromReceipt = await contractDeployed.methods .safeTransferFrom(tempAccount.address, toAccount.address, tokenId) .send({ ...sendOptions, @@ -124,6 +143,12 @@ describe('contract', () => { from: toAccount.address, }); + expect(safeTransferFromReceipt.events).toBeDefined(); + expect(safeTransferFromReceipt.events?.Transfer).toBeDefined(); + expect(safeTransferFromReceipt.events?.Transfer.event).toBe('Transfer'); + expect(safeTransferFromReceipt.events?.Approval).toBeDefined(); + expect(safeTransferFromReceipt.events?.Approval.event).toBe('Approval'); + expect( toUpperCaseHex( (await contractDeployed.methods.ownerOf(tokenId).call()) as unknown as string, @@ -137,24 +162,34 @@ describe('contract', () => { const tempAccount = await createLocalAccount(web3); const toAccount = await createLocalAccount(web3); - await contractDeployed.methods.setApprovalForAll(toAccount.address, true).send({ - ...sendOptions, - type, - from: tempAccount.address, - }); - + const setApprovalReceipt = await contractDeployed.methods + .setApprovalForAll(toAccount.address, true) + .send({ + ...sendOptions, + type, + from: tempAccount.address, + }); + expect(setApprovalReceipt.events).toBeDefined(); + expect(setApprovalReceipt.events?.ApprovalForAll).toBeDefined(); + expect(setApprovalReceipt.events?.ApprovalForAll.event).toBe('ApprovalForAll'); expect( await contractDeployed.methods .isApprovedForAll(tempAccount.address, toAccount.address) .call(), ).toBe(true); - await contractDeployed.methods.setApprovalForAll(toAccount.address, false).send({ - ...sendOptions, - type, - from: tempAccount.address, - }); - + const setApprovalForAllReceipt = await contractDeployed.methods + .setApprovalForAll(toAccount.address, false) + .send({ + ...sendOptions, + type, + from: tempAccount.address, + }); + expect(setApprovalForAllReceipt.events).toBeDefined(); + expect(setApprovalForAllReceipt.events?.ApprovalForAll).toBeDefined(); + expect(setApprovalForAllReceipt.events?.ApprovalForAll.event).toBe( + 'ApprovalForAll', + ); expect( await contractDeployed.methods .isApprovedForAll(tempAccount.address, toAccount.address) diff --git a/packages/web3-eth/test/integration/web3_eth/send_transaction.test.ts b/packages/web3-eth/test/integration/web3_eth/send_transaction.test.ts index 3d8f824fd6f..b3587a47c45 100644 --- a/packages/web3-eth/test/integration/web3_eth/send_transaction.test.ts +++ b/packages/web3-eth/test/integration/web3_eth/send_transaction.test.ts @@ -61,6 +61,7 @@ describe('Web3Eth.sendTransaction', () => { }; const response = await web3Eth.sendTransaction(transaction); expect(response.status).toBe(BigInt(1)); + expect(response.events).toBeUndefined(); const minedTransactionData = await web3Eth.getTransaction(response.transactionHash); expect(minedTransactionData).toMatchObject(transaction); @@ -84,6 +85,7 @@ describe('Web3Eth.sendTransaction', () => { }; const response = await web3EthWithWallet.sendTransaction(transaction); expect(response.status).toBe(BigInt(1)); + expect(response.events).toBeUndefined(); const minedTransactionData = await web3EthWithWallet.getTransaction( response.transactionHash, @@ -114,6 +116,7 @@ describe('Web3Eth.sendTransaction', () => { }; const response = await web3EthWithWallet.sendTransaction(transaction); expect(response.status).toBe(BigInt(1)); + expect(response.events).toBeUndefined(); const minedTransactionData = await web3EthWithWallet.getTransaction( response.transactionHash, @@ -148,6 +151,7 @@ describe('Web3Eth.sendTransaction', () => { }; const response = await web3EthWithWallet.sendTransaction(transaction); expect(response.status).toBe(BigInt(1)); + expect(response.events).toBeUndefined(); const minedTransactionData = await web3EthWithWallet.getTransaction( response.transactionHash, @@ -167,6 +171,7 @@ describe('Web3Eth.sendTransaction', () => { }; const response = await web3Eth.sendTransaction(transaction); expect(response.status).toBe(BigInt(1)); + expect(response.events).toBeUndefined(); const minedTransactionData = await web3Eth.getTransaction(response.transactionHash); expect(minedTransactionData).toMatchObject(transaction); @@ -180,6 +185,7 @@ describe('Web3Eth.sendTransaction', () => { }; const response = await web3Eth.sendTransaction(transaction); expect(response.status).toBe(BigInt(1)); + expect(response.events).toBeUndefined(); const minedTransactionData = await web3Eth.getTransaction(response.transactionHash); expect(minedTransactionData).toMatchObject(transaction); @@ -199,6 +205,7 @@ describe('Web3Eth.sendTransaction', () => { }; const response = await web3Eth.sendTransaction(transaction); expect(response.status).toBe(BigInt(1)); + expect(response.events).toBeUndefined(); expect(response.contractAddress).toBeDefined(); const minedTransactionData = await web3Eth.getTransaction(response.transactionHash); @@ -221,6 +228,7 @@ describe('Web3Eth.sendTransaction', () => { input: contractFunctionCall, }; const response = await web3Eth.sendTransaction(transaction); + expect(response.events).toBeUndefined(); expect(response.status).toBe(BigInt(1)); const minedTransactionData = await web3Eth.getTransaction(response.transactionHash); @@ -241,6 +249,7 @@ describe('Web3Eth.sendTransaction', () => { type: BigInt(0), }; const response = await web3Eth.sendTransaction(transaction); + expect(response.events).toBeUndefined(); expect(response.type).toBe(BigInt(0)); expect(response.status).toBe(BigInt(1)); @@ -260,6 +269,7 @@ describe('Web3Eth.sendTransaction', () => { accessList: [], }; const response = await web3Eth.sendTransaction(transaction); + expect(response.events).toBeUndefined(); expect(response.type).toBe(BigInt(1)); expect(response.status).toBe(BigInt(1)); @@ -275,6 +285,7 @@ describe('Web3Eth.sendTransaction', () => { type: BigInt(2), }; const response = await web3Eth.sendTransaction(transaction); + expect(response.events).toBeUndefined(); expect(response.type).toBe(BigInt(2)); expect(response.status).toBe(BigInt(1)); @@ -291,6 +302,7 @@ describe('Web3Eth.sendTransaction', () => { }; const response = await web3Eth.sendTransaction(transaction, DEFAULT_RETURN_FORMAT); expect(response.type).toBe(BigInt(0)); + expect(response.events).toBeUndefined(); expect(response.status).toBe(BigInt(1)); const minedTransactionData = await web3Eth.getTransaction(response.transactionHash); expect(minedTransactionData).toMatchObject(transaction); @@ -304,6 +316,7 @@ describe('Web3Eth.sendTransaction', () => { maxFeePerGas: BigInt(2500000016), }; const response = await web3Eth.sendTransaction(transaction); + expect(response.events).toBeUndefined(); expect(response.type).toBe(BigInt(2)); expect(response.status).toBe(BigInt(1)); const minedTransactionData = await web3Eth.getTransaction(response.transactionHash); @@ -318,6 +331,7 @@ describe('Web3Eth.sendTransaction', () => { maxPriorityFeePerGas: BigInt(100), }; const response = await web3Eth.sendTransaction(transaction); + expect(response.events).toBeUndefined(); expect(response.type).toBe(BigInt(2)); expect(response.status).toBe(BigInt(1)); const minedTransactionData = await web3Eth.getTransaction(response.transactionHash); @@ -379,8 +393,9 @@ describe('Web3Eth.sendTransaction', () => { expect(typeof data.transactionIndex).toBe('bigint'); expect(data.status).toBe(BigInt(1)); expect(data.type).toBe(BigInt(0)); + expect(data.events).toBeUndefined(); }); - expect.assertions(8); + expect.assertions(9); }); it('should listen to the confirmation event', async () => { From 65ed6e86762fcab5bd09283b5aacf5ad2a780f09 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Tue, 19 Sep 2023 11:16:58 -0400 Subject: [PATCH 11/13] update latest version. changelog sync --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 387f493760a..91127e5e67f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2082,3 +2082,15 @@ If there are any bugs, improvements, optimizations or any new feature proposal f - Dependencies updated ## [Unreleased] + +### Added + +#### web3-eth + +- Added `ALL_EVENTS` and `ALL_EVENTS_ABI` constants, `SendTransactionEventsBase` type, `decodeEventABI` method (#6410) + +### Fixed + +#### web3-eth + +- Ensure provider.supportsSubscriptions exists before watching by subscription (#6440) From 9c7a69789288070e831f1ff0137faeea6d2ecb50 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Wed, 20 Sep 2023 10:52:41 -0400 Subject: [PATCH 12/13] add change logs --- CHANGELOG.md | 10 ++++++++++ packages/web3-eth-contract/CHANGELOG.md | 6 +++++- packages/web3-types/CHANGELOG.md | 6 +++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91127e5e67f..98da596e4a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2089,8 +2089,18 @@ If there are any bugs, improvements, optimizations or any new feature proposal f - Added `ALL_EVENTS` and `ALL_EVENTS_ABI` constants, `SendTransactionEventsBase` type, `decodeEventABI` method (#6410) +#### web3-types + +- Interface `EventLog` was added. (#6410) + ### Fixed #### web3-eth - Ensure provider.supportsSubscriptions exists before watching by subscription (#6440) + +### Changed + +#### web3-eth-contract + +- The `events` property was added to the `receipt` object (#6410) diff --git a/packages/web3-eth-contract/CHANGELOG.md b/packages/web3-eth-contract/CHANGELOG.md index 4e1667e2a96..cd30b90db36 100644 --- a/packages/web3-eth-contract/CHANGELOG.md +++ b/packages/web3-eth-contract/CHANGELOG.md @@ -308,4 +308,8 @@ Documentation: - Added to `Web3Config` property `contractDataInputFill` allowing users to have the choice using property `data`, `input` or `both` for contract methods to be sent to the RPC provider when creating contracts. (#6377) -## [Unreleased] \ No newline at end of file +## [Unreleased] + +### Changed + +- The `events` property was added to the `receipt` object (#6410) diff --git a/packages/web3-types/CHANGELOG.md b/packages/web3-types/CHANGELOG.md index f85baa353bc..e0f82088f8e 100644 --- a/packages/web3-types/CHANGELOG.md +++ b/packages/web3-types/CHANGELOG.md @@ -164,4 +164,8 @@ Documentation: - add `asEIP1193Provider` to `Web3BaseProvider` so every inherited class can have the returned value of `request` method, fully compatible with EIP-1193. (#6407) -## [Unreleased] \ No newline at end of file +## [Unreleased] + +### Added + +- Interface `EventLog` was added. (#6410) From abd7870de38dc9d95aa8546ada6bfb88c1928dbf Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Thu, 21 Sep 2023 10:29:59 -0400 Subject: [PATCH 13/13] add values check to test --- .../test/integration/contract_erc20.test.ts | 14 ++ .../test/integration/contract_erc721.test.ts | 25 ++- .../local_account/contract_erc20.test.ts | 86 ++++++++++ .../local_account/contract_erc721.test.ts | 158 ++++++++++++++++++ 4 files changed, 282 insertions(+), 1 deletion(-) 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 930fb551844..15d1852a055 100644 --- a/packages/web3-eth-contract/test/integration/contract_erc20.test.ts +++ b/packages/web3-eth-contract/test/integration/contract_erc20.test.ts @@ -101,6 +101,20 @@ describe('contract', () => { expect(receipt.events).toBeDefined(); expect(receipt.events?.Transfer).toBeDefined(); expect(receipt.events?.Transfer.event).toBe('Transfer'); + expect(String(receipt.events?.Transfer.returnValues.from).toLowerCase()).toBe( + mainAcc.address.toLowerCase(), + ); + expect(String(receipt.events?.Transfer.returnValues[0]).toLowerCase()).toBe( + mainAcc.address.toLowerCase(), + ); + expect(String(receipt.events?.Transfer.returnValues.to).toLowerCase()).toBe( + acc2.address.toLowerCase(), + ); + expect(String(receipt.events?.Transfer.returnValues[1]).toLowerCase()).toBe( + acc2.address.toLowerCase(), + ); + expect(receipt.events?.Transfer.returnValues.value).toBe(value); + expect(receipt.events?.Transfer.returnValues[2]).toBe(value); expect(await contractDeployed.methods.balanceOf(acc2.address).call()).toBe( value, diff --git a/packages/web3-eth-contract/test/integration/contract_erc721.test.ts b/packages/web3-eth-contract/test/integration/contract_erc721.test.ts index 704fdada972..9463a636930 100644 --- a/packages/web3-eth-contract/test/integration/contract_erc721.test.ts +++ b/packages/web3-eth-contract/test/integration/contract_erc721.test.ts @@ -88,7 +88,18 @@ describe('contract', () => { expect(receipt.events).toBeDefined(); expect(receipt.events?.Transfer).toBeDefined(); expect(receipt.events?.Transfer.event).toBe('Transfer'); - + expect(String(receipt.events?.Transfer.returnValues.from).toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000', + ); + expect(String(receipt.events?.Transfer.returnValues[0]).toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000', + ); + expect(String(receipt.events?.Transfer.returnValues.to).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect(String(receipt.events?.Transfer.returnValues[1]).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); const tokenId = toBigInt(0); expect( toUpperCaseHex( @@ -300,6 +311,18 @@ describe('contract', () => { expect(receipt.events).toBeDefined(); expect(receipt.events?.Transfer).toBeDefined(); expect(receipt.events?.Transfer.event).toBe('Transfer'); + expect( + String(receipt.events?.Transfer.returnValues.from).toLowerCase(), + ).toBe('0x0000000000000000000000000000000000000000'); + expect( + String(receipt.events?.Transfer.returnValues[0]).toLowerCase(), + ).toBe('0x0000000000000000000000000000000000000000'); + expect( + String(receipt.events?.Transfer.returnValues.to).toLowerCase(), + ).toBe(acc2.address.toLowerCase()); + expect( + String(receipt.events?.Transfer.returnValues[1]).toLowerCase(), + ).toBe(acc2.address.toLowerCase()); }), ).resolves.toEqual({ from: '0x0000000000000000000000000000000000000000', diff --git a/packages/web3-eth-contract/test/integration/local_account/contract_erc20.test.ts b/packages/web3-eth-contract/test/integration/local_account/contract_erc20.test.ts index 2346b198244..edff03fe598 100644 --- a/packages/web3-eth-contract/test/integration/local_account/contract_erc20.test.ts +++ b/packages/web3-eth-contract/test/integration/local_account/contract_erc20.test.ts @@ -68,6 +68,23 @@ describe('contract', () => { expect(receipt.events).toBeDefined(); expect(receipt.events?.Transfer).toBeDefined(); expect(receipt.events?.Transfer.event).toBe('Transfer'); + expect(receipt.events).toBeDefined(); + expect(receipt.events?.Transfer).toBeDefined(); + expect(receipt.events?.Transfer.event).toBe('Transfer'); + expect(String(receipt.events?.Transfer.returnValues.from).toLowerCase()).toBe( + localAccount.address.toLowerCase(), + ); + expect(String(receipt.events?.Transfer.returnValues[0]).toLowerCase()).toBe( + localAccount.address.toLowerCase(), + ); + expect(String(receipt.events?.Transfer.returnValues.to).toLowerCase()).toBe( + acc.address.toLowerCase(), + ); + expect(String(receipt.events?.Transfer.returnValues[1]).toLowerCase()).toBe( + acc.address.toLowerCase(), + ); + expect(receipt.events?.Transfer.returnValues.value).toBe(value); + expect(receipt.events?.Transfer.returnValues[2]).toBe(value); expect(await contractDeployed.methods.balanceOf(acc.address).call()).toBe(value); }); @@ -89,8 +106,42 @@ describe('contract', () => { expect(transferFromReceipt.events).toBeDefined(); expect(transferFromReceipt.events?.Approval).toBeDefined(); expect(transferFromReceipt.events?.Approval.event).toBe('Approval'); + expect( + String(transferFromReceipt.events?.Approval.returnValues.owner).toLowerCase(), + ).toBe(localAccount.address.toLowerCase()); + expect(String(transferFromReceipt.events?.Approval.returnValues[0]).toLowerCase()).toBe( + localAccount.address.toLowerCase(), + ); + expect( + String(transferFromReceipt.events?.Approval.returnValues.spender).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect(String(transferFromReceipt.events?.Approval.returnValues[1]).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect(transferFromReceipt.events?.Approval.returnValues.value).toBe( + value - transferFromValue, + ); + expect(transferFromReceipt.events?.Approval.returnValues[2]).toBe( + value - transferFromValue, + ); + expect(transferFromReceipt.events?.Transfer).toBeDefined(); expect(transferFromReceipt.events?.Transfer.event).toBe('Transfer'); + expect(transferFromReceipt.events).toBeDefined(); + expect( + String(transferFromReceipt.events?.Transfer.returnValues.from).toLowerCase(), + ).toBe(localAccount.address.toLowerCase()); + expect(String(transferFromReceipt.events?.Transfer.returnValues[0]).toLowerCase()).toBe( + localAccount.address.toLowerCase(), + ); + expect(String(transferFromReceipt.events?.Transfer.returnValues.to).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect(String(transferFromReceipt.events?.Transfer.returnValues[1]).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect(transferFromReceipt.events?.Transfer.returnValues.value).toBe(transferFromValue); + expect(transferFromReceipt.events?.Transfer.returnValues[2]).toBe(transferFromValue); expect(await contractDeployed.methods.balanceOf(tempAccount.address).call()).toBe( transferFromValue, @@ -116,6 +167,21 @@ describe('contract', () => { expect(approvalReceipt.events).toBeDefined(); expect(approvalReceipt.events?.Approval).toBeDefined(); expect(approvalReceipt.events?.Approval.event).toBe('Approval'); + expect(String(approvalReceipt.events?.Approval.returnValues.owner).toLowerCase()).toBe( + localAccount.address.toLowerCase(), + ); + expect(String(approvalReceipt.events?.Approval.returnValues[0]).toLowerCase()).toBe( + localAccount.address.toLowerCase(), + ); + expect( + String(approvalReceipt.events?.Approval.returnValues.spender).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect(String(approvalReceipt.events?.Approval.returnValues[1]).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect(approvalReceipt.events?.Approval.returnValues.value).toBe(value); + expect(approvalReceipt.events?.Approval.returnValues[2]).toBe(value); + // allowance expect( await contractDeployed.methods @@ -131,6 +197,26 @@ describe('contract', () => { expect(increaseAllowanceReceipt.events).toBeDefined(); expect(increaseAllowanceReceipt.events?.Approval).toBeDefined(); expect(increaseAllowanceReceipt.events?.Approval.event).toBe('Approval'); + expect( + String(increaseAllowanceReceipt.events?.Approval.returnValues.owner).toLowerCase(), + ).toBe(localAccount.address.toLowerCase()); + expect( + String(increaseAllowanceReceipt.events?.Approval.returnValues[0]).toLowerCase(), + ).toBe(localAccount.address.toLowerCase()); + expect( + String( + increaseAllowanceReceipt.events?.Approval.returnValues.spender, + ).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect( + String(increaseAllowanceReceipt.events?.Approval.returnValues[1]).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect(increaseAllowanceReceipt.events?.Approval.returnValues.value).toBe( + value + extraAmount, + ); + expect(increaseAllowanceReceipt.events?.Approval.returnValues[2]).toBe( + value + extraAmount, + ); // check allowance expect( await contractDeployed.methods diff --git a/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts b/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts index 2923b0e6417..2b50e735257 100644 --- a/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts +++ b/packages/web3-eth-contract/test/integration/local_account/contract_erc721.test.ts @@ -67,6 +67,19 @@ describe('contract', () => { expect(awardReceipt.events?.Transfer).toBeDefined(); expect(awardReceipt.events?.Transfer.event).toBe('Transfer'); + expect(String(awardReceipt.events?.Transfer.returnValues.from).toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000', + ); + expect(String(awardReceipt.events?.Transfer.returnValues[0]).toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000', + ); + expect(String(awardReceipt.events?.Transfer.returnValues.to).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect(String(awardReceipt.events?.Transfer.returnValues[1]).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + const logs = await contractDeployed.getPastEvents('Transfer'); const tokenId = (logs[0] as EventLog)?.returnValues?.tokenId as string; @@ -87,6 +100,19 @@ describe('contract', () => { expect(awardReceipt.events?.Transfer).toBeDefined(); expect(awardReceipt.events?.Transfer.event).toBe('Transfer'); + expect(String(awardReceipt.events?.Transfer.returnValues.from).toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000', + ); + expect(String(awardReceipt.events?.Transfer.returnValues[0]).toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000', + ); + expect(String(awardReceipt.events?.Transfer.returnValues.to).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect(String(awardReceipt.events?.Transfer.returnValues[1]).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + const logs = await contractDeployed.getPastEvents('Transfer'); const tokenId = (logs[0] as EventLog)?.returnValues?.tokenId as string; const transferFromReceipt = await contractDeployed.methods @@ -99,8 +125,38 @@ describe('contract', () => { expect(transferFromReceipt.events).toBeDefined(); expect(transferFromReceipt.events?.Transfer).toBeDefined(); expect(transferFromReceipt.events?.Transfer.event).toBe('Transfer'); + expect( + String(transferFromReceipt.events?.Transfer.returnValues.from).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect(String(transferFromReceipt.events?.Transfer.returnValues[0]).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect(String(transferFromReceipt.events?.Transfer.returnValues.to).toLowerCase()).toBe( + toAccount.address.toLowerCase(), + ); + expect(String(transferFromReceipt.events?.Transfer.returnValues[1]).toLowerCase()).toBe( + toAccount.address.toLowerCase(), + ); + expect(transferFromReceipt.events?.Transfer.returnValues.tokenId).toBe(tokenId); + expect(transferFromReceipt.events?.Transfer.returnValues[2]).toBe(tokenId); + expect(transferFromReceipt.events?.Approval).toBeDefined(); expect(transferFromReceipt.events?.Approval.event).toBe('Approval'); + expect( + String(transferFromReceipt.events?.Approval.returnValues.owner).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect(String(transferFromReceipt.events?.Approval.returnValues[0]).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect( + String(transferFromReceipt.events?.Approval.returnValues.approved).toLowerCase(), + ).toBe('0x0000000000000000000000000000000000000000'); + expect(String(transferFromReceipt.events?.Approval.returnValues[1]).toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000', + ); + expect(transferFromReceipt.events?.Approval.returnValues.tokenId).toBe(tokenId); + expect(transferFromReceipt.events?.Approval.returnValues[2]).toBe(tokenId); + expect( toUpperCaseHex( (await contractDeployed.methods.ownerOf(tokenId).call()) as unknown as string, @@ -117,6 +173,18 @@ describe('contract', () => { expect(awardReceipt.events).toBeDefined(); expect(awardReceipt.events?.Transfer).toBeDefined(); expect(awardReceipt.events?.Transfer.event).toBe('Transfer'); + expect(String(awardReceipt.events?.Transfer.returnValues.from).toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000', + ); + expect(String(awardReceipt.events?.Transfer.returnValues[0]).toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000', + ); + expect(String(awardReceipt.events?.Transfer.returnValues.to).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect(String(awardReceipt.events?.Transfer.returnValues[1]).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); const logs = await contractDeployed.getPastEvents('Transfer'); const tokenId = (logs[0] as EventLog)?.returnValues?.tokenId as string; @@ -131,6 +199,20 @@ describe('contract', () => { expect(approveReceipt.events).toBeDefined(); expect(approveReceipt.events?.Approval).toBeDefined(); expect(approveReceipt.events?.Approval.event).toBe('Approval'); + expect(String(approveReceipt.events?.Approval.returnValues.owner).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect(String(approveReceipt.events?.Approval.returnValues[0]).toLowerCase()).toBe( + tempAccount.address.toLowerCase(), + ); + expect( + String(approveReceipt.events?.Approval.returnValues.approved).toLowerCase(), + ).toBe(toAccount.address.toLowerCase()); + expect(String(approveReceipt.events?.Approval.returnValues[1]).toLowerCase()).toBe( + toAccount.address.toLowerCase(), + ); + expect(approveReceipt.events?.Approval.returnValues.tokenId).toBe(tokenId); + expect(approveReceipt.events?.Approval.returnValues[2]).toBe(tokenId); const res = await contractDeployed.methods.getApproved(tokenId).call(); expect(res.toString().toUpperCase()).toBe(toAccount.address.toUpperCase()); @@ -149,6 +231,38 @@ describe('contract', () => { expect(safeTransferFromReceipt.events?.Approval).toBeDefined(); expect(safeTransferFromReceipt.events?.Approval.event).toBe('Approval'); + expect( + String(safeTransferFromReceipt.events?.Transfer.returnValues.from).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect( + String(safeTransferFromReceipt.events?.Transfer.returnValues[0]).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect( + String(safeTransferFromReceipt.events?.Transfer.returnValues.to).toLowerCase(), + ).toBe(toAccount.address.toLowerCase()); + expect( + String(safeTransferFromReceipt.events?.Transfer.returnValues[1]).toLowerCase(), + ).toBe(toAccount.address.toLowerCase()); + expect(safeTransferFromReceipt.events?.Transfer.returnValues.tokenId).toBe(tokenId); + expect(safeTransferFromReceipt.events?.Transfer.returnValues[2]).toBe(tokenId); + + expect( + String(safeTransferFromReceipt.events?.Approval.returnValues.owner).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect( + String(safeTransferFromReceipt.events?.Approval.returnValues[0]).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect( + String( + safeTransferFromReceipt.events?.Approval.returnValues.approved, + ).toLowerCase(), + ).toBe('0x0000000000000000000000000000000000000000'); + expect( + String(safeTransferFromReceipt.events?.Approval.returnValues[1]).toLowerCase(), + ).toBe('0x0000000000000000000000000000000000000000'); + expect(safeTransferFromReceipt.events?.Approval.returnValues.tokenId).toBe(tokenId); + expect(safeTransferFromReceipt.events?.Approval.returnValues[2]).toBe(tokenId); + expect( toUpperCaseHex( (await contractDeployed.methods.ownerOf(tokenId).call()) as unknown as string, @@ -172,6 +286,26 @@ describe('contract', () => { expect(setApprovalReceipt.events).toBeDefined(); expect(setApprovalReceipt.events?.ApprovalForAll).toBeDefined(); expect(setApprovalReceipt.events?.ApprovalForAll.event).toBe('ApprovalForAll'); + + expect( + String( + setApprovalReceipt.events?.ApprovalForAll.returnValues.owner, + ).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect( + String(setApprovalReceipt.events?.ApprovalForAll.returnValues[0]).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect( + String( + setApprovalReceipt.events?.ApprovalForAll.returnValues.operator, + ).toLowerCase(), + ).toBe(toAccount.address.toLowerCase()); + expect( + String(setApprovalReceipt.events?.ApprovalForAll.returnValues[1]).toLowerCase(), + ).toBe(toAccount.address.toLowerCase()); + expect(setApprovalReceipt.events?.ApprovalForAll.returnValues.approved).toBe(true); + expect(setApprovalReceipt.events?.ApprovalForAll.returnValues[2]).toBe(true); + expect( await contractDeployed.methods .isApprovedForAll(tempAccount.address, toAccount.address) @@ -190,6 +324,30 @@ describe('contract', () => { expect(setApprovalForAllReceipt.events?.ApprovalForAll.event).toBe( 'ApprovalForAll', ); + expect( + String( + setApprovalForAllReceipt.events?.ApprovalForAll.returnValues.owner, + ).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect( + String( + setApprovalForAllReceipt.events?.ApprovalForAll.returnValues[0], + ).toLowerCase(), + ).toBe(tempAccount.address.toLowerCase()); + expect( + String( + setApprovalForAllReceipt.events?.ApprovalForAll.returnValues.operator, + ).toLowerCase(), + ).toBe(toAccount.address.toLowerCase()); + expect( + String( + setApprovalForAllReceipt.events?.ApprovalForAll.returnValues[1], + ).toLowerCase(), + ).toBe(toAccount.address.toLowerCase()); + expect(setApprovalForAllReceipt.events?.ApprovalForAll.returnValues.approved).toBe( + false, + ); + expect(setApprovalForAllReceipt.events?.ApprovalForAll.returnValues[2]).toBe(false); expect( await contractDeployed.methods .isApprovedForAll(tempAccount.address, toAccount.address)