Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contract Transactions Middleware #7138

Merged
merged 16 commits into from
Jul 8, 2024
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 @@
ALL_EVENTS,
ALL_EVENTS_ABI,
SendTransactionEvents,
TransactionMiddleware,
SendTransactionOptions,
} from 'web3-eth';
import {
encodeEventSignature,
Expand Down Expand Up @@ -433,7 +435,7 @@
*/

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 @@
}
}

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

public getTransactionMiddleware() {
return this.transactionMiddleware;
}

/**
* Subscribe to an event.
*
Expand Down Expand Up @@ -1396,11 +1406,17 @@
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, {

Check warning on line 1415 in packages/web3-eth-contract/src/contract.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-contract/src/contract.ts#L1415

Added line #L1415 was not covered by tests
// 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 @@
options: { ...options, dataInputFill: this.contractDataInputFill },
contractOptions: modifiedContractOptions,
});
return sendTransaction(this, tx, this.defaultReturnFormat, {
transactionResolver: receipt => {

const returnTxOptions: SendTransactionOptions<Contract<Abi>> = {
transactionResolver: (receipt: TransactionReceipt) => {

Check warning on line 1450 in packages/web3-eth-contract/src/contract.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-contract/src/contract.ts#L1450

Added line #L1450 was not covered by tests
if (receipt.status === BigInt(0)) {
throw new Web3ContractError("code couldn't be stored", receipt);
}
Expand All @@ -1444,7 +1461,12 @@
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));

Check warning on line 1469 in packages/web3-eth-contract/src/contract.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-contract/src/contract.ts#L1469

Added line #L1469 was not covered by tests
}

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);
}

}
12 changes: 12 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 @@ -788,6 +789,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 @@

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);

Check warning on line 212 in packages/web3/src/web3.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3/src/web3.ts#L212

Added line #L212 was not covered by tests
}
}
}
}

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);
}

}
116 changes: 116 additions & 0 deletions packages/web3/test/integration/contract-middleware.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
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 { CTransactionMiddleware } from
// eslint-disable-next-line import/no-relative-packages
"../fixtures/transaction_middleware";

import { blockMockResult, receiptMockResult } from
// eslint-disable-next-line import/no-relative-packages
"../../../../tools/web3-plugin-example/test/unit/fixtures/transactions_data";

import { Web3 } from '../../src/index';
import {
ERC20TokenAbi,
// eslint-disable-next-line import/no-relative-packages
} from '../shared_fixtures/contracts/ERC20Token';

describe('Contract Middleware', () => {
it('should set transaction middleware in contract new instance if its set at eth package', async () => {
const web3 = new Web3();
const contractA = new web3.eth.Contract(
ERC20TokenAbi,
'0x7af963cF6D228E564e2A0aA0DdBF06210B38615D',
);

expect(web3.eth.getTransactionMiddleware()).toBeUndefined();
expect(contractA.getTransactionMiddleware()).toBeUndefined();

const middleware = new CTransactionMiddleware();
web3.eth.setTransactionMiddleware(middleware);

const contractB = new web3.eth.Contract(
ERC20TokenAbi,
'0x7af963cF6D228E564e2A0aA0DdBF06210B38615D',
);

expect(web3.eth.getTransactionMiddleware()).toBeDefined();
expect(contractB.getTransactionMiddleware()).toBeDefined();
expect(web3.eth.getTransactionMiddleware()).toEqual(contractB.getTransactionMiddleware());

});

it('should send transaction middleware in contract new instance if its set at eth package', async () => {
const web3 = new Web3();

const middleware = new CTransactionMiddleware();
web3.eth.setTransactionMiddleware(middleware);

const contract = new web3.eth.Contract(
ERC20TokenAbi,
'0x7af963cF6D228E564e2A0aA0DdBF06210B38615D',
);

const sendTransactionSpy = jest.fn();
const account = web3.eth.accounts.create();

let blockNum = 1000;
web3.requestManager.send = jest.fn(async (request) => {
blockNum += 1;

if (request.method === 'eth_getBlockByNumber') {

return Promise.resolve(blockMockResult.result);
}
if (request.method === 'eth_call') {

return Promise.resolve("0x");
}
if (request.method === 'eth_blockNumber') {

return Promise.resolve(blockNum.toString(16));
}
if (request.method === 'eth_sendTransaction') {

sendTransactionSpy(request.params);

return Promise.resolve("0xdf7756865c2056ce34c4eabe4eff42ad251a9f920a1c620c00b4ea0988731d3f");
}
if (request.method === 'eth_getTransactionReceipt') {
return Promise.resolve(receiptMockResult.result);
}

return Promise.resolve("Unknown Request" as any);
});

await contract.methods.transfer(account.address, 100).send({ from: account?.address });

// Check that sendTransactionSpy has been called exactly once
expect(sendTransactionSpy).toHaveBeenCalledTimes(1);

// Check that sendTransactionSpy has been called with a parameter containing data: "0x123"
expect(sendTransactionSpy).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
data: "0x123",
from: account.address,
})
])
);
});
});

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export class TransactionMiddlewarePlugin extends Web3PluginBase {
public link(parentContext: Web3Context): void {

if (this.txMiddleware){
// Following can modify Web3-Eth and also Web3-Eth-Contract packages transactions

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
(parentContext as any).Web3Eth.setTransactionMiddleware(this.txMiddleware);
}
Expand Down
Loading
Loading