Skip to content

Commit

Permalink
Contract Transactions Middleware (#7138)
Browse files Browse the repository at this point in the history
* contract transactionMiddleware

* transactionMiddleware setting for contract from eth

* contract Tx middleware plugin

* contract tx middleware tests

* changelog

* fix

* test import fix

* integration patch fix

* lint fix

* integration tests

* middleware in fixture

* unit test

* lint fix

* fixture fix for lint import err via symlink

* unit test coverage push

* up coverage
  • Loading branch information
jdevcs authored Jul 8, 2024
1 parent 280b8f0 commit e9fc019
Show file tree
Hide file tree
Showing 12 changed files with 525 additions and 10 deletions.
6 changes: 5 additions & 1 deletion packages/web3-eth-contract/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,8 @@ Documentation:

- `defaultReturnFormat` was added to all methods that have `ReturnType` param. (#6947)

## [Unreleased]
## [Unreleased]

### Added

- Contract has `setTransactionMiddleware` and `getTransactionMiddleware` for automatically passing to `sentTransaction` for `deploy` and `send` functions (#7138)
40 changes: 31 additions & 9 deletions packages/web3-eth-contract/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {
ALL_EVENTS,
ALL_EVENTS_ABI,
SendTransactionEvents,
TransactionMiddleware,
SendTransactionOptions,
} from 'web3-eth';
import {
encodeEventSignature,
Expand Down Expand Up @@ -433,7 +435,7 @@ export class Contract<Abi extends ContractAbi>
*/

public readonly options: ContractOptions;

private transactionMiddleware?: TransactionMiddleware;
/**
* Set to true if you want contracts' defaults to sync with global defaults.
*/
Expand Down Expand Up @@ -640,6 +642,14 @@ export class Contract<Abi extends ContractAbi>
}
}

public setTransactionMiddleware(transactionMiddleware: TransactionMiddleware) {
this.transactionMiddleware = transactionMiddleware;
}

public getTransactionMiddleware() {
return this.transactionMiddleware;
}

/**
* Subscribe to an event.
*
Expand Down Expand Up @@ -1396,11 +1406,17 @@ export class Contract<Abi extends ContractAbi>
contractOptions: modifiedContractOptions,
});

const transactionToSend = sendTransaction(this, tx, this.defaultReturnFormat, {
// TODO Should make this configurable by the user
checkRevertBeforeSending: false,
contractAbi: this._jsonInterface,
});
const transactionToSend = (isNullish(this.transactionMiddleware)) ?
sendTransaction(this, tx, this.defaultReturnFormat, {
// TODO Should make this configurable by the user
checkRevertBeforeSending: false,
contractAbi: this._jsonInterface, // explicitly not passing middleware so if some one is using old eth package it will not break
}) :
sendTransaction(this, tx, this.defaultReturnFormat, {
// TODO Should make this configurable by the user
checkRevertBeforeSending: false,
contractAbi: this._jsonInterface,
}, this.transactionMiddleware);

// eslint-disable-next-line no-void
void transactionToSend.on('error', (error: unknown) => {
Expand Down Expand Up @@ -1429,8 +1445,9 @@ export class Contract<Abi extends ContractAbi>
options: { ...options, dataInputFill: this.contractDataInputFill },
contractOptions: modifiedContractOptions,
});
return sendTransaction(this, tx, this.defaultReturnFormat, {
transactionResolver: receipt => {

const returnTxOptions: SendTransactionOptions<Contract<Abi>> = {
transactionResolver: (receipt: TransactionReceipt) => {
if (receipt.status === BigInt(0)) {
throw new Web3ContractError("code couldn't be stored", receipt);
}
Expand All @@ -1444,7 +1461,12 @@ export class Contract<Abi extends ContractAbi>
contractAbi: this._jsonInterface,
// TODO Should make this configurable by the user
checkRevertBeforeSending: false,
});
};

return (
(isNullish(this.transactionMiddleware)) ?
sendTransaction(this, tx, this.defaultReturnFormat, returnTxOptions) : // not calling this with undefined Middleware because it will not break if Eth package is not updated
sendTransaction(this, tx, this.defaultReturnFormat, returnTxOptions, this.transactionMiddleware));
}

private async _contractMethodEstimateGas<
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
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 <http://www.gnu.org/licenses/>.
*/

import { TransactionMiddleware, TransactionMiddlewareData } from "web3-eth";

// Sample Transaction Middleware
export class ContractTransactionMiddleware implements TransactionMiddleware {

// eslint-disable-next-line class-methods-use-this
public async processTransaction(transaction: TransactionMiddlewareData,
_options?: { [key: string]: unknown } | undefined):

Promise<TransactionMiddlewareData> {

// eslint-disable-next-line prefer-const
let txObj = { ...transaction };

// Add your logic here for transaction modification
txObj.data = '0x123';

return Promise.resolve(txObj);
}

}
67 changes: 67 additions & 0 deletions packages/web3-eth-contract/test/unit/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { getSystemTestProvider } from '../fixtures/system_test_utils';
import { erc721Abi } from '../fixtures/erc721';
import { ERC20TokenAbi } from '../shared_fixtures/build/ERC20Token';
import { processAsync } from '../shared_fixtures/utils';
import { ContractTransactionMiddleware } from "../fixtures/contract_transaction_middleware";

jest.mock('web3-eth', () => {
const allAutoMocked = jest.createMockFromModule('web3-eth');
Expand Down Expand Up @@ -275,6 +276,61 @@ describe('Contract', () => {
sendTransactionSpy.mockClear();
});

it('should pass middleware to sendTransaction when middleware is there and deploy().send() is called', async () => {
const contract = new Contract(GreeterAbi);
const middleware = new ContractTransactionMiddleware();
contract.setTransactionMiddleware(middleware)

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const sendTransactionSpy = jest
.spyOn(eth, 'sendTransaction')
.mockImplementation((_objInstance, _tx, _dataFormat, _options, _middleware) => {

expect(_middleware).toBeDefined();
const newContract = contract.clone();
newContract.options.address = deployedAddr;

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return Promise.resolve(newContract) as any;
});

await contract
.deploy({
input: GreeterBytecode,
arguments: ['My Greeting'],
})
.send(sendOptions);

sendTransactionSpy.mockClear();
});

it('should pass middleware to sendTransaction when middleware is there and contract.method.send() is called', async () => {

const contract = new Contract(GreeterAbi, '0x12264916b10Ae90076dDa6dE756EE1395BB69ec2');
const middleware = new ContractTransactionMiddleware();
contract.setTransactionMiddleware(middleware);

const spyTx = jest
.spyOn(eth, 'sendTransaction')
.mockImplementation((_objInstance, _tx, _dataformat, _options, _middleware) => {

expect(_middleware).toBeDefined();

// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-empty-function
return { status: '0x1', on: () => {} } as any;

});

const receipt = await contract.methods.setGreeting('Hello').send({
from: '0x12364916b10Ae90076dDa6dE756EE1395BB69ec2',
gas: '1000000'
});

expect(receipt.status).toBe('0x1');

spyTx.mockClear();
});

it('should deploy contract with input property with no ABI', async () => {
const input = `${GreeterBytecode}0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b4d79204772656574696e67000000000000000000000000000000000000000000`;
const contract = new Contract([]);
Expand Down Expand Up @@ -788,6 +844,17 @@ describe('Contract', () => {
expect(contract.methods.setGreeting).toBeDefined();
});

it('should be able to set and get transaction middleware', () => {
const contract = new Contract(sampleStorageContractABI);
const middleware = new ContractTransactionMiddleware();

expect(contract.getTransactionMiddleware()).toBeUndefined();

contract.setTransactionMiddleware(middleware);
expect(contract.getTransactionMiddleware()).toBeDefined();
expect(contract.getTransactionMiddleware()).toEqual(middleware);
});

it('defaults set and get should work', () => {
const contract = new Contract([], '0x00000000219ab540356cBB839Cbe05303d7705Fa');

Expand Down
6 changes: 6 additions & 0 deletions packages/web3/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,3 +382,9 @@ Documentation:
- `getName` reverse resolution

## [Unreleased]

### Added

#### web3

- `web3.eth.Contract` will get transaction middleware and use it, if `web3.eth` has transaction middleware. (#7138)
9 changes: 9 additions & 0 deletions packages/web3/src/web3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,15 @@ export class Web3<

super(jsonInterface, address, options, context, dataFormat);
super.subscribeToContextEvents(self);

// eslint-disable-next-line no-use-before-define
if (!isNullish(eth)) {
// eslint-disable-next-line no-use-before-define
const TxMiddleware = eth.getTransactionMiddleware();
if (!isNullish(TxMiddleware)) {
super.setTransactionMiddleware(TxMiddleware);
}
}
}
}

Expand Down
38 changes: 38 additions & 0 deletions packages/web3/test/fixtures/transaction_middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
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 <http://www.gnu.org/licenses/>.
*/

import { TransactionMiddleware, TransactionMiddlewareData } from "web3-eth";

// Sample Transaction Middleware
export class CTransactionMiddleware implements TransactionMiddleware {

// eslint-disable-next-line class-methods-use-this
public async processTransaction(transaction: TransactionMiddlewareData,
_options?: { [key: string]: unknown } | undefined):

Promise<TransactionMiddlewareData> {

// eslint-disable-next-line prefer-const
let txObj = { ...transaction };

// Add your logic here for transaction modification
txObj.data = '0x123';

return Promise.resolve(txObj);
}

}
Loading

1 comment on commit e9fc019

@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: e9fc019 Previous: 280b8f0 Ratio
processingTx 9175 ops/sec (±3.95%) 8643 ops/sec (±4.01%) 0.94
processingContractDeploy 38902 ops/sec (±6.92%) 37706 ops/sec (±7.16%) 0.97
processingContractMethodSend 15550 ops/sec (±7.85%) 15011 ops/sec (±8.43%) 0.97
processingContractMethodCall 27222 ops/sec (±8.01%) 26199 ops/sec (±7.75%) 0.96
abiEncode 43564 ops/sec (±6.49%) 40879 ops/sec (±7.43%) 0.94
abiDecode 30187 ops/sec (±7.32%) 30556 ops/sec (±6.68%) 1.01
sign 1545 ops/sec (±0.67%) 1503 ops/sec (±3.72%) 0.97
verify 367 ops/sec (±0.61%) 363 ops/sec (±0.57%) 0.99

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

Please sign in to comment.