Skip to content

Commit

Permalink
fix: ensure contractInstance.subscriptionManager.subscribe is not thr…
Browse files Browse the repository at this point in the history
…owing (#7327)

* rename `LogsSubscription` at `web3-eth-contract` to `ContractLogsSubscription`

* fix issue that `contractInstance.subscriptionManager.subscribe` was throwing

* add a unit test
  • Loading branch information
Muhammad-Altabba authored Oct 21, 2024
1 parent fab66e9 commit ed85cce
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 22 deletions.
72 changes: 72 additions & 0 deletions packages/web3-eth-contract/src/contract-subscription-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
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 { Web3SubscriptionConstructor, Web3SubscriptionManager } from 'web3-core';
import { EthExecutionAPI, ContractAbi, DataFormat, DEFAULT_RETURN_FORMAT } from 'web3-types';
// eslint-disable-next-line import/no-cycle
import { Contract } from './contract.js';

/**
* Similar to `Web3SubscriptionManager` but has a reference to the Contract that uses
*/
export class ContractSubscriptionManager<
API extends EthExecutionAPI,
RegisteredSubs extends {
[key: string]: Web3SubscriptionConstructor<API>;
} = any, // = ContractSubscriptions
> extends Web3SubscriptionManager<API, RegisteredSubs> {
public readonly parentContract: Contract<ContractAbi>;

/**
*
* @param - Web3SubscriptionManager
* @param - parentContract
*
* @example
* ```ts
* const requestManager = new Web3RequestManager("ws://localhost:8545");
* const contract = new Contract(...)
* const subscriptionManager = new Web3SubscriptionManager(requestManager, {}, contract);
* ```
*/
public constructor(
self: Web3SubscriptionManager<API, RegisteredSubs>,
parentContract: Contract<ContractAbi>,
) {
super(self.requestManager, self.registeredSubscriptions);

this.parentContract = parentContract;
}

/**
* Will create a new subscription
*
* @param name - The subscription you want to subscribe to
* @param args - Optional additional parameters, depending on the subscription type
* @param returnFormat- ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) - Specifies how the return data from the call should be formatted.
*
* Will subscribe to a specific topic (note: name)
* @returns The subscription object
*/
public async subscribe<T extends keyof RegisteredSubs>(
name: T,
args?: ConstructorParameters<RegisteredSubs[T]>[0],
returnFormat: DataFormat = DEFAULT_RETURN_FORMAT,
): Promise<InstanceType<RegisteredSubs[T]>> {
return super.subscribe(name, args ?? this.parentContract.options, returnFormat);
}
}
27 changes: 21 additions & 6 deletions packages/web3-eth-contract/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ import {
encodeEventABI,
encodeMethodABI,
} from './encoding.js';
import { LogsSubscription } from './log_subscription.js';
import { ContractLogsSubscription } from './contract_log_subscription.js';
import {
ContractEventOptions,
NonPayableMethodObject,
Expand All @@ -123,6 +123,8 @@ import {
} from './utils.js';
// eslint-disable-next-line import/no-cycle
import { DeployerMethodClass } from './contract-deployer-method-class.js';
// eslint-disable-next-line import/no-cycle
import { ContractSubscriptionManager } from './contract-subscription-manager.js';

type ContractBoundMethod<
Abi extends AbiFunctionFragment,
Expand Down Expand Up @@ -179,9 +181,9 @@ export type ContractMethodSend = Web3PromiEvent<
* ```
*
* @param options - The options used to subscribe for the event
* @returns - A Promise resolved with {@link LogsSubscription} object
* @returns - A Promise resolved with {@link ContractLogsSubscription} object
*/
export type ContractBoundEvent = (options?: ContractEventOptions) => LogsSubscription;
export type ContractBoundEvent = (options?: ContractEventOptions) => ContractLogsSubscription;

// To avoid circular dependency between types and encoding, declared these types here.
export type ContractEventsInterface<
Expand All @@ -204,11 +206,13 @@ export type ContractEventEmitterInterface<Abi extends ContractAbi> = {
type EventParameters = Parameters<typeof encodeEventABI>[2];

const contractSubscriptions = {
logs: LogsSubscription,
logs: ContractLogsSubscription,
newHeads: NewHeadsSubscription,
newBlockHeaders: NewHeadsSubscription,
};

type ContractSubscriptions = typeof contractSubscriptions;

/**
* The `web3.eth.Contract` makes it easy to interact with smart contracts on the ethereum blockchain.
* For using contract package, first install Web3 package using: `npm i web3` or `yarn add web3` based on your package manager, after that contracts features can be used as mentioned in following snippet.
Expand Down Expand Up @@ -406,9 +410,15 @@ const contractSubscriptions = {
*
*/
export class Contract<Abi extends ContractAbi>
extends Web3Context<EthExecutionAPI, typeof contractSubscriptions>
extends Web3Context<EthExecutionAPI, ContractSubscriptions>
implements Web3EventEmitter<ContractEventEmitterInterface<Abi>>
{
protected override _subscriptionManager: ContractSubscriptionManager<EthExecutionAPI>;

public override get subscriptionManager(): ContractSubscriptionManager<EthExecutionAPI> {
return this._subscriptionManager;
}

/**
* The options `object` for the contract instance. `from`, `gas` and `gasPrice` are used as fallback values when sending transactions.
*
Expand Down Expand Up @@ -564,6 +574,11 @@ export class Contract<Abi extends ContractAbi>
registeredSubscriptions: contractSubscriptions,
});

this._subscriptionManager = new ContractSubscriptionManager<
EthExecutionAPI,
ContractSubscriptions
>(super.subscriptionManager, this);

// Init protected properties
if ((contractContext as Web3Context)?.wallet) {
this._wallet = (contractContext as Web3Context).wallet;
Expand Down Expand Up @@ -1414,7 +1429,7 @@ export class Contract<Abi extends ContractAbi>
abi,
params[0] as EventParameters,
);
const sub = new LogsSubscription(
const sub = new ContractLogsSubscription(
{
address: this.options.address,
topics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import { Web3RequestManager, Web3Subscription, Web3SubscriptionManager } from 'w
import { decodeEventABI } from 'web3-eth';

/**
* LogSubscription to be used to subscribe to events logs.
* ContractLogsSubscription to be used to subscribe to events logs.
*
* Following events are supported and can be accessed with either {@link LogsSubscription.once} or ${@link LogsSubscription.on} methods.
* Following events are supported and can be accessed with either {@link ContractLogsSubscription.once} or ${@link ContractLogsSubscription.on} methods.
*
* - **connected**: Emitted when the subscription is connected.
* - **data**: Fires on each incoming event with the event object as argument.
Expand Down Expand Up @@ -81,7 +81,7 @@ import { decodeEventABI } from 'web3-eth';
* }
* ```
*/
export class LogsSubscription extends Web3Subscription<
export class ContractLogsSubscription extends Web3Subscription<
{
data: EventLog;
changed: EventLog & { removed: true };
Expand Down Expand Up @@ -162,3 +162,7 @@ export class LogsSubscription extends Web3Subscription<
return decodeEventABI(this.abi, data as LogsInput, this.jsonInterface, super.returnFormat);
}
}
/**
* @deprecated LogsSubscription is renamed to ContractLogsSubscription in this package to not be confused with LogsSubscription at 'web3-eth'.
*/
export const LogsSubscription = ContractLogsSubscription;
6 changes: 5 additions & 1 deletion packages/web3-eth-contract/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,15 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
import { Contract } from './contract.js';

import { ContractLogsSubscription } from './contract_log_subscription.js';
/** @deprecated Use `ContractLogsSubscription` instead. */
export type LogsSubscription = ContractLogsSubscription;

export * from './encoding.js';

export * from './contract.js';
export * from './contract-deployer-method-class.js';
export * from './log_subscription.js';
export * from './contract_log_subscription.js';
export * from './types.js';
export * from './utils.js';

Expand Down
4 changes: 2 additions & 2 deletions packages/web3-eth-contract/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
} from 'web3-types';
import { NewHeadsSubscription, SendTransactionEvents } from 'web3-eth';
import type { ContractOptions } from 'web3-types';
import { LogsSubscription } from './log_subscription.js';
import { ContractLogsSubscription } from './contract_log_subscription.js';

export type NonPayableTxOptions = NonPayableCallOptions;
export type PayableTxOptions = PayableCallOptions;
Expand Down Expand Up @@ -462,7 +462,7 @@ export type Web3ContractContext = Partial<
Web3ContextInitOptions<
EthExecutionAPI,
{
logs: typeof LogsSubscription;
logs: typeof ContractLogsSubscription;
newHeads: typeof NewHeadsSubscription;
newBlockHeaders: typeof NewHeadsSubscription;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/web3-eth-contract/test/fixtures/erc20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import { Address, Numbers } from 'web3-types';
import { LogsSubscription } from '../../src/log_subscription';
import { ContractLogsSubscription } from '../../src/contract_log_subscription';
import { ContractEventOptions, PayableMethodObject, NonPayableMethodObject } from '../../src/types';

export interface Erc20Interface {
Expand Down Expand Up @@ -53,9 +53,9 @@ export interface Erc20Interface {
};

events: {
[key: string]: (options?: ContractEventOptions) => LogsSubscription;
Approval: (options?: ContractEventOptions) => LogsSubscription;
Transfer: (options?: ContractEventOptions) => LogsSubscription;
[key: string]: (options?: ContractEventOptions) => ContractLogsSubscription;
Approval: (options?: ContractEventOptions) => ContractLogsSubscription;
Transfer: (options?: ContractEventOptions) => ContractLogsSubscription;
};
}

Expand Down
10 changes: 5 additions & 5 deletions packages/web3-eth-contract/test/fixtures/erc721.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import { Address, Numbers } from 'web3-types';
import { LogsSubscription } from '../../src/log_subscription';
import { ContractLogsSubscription } from '../../src/contract_log_subscription';
import { ContractEventOptions, NonPayableMethodObject, PayableMethodObject } from '../../src/types';

export interface Erc721Interface {
Expand Down Expand Up @@ -55,10 +55,10 @@ export interface Erc721Interface {
};

events: {
[key: string]: (options?: ContractEventOptions) => LogsSubscription;
Transfer: (options?: ContractEventOptions) => LogsSubscription;
Approval: (options?: ContractEventOptions) => LogsSubscription;
ApprovalForAll: (options?: ContractEventOptions) => LogsSubscription;
[key: string]: (options?: ContractEventOptions) => ContractLogsSubscription;
Transfer: (options?: ContractEventOptions) => ContractLogsSubscription;
Approval: (options?: ContractEventOptions) => ContractLogsSubscription;
ApprovalForAll: (options?: ContractEventOptions) => ContractLogsSubscription;
};
}

Expand Down
38 changes: 37 additions & 1 deletion packages/web3-eth-contract/test/unit/log_subscription.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

import * as eth from 'web3-eth';
import { WebSocketProvider } from 'web3-providers-ws';
import { Contract } from '../../src';
import { Web3SubscriptionManager } from 'web3-core';
import { Contract, ContractLogsSubscription } from '../../src';
import { GreeterAbi, GreeterBytecode } from '../shared_fixtures/build/Greeter';

jest.mock('web3-eth');
Expand Down Expand Up @@ -76,4 +77,39 @@ describe('contract log subscription', () => {
],
});
});

it('should be able to subscribe to logs with contractInstance.subscriptionManager.subscribe', async () => {
const address = '0x407D73d8a49eeb85D32Cf465507dd71d507100c1';
const contractInstance = new Contract(GreeterAbi, address);

jest.spyOn(WebSocketProvider.prototype, 'request').mockImplementation(
async (payload: any) => {
return {
jsonrpc: '2.0',
id: payload.id,
result: {},
};
},
);

jest.spyOn(Web3SubscriptionManager.prototype, 'subscribe').mockImplementation(
async (name: string | number | symbol, args?: any) => {
expect(name).toBe('logs');
expect(args.address).toBe(address);

return new ContractLogsSubscription(args, {
subscriptionManager: contractInstance.subscriptionManager,
});
},
);

contract.setProvider(providerString);

const sub = contractInstance.subscriptionManager.subscribe('logs');
expect(await sub).toBeInstanceOf(ContractLogsSubscription);

contractInstance.subscriptionManager.clear();

contractInstance.provider?.disconnect();
});
});

1 comment on commit ed85cce

@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: ed85cce Previous: fab66e9 Ratio
processingTx 22475 ops/sec (±7.29%) 22533 ops/sec (±6.62%) 1.00
processingContractDeploy 37859 ops/sec (±7.67%) 39420 ops/sec (±7.63%) 1.04
processingContractMethodSend 16019 ops/sec (±6.80%) 16012 ops/sec (±7.57%) 1.00
processingContractMethodCall 27428 ops/sec (±6.22%) 27328 ops/sec (±7.22%) 1.00
abiEncode 42843 ops/sec (±7.53%) 42745 ops/sec (±7.84%) 1.00
abiDecode 29934 ops/sec (±7.71%) 29680 ops/sec (±8.64%) 0.99
sign 1501 ops/sec (±3.06%) 1484 ops/sec (±3.35%) 0.99
verify 368 ops/sec (±0.51%) 364 ops/sec (±0.49%) 0.99

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

Please sign in to comment.