Skip to content

Commit

Permalink
feat: make getEthereumjsTxDataFromTransaction not trim extra fields (#…
Browse files Browse the repository at this point in the history
…7282)

* feat: make getEthereumjsTxDataFromTransaction not trim extra fields

* fix: lint issues

* fix: tests

* fix: revert the changes and just spread extra fields

* chore: changelog
  • Loading branch information
nicolasbrugneaux authored Sep 26, 2024
1 parent c602fc6 commit cc99825
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 19 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
4 changes: 4 additions & 0 deletions packages/web3-eth/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { transactionBuilder } from './transaction_builder.js';
const getEthereumjsTxDataFromTransaction = (
transaction: FormatType<PopulatedUnsignedTransaction, typeof ETH_DATA_FORMAT>,
) => ({
...transaction,
nonce: transaction.nonce,
gasPrice: transaction.gasPrice,
gasLimit: transaction.gasLimit ?? transaction.gas,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<EthExecutionAPI>({
Expand Down Expand Up @@ -317,4 +321,52 @@ describe('prepareTransactionForSigning', () => {
},
);
});

it('should not remove extra fields when using a custom schema', async () => {
const context = new Web3Context<EthExecutionAPI>({
provider: new HttpProvider('http://127.0.0.1'),
config: {
defaultNetworkId: '0x1',
customTransactionSchema: {
type: 'object',
properties: {
...transactionSchema.properties,
feeCurrency: { format: 'address' },
},
},
},
});

async function transactionBuilder<ReturnType = TransactionType>(options: {
transaction: TransactionType;
web3Context: Web3Context<EthExecutionAPI & Web3NetAPI>;
privateKey?: HexString | Uint8Array;
fillGasPrice?: boolean;
fillGasLimit?: boolean;
}): Promise<ReturnType> {
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 feeCurrency is a custom field for testing here
expect(spy.mock.lastCall[0].feeCurrency).toBe('0x1234567890123456789012345678901234567890');
});
});
50 changes: 38 additions & 12 deletions packages/web3/test/fixtures/tx-type-15/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ import {
AccessList,
AccessListUint8Array,
FeeMarketEIP1559TxData,
FeeMarketEIP1559ValuesArray,
JsonTx,
TxOptions,
TxValuesArray,
} from 'web3-eth-accounts';

const { getAccessListData, getAccessListJSON, getDataFeeEIP2930, verifyAccessList } = txUtils;
Expand All @@ -43,19 +43,40 @@ 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
*
* - TransactionType: 2
* - EIP: [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)
*/
// eslint-disable-next-line no-use-before-define
export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Transaction> {
export class SomeNewTxTypeTransaction extends BaseTransaction<SomeNewTxTypeTransaction> {
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;

Expand All @@ -77,7 +98,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
* - `chainId` will be set automatically if not provided
* - All parameters are optional and have some basic default values
*/
public static fromTxData(txData: FeeMarketEIP1559TxData, opts: TxOptions = {}) {
public static fromTxData(txData: SomeNewTxTypeTxData, opts: TxOptions = {}) {
return new SomeNewTxTypeTransaction(txData, opts);
}

Expand Down Expand Up @@ -110,10 +131,10 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
* Format: `[chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data,
* accessList, signatureYParity, signatureR, signatureS]`
*/
public static fromValuesArray(values: FeeMarketEIP1559ValuesArray, opts: TxOptions = {}) {
if (values.length !== 9 && values.length !== 12) {
public static fromValuesArray(values: CustomFieldTxValuesArray, opts: TxOptions = {}) {
if (values.length !== 10 && values.length !== 13) {
throw new Error(
'Invalid EIP-1559 transaction. Only expecting 9 values (for unsigned tx) or 12 values (for signed tx).',
'Invalid CUSTOM TEST transaction. Only expecting 10 values (for unsigned tx) or 13 values (for signed tx).',
);
}

Expand All @@ -127,6 +148,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
value,
data,
accessList,
customTestField,
v,
r,
s,
Expand All @@ -144,7 +166,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
s,
});

return new FeeMarketEIP1559Transaction(
return new SomeNewTxTypeTransaction(
{
chainId: uint8ArrayToBigInt(chainId),
nonce,
Expand All @@ -155,6 +177,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
value,
data,
accessList: accessList ?? [],
customTestField: uint8ArrayToBigInt(customTestField),
v: v !== undefined ? uint8ArrayToBigInt(v) : undefined, // EIP2930 supports v's with value 0 (empty Uint8Array)
r,
s,
Expand All @@ -170,12 +193,13 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
* the static factory methods to assist in creating a Transaction object from
* varying data types.
*/
public constructor(txData: FeeMarketEIP1559TxData, opts: TxOptions = {}) {
public constructor(txData: SomeNewTxTypeTxData, opts: TxOptions = {}) {
super({ ...txData, type: TRANSACTION_TYPE }, opts);
const { chainId, accessList, maxFeePerGas, maxPriorityFeePerGas } = txData;
const { chainId, accessList, maxFeePerGas, maxPriorityFeePerGas, customTestField } = txData;

this.common = this._getCommon(opts.common, chainId);
this.chainId = this.common.chainId();
this.customTestField = customTestField;

if (!this.common.isActivatedEIP(1559)) {
throw new Error('EIP-1559 not enabled on Common');
Expand Down Expand Up @@ -272,7 +296,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
* signature parameters `v`, `r` and `s` for encoding. For an EIP-155 compliant
* representation for external signing use {@link FeeMarketEIP1559Transaction.getMessageToSign}.
*/
public raw(): FeeMarketEIP1559ValuesArray {
public raw(): TxValuesArray {
return [
bigIntToUnpaddedUint8Array(this.chainId),
bigIntToUnpaddedUint8Array(this.nonce),
Expand All @@ -283,10 +307,11 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
bigIntToUnpaddedUint8Array(this.value),
this.data,
this.accessList,
bigIntToUnpaddedUint8Array(this.customTestField),
this.v !== undefined ? bigIntToUnpaddedUint8Array(this.v) : Uint8Array.from([]),
this.r !== undefined ? bigIntToUnpaddedUint8Array(this.r) : Uint8Array.from([]),
this.s !== undefined ? bigIntToUnpaddedUint8Array(this.s) : Uint8Array.from([]),
];
] as TxValuesArray;
}

/**
Expand Down Expand Up @@ -384,7 +409,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
public _processSignature(v: bigint, r: Uint8Array, s: Uint8Array) {
const opts = { ...this.txOptions, common: this.common };

return FeeMarketEIP1559Transaction.fromTxData(
return SomeNewTxTypeTransaction.fromTxData(
{
chainId: this.chainId,
nonce: this.nonce,
Expand All @@ -395,6 +420,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
value: this.value,
data: this.data,
accessList: this.accessList,
customTestField: this.customTestField,
v: v - BigInt(27), // This looks extremely hacky: /util actually adds 27 to the value, the recovery bit is either 0 or 1.
r: uint8ArrayToBigInt(r),
s: uint8ArrayToBigInt(s),
Expand Down
28 changes: 21 additions & 7 deletions packages/web3/test/integration/web3-plugin-add-tx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
/* 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,
Expand Down Expand Up @@ -51,23 +52,36 @@ 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,
value: '0x1',
type: TRANSACTION_TYPE,
maxPriorityFeePerGas: BigInt(5000000),
maxFeePerGas: BigInt(5000000),
customField: BigInt(42),
};
const sub = web3.eth.sendTransaction(tx);

const waitForEvent: Promise<Transaction> = new Promise(resolve => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sub.on('sending', txData => {
resolve(txData as unknown as Transaction);
});
});
expect(Number((await waitForEvent).type)).toBe(TRANSACTION_TYPE);
const waitForEvent: Promise<Transaction & { customField: bigint }> = new Promise(
resolve => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sub.on('sending', txData => {
resolve(txData as unknown as Transaction & { customField: bigint });
});
},
);
const { type, customField } = await waitForEvent;
expect(Number(type)).toBe(TRANSACTION_TYPE);
expect(BigInt(customField)).toBe(BigInt(42));
await expect(sub).rejects.toThrow();
});
});

1 comment on commit cc99825

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: cc99825 Previous: c602fc6 Ratio
processingTx 21879 ops/sec (±7.68%) 22432 ops/sec (±6.29%) 1.03
processingContractDeploy 38811 ops/sec (±6.95%) 39716 ops/sec (±8.35%) 1.02
processingContractMethodSend 15153 ops/sec (±7.20%) 15739 ops/sec (±9.03%) 1.04
processingContractMethodCall 27291 ops/sec (±6.06%) 28613 ops/sec (±6.55%) 1.05
abiEncode 41796 ops/sec (±7.77%) 45466 ops/sec (±7.38%) 1.09
abiDecode 28813 ops/sec (±7.30%) 30872 ops/sec (±7.68%) 1.07
sign 1512 ops/sec (±0.76%) 1492 ops/sec (±3.27%) 0.99
verify 359 ops/sec (±0.56%) 369 ops/sec (±0.47%) 1.03

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.