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, {
// 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));
}

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
Loading