From ef17da25272fb56351df9e692f90b4117fc4c4f2 Mon Sep 17 00:00:00 2001 From: Nicolas Brugneaux Date: Mon, 23 Sep 2024 16:02:43 +0200 Subject: [PATCH 1/5] feat: make getEthereumjsTxDataFromTransaction not trim extra fields --- .../utils/prepare_transaction_for_signing.ts | 39 +++++++------- .../prepare_transaction_for_signing.test.ts | 54 +++++++++++++++++++ .../web3/test/fixtures/tx-type-15/index.ts | 50 ++++++++++++----- .../integration/web3-plugin-add-tx.test.ts | 16 +++--- 4 files changed, 120 insertions(+), 39 deletions(-) diff --git a/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts b/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts index cd70e1e265c..bd562244e11 100644 --- a/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts +++ b/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts @@ -18,8 +18,6 @@ along with web3.js. If not, see . import { EthExecutionAPI, HexString, - PopulatedUnsignedEip1559Transaction, - PopulatedUnsignedEip2930Transaction, PopulatedUnsignedTransaction, Transaction, ValidChains, @@ -36,25 +34,24 @@ import { transactionBuilder } from './transaction_builder.js'; const getEthereumjsTxDataFromTransaction = ( transaction: FormatType, -) => ({ - nonce: transaction.nonce, - gasPrice: transaction.gasPrice, - gasLimit: transaction.gasLimit ?? transaction.gas, - to: transaction.to, - value: transaction.value, - data: transaction.data ?? transaction.input, - type: transaction.type, - chainId: transaction.chainId, - accessList: ( - transaction as FormatType - ).accessList, - maxPriorityFeePerGas: ( - transaction as FormatType - ).maxPriorityFeePerGas, - maxFeePerGas: ( - transaction as FormatType - ).maxFeePerGas, -}); +) => { + const txData = { ...transaction }; + + const aliases = [ + ['input', 'data'], + ['gas', 'gasLimit'], + ] as const; + + for (const [oldField, newField] of aliases) { + if (typeof txData[oldField] !== 'undefined') { + // @ts-expect-error + txData[newField] = txData[oldField]; + delete txData[oldField]; + } + } + + return txData; +}; const getEthereumjsTransactionOptions = ( transaction: FormatType, diff --git a/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts b/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts index ce83a505d17..9dba4b7bdfa 100644 --- a/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts +++ b/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts @@ -28,9 +28,13 @@ import { FeeMarketEIP1559Transaction, Transaction, Hardfork, + TypedTransaction, + TransactionFactory, } from 'web3-eth-accounts'; import { prepareTransactionForSigning } from '../../src/utils/prepare_transaction_for_signing'; import { validTransactions } from '../fixtures/prepare_transaction_for_signing'; +import { transactionSchema } from '../../src/schemas'; +import { CustomFieldTransaction } from '../fixtures/format_transaction'; describe('prepareTransactionForSigning', () => { const web3Context = new Web3Context({ @@ -317,4 +321,54 @@ describe('prepareTransactionForSigning', () => { }, ); }); + + it('should not remove extra fields when using a custom schema', async () => { + const context = new Web3Context({ + provider: new HttpProvider('http://127.0.0.1'), + config: { + defaultNetworkId: '0x1', + customTransactionSchema: { + type: 'object', + properties: { + ...transactionSchema.properties, + feeCurrency: { format: 'address' }, + }, + }, + }, + }); + + async function transactionBuilder(options: { + transaction: TransactionType; + web3Context: Web3Context; + privateKey?: HexString | Uint8Array; + fillGasPrice?: boolean; + fillGasLimit?: boolean; + }): Promise { + const tx = { ...options.transaction }; + return tx as unknown as ReturnType; + } + + context.transactionBuilder = transactionBuilder; + + const spy = jest.spyOn(TransactionFactory, 'fromTxData'); + (await prepareTransactionForSigning( + { + chainId: 1458, + nonce: 1, + gasPrice: BigInt(20000000000), + gasLimit: BigInt(21000), + to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', + from: '0x2c7536E3605D9C16a7a3D7b1898e529396a65c23', + value: '1000000000', + input: '', + feeCurrency: '0x1234567890123456789012345678901234567890', + } as CustomFieldTransaction, + context, + )) as TypedTransaction & { feeCurrency: string }; + + // @ts-expect-error + expect(spy.mock.lastCall[0].feeCurrency).toEqual( + '0x1234567890123456789012345678901234567890', + ); + }); }); diff --git a/packages/web3/test/fixtures/tx-type-15/index.ts b/packages/web3/test/fixtures/tx-type-15/index.ts index 8dd34b35e25..1d7dd778067 100644 --- a/packages/web3/test/fixtures/tx-type-15/index.ts +++ b/packages/web3/test/fixtures/tx-type-15/index.ts @@ -32,9 +32,9 @@ import { AccessList, AccessListUint8Array, FeeMarketEIP1559TxData, - FeeMarketEIP1559ValuesArray, JsonTx, TxOptions, + TxValuesArray, } from 'web3-eth-accounts'; const { getAccessListData, getAccessListJSON, getDataFeeEIP2930, verifyAccessList } = txUtils; @@ -43,6 +43,26 @@ const MAX_INTEGER = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffff export const TRANSACTION_TYPE = 15; const TRANSACTION_TYPE_UINT8ARRAY = hexToBytes(TRANSACTION_TYPE.toString(16).padStart(2, '0')); +type CustomFieldTxValuesArray = [ + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + Uint8Array, + AccessListUint8Array, + Uint8Array, + Uint8Array?, + Uint8Array?, + Uint8Array?, +]; + +type SomeNewTxTypeTxData = FeeMarketEIP1559TxData & { + customTestField: bigint; +}; + /** * Typed transaction with a new gas fee market mechanism * @@ -50,12 +70,13 @@ const TRANSACTION_TYPE_UINT8ARRAY = hexToBytes(TRANSACTION_TYPE.toString(16).pad * - EIP: [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) */ // eslint-disable-next-line no-use-before-define -export class SomeNewTxTypeTransaction extends BaseTransaction { +export class SomeNewTxTypeTransaction extends BaseTransaction { public readonly chainId: bigint; public readonly accessList: AccessListUint8Array; public readonly AccessListJSON: AccessList; public readonly maxPriorityFeePerGas: bigint; public readonly maxFeePerGas: bigint; + public readonly customTestField: bigint; public readonly common: Common; @@ -77,7 +98,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction { type: TRANSACTION_TYPE, maxPriorityFeePerGas: BigInt(5000000), maxFeePerGas: BigInt(5000000), + customField: BigInt(42), }; const sub = web3.eth.sendTransaction(tx); - const waitForEvent: Promise = new Promise(resolve => { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - sub.on('sending', txData => { - resolve(txData as unknown as Transaction); - }); - }); + const waitForEvent: Promise = new Promise( + resolve => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + sub.on('sending', txData => { + resolve(txData as unknown as Transaction & { customField: bigint }); + }); + }, + ); expect(Number((await waitForEvent).type)).toBe(TRANSACTION_TYPE); + expect(BigInt((await waitForEvent).customField)).toBe(BigInt(42)); await expect(sub).rejects.toThrow(); }); }); From 477d992157a43f6db0e8fcbcfee3843d686d84ed Mon Sep 17 00:00:00 2001 From: Nicolas Brugneaux Date: Tue, 24 Sep 2024 08:45:11 +0200 Subject: [PATCH 2/5] fix: lint issues --- .../web3-eth/src/utils/prepare_transaction_for_signing.ts | 3 +-- .../test/unit/prepare_transaction_for_signing.test.ts | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts b/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts index bd562244e11..032dd555172 100644 --- a/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts +++ b/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts @@ -44,8 +44,7 @@ const getEthereumjsTxDataFromTransaction = ( for (const [oldField, newField] of aliases) { if (typeof txData[oldField] !== 'undefined') { - // @ts-expect-error - txData[newField] = txData[oldField]; + txData[newField] = txData[oldField]!; delete txData[oldField]; } } diff --git a/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts b/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts index 9dba4b7bdfa..e0955db8fac 100644 --- a/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts +++ b/packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts @@ -366,9 +366,7 @@ describe('prepareTransactionForSigning', () => { context, )) as TypedTransaction & { feeCurrency: string }; - // @ts-expect-error - expect(spy.mock.lastCall[0].feeCurrency).toEqual( - '0x1234567890123456789012345678901234567890', - ); + // @ts-expect-error feeCurrency is a custom field for testing here + expect(spy.mock.lastCall[0].feeCurrency).toBe('0x1234567890123456789012345678901234567890'); }); }); From 7e57cbe529d918e20d2b24ab5905254f23de68f5 Mon Sep 17 00:00:00 2001 From: Nicolas Brugneaux Date: Tue, 24 Sep 2024 09:21:06 +0200 Subject: [PATCH 3/5] fix: tests --- .../test/integration/web3-plugin-add-tx.test.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/web3/test/integration/web3-plugin-add-tx.test.ts b/packages/web3/test/integration/web3-plugin-add-tx.test.ts index fb857245b24..505862a4eeb 100644 --- a/packages/web3/test/integration/web3-plugin-add-tx.test.ts +++ b/packages/web3/test/integration/web3-plugin-add-tx.test.ts @@ -18,6 +18,7 @@ along with web3.js. If not, see . /* eslint-disable @typescript-eslint/no-magic-numbers */ import { Transaction, Web3Account } from 'web3-eth-accounts'; +import { transactionSchema } from 'web3-eth'; import { SupportedProviders, Web3, Web3PluginBase } from '../../src'; import { createAccount, @@ -51,6 +52,14 @@ describe('Add New Tx as a Plugin', () => { }); it('should receive correct type of tx', async () => { web3.registerPlugin(new Eip4844Plugin()); + web3.config.customTransactionSchema = { + type: 'object', + properties: { + ...transactionSchema.properties, + customField: { format: 'string' }, + }, + }; + web3.eth.config.customTransactionSchema = web3.config.customTransactionSchema; const tx = { from: account1.address, to: account2.address, @@ -70,8 +79,9 @@ describe('Add New Tx as a Plugin', () => { }); }, ); - expect(Number((await waitForEvent).type)).toBe(TRANSACTION_TYPE); - expect(BigInt((await waitForEvent).customField)).toBe(BigInt(42)); + const { type, customField } = await waitForEvent; + expect(Number(type)).toBe(TRANSACTION_TYPE); + expect(BigInt(customField)).toBe(BigInt(42)); await expect(sub).rejects.toThrow(); }); }); From f176bff748f0b02ee3b57e0725a272dd8b23817d Mon Sep 17 00:00:00 2001 From: Nicolas Brugneaux Date: Tue, 24 Sep 2024 16:37:48 +0200 Subject: [PATCH 4/5] fix: revert the changes and just spread extra fields --- .../utils/prepare_transaction_for_signing.ts | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts b/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts index 032dd555172..a90ce32173d 100644 --- a/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts +++ b/packages/web3-eth/src/utils/prepare_transaction_for_signing.ts @@ -18,6 +18,8 @@ along with web3.js. If not, see . import { EthExecutionAPI, HexString, + PopulatedUnsignedEip1559Transaction, + PopulatedUnsignedEip2930Transaction, PopulatedUnsignedTransaction, Transaction, ValidChains, @@ -34,23 +36,26 @@ import { transactionBuilder } from './transaction_builder.js'; const getEthereumjsTxDataFromTransaction = ( transaction: FormatType, -) => { - const txData = { ...transaction }; - - const aliases = [ - ['input', 'data'], - ['gas', 'gasLimit'], - ] as const; - - for (const [oldField, newField] of aliases) { - if (typeof txData[oldField] !== 'undefined') { - txData[newField] = txData[oldField]!; - delete txData[oldField]; - } - } - - return txData; -}; +) => ({ + ...transaction, + nonce: transaction.nonce, + gasPrice: transaction.gasPrice, + gasLimit: transaction.gasLimit ?? transaction.gas, + to: transaction.to, + value: transaction.value, + data: transaction.data ?? transaction.input, + type: transaction.type, + chainId: transaction.chainId, + accessList: ( + transaction as FormatType + ).accessList, + maxPriorityFeePerGas: ( + transaction as FormatType + ).maxPriorityFeePerGas, + maxFeePerGas: ( + transaction as FormatType + ).maxFeePerGas, +}); const getEthereumjsTransactionOptions = ( transaction: FormatType, From 3371c43872c7e8dfd734ecb81de60c4b716e937b Mon Sep 17 00:00:00 2001 From: Nicolas Brugneaux Date: Tue, 24 Sep 2024 16:41:37 +0200 Subject: [PATCH 5/5] chore: changelog --- CHANGELOG.md | 6 ++++++ packages/web3-eth/CHANGELOG.md | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6714ae042fa..46c0eefe645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2728,3 +2728,9 @@ If there are any bugs, improvements, optimizations or any new feature proposal f - The callback function provided to the static `Web3.onNewProviderDiscovered` function expects a parameter of type `EIP6963ProvidersMapUpdateEvent` as opposed to `EIP6963AnnounceProviderEvent`. (#7242) ## [Unreleased] + +### Changed + +#### web3-eth + +- Allow `getEthereumjsTxDataFrom` to return additional fields that may be passed if using a `customTransactionSchema`. diff --git a/packages/web3-eth/CHANGELOG.md b/packages/web3-eth/CHANGELOG.md index e75b0f867b2..71fd65e4f9e 100644 --- a/packages/web3-eth/CHANGELOG.md +++ b/packages/web3-eth/CHANGELOG.md @@ -280,3 +280,7 @@ Documentation: - Adds the same `{transactionSchema?: ValidationSchemaInput}` that exists in `formatTransaction` to `validateTransactionForSigning` ## [Unreleased] + +### Changed + +- Allow `getEthereumjsTxDataFrom` to return additional fields that may be passed if using a `customTransactionSchema`.