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

fix: ensure contractInstance.subscriptionManager.subscribe is not throwing #7327

Merged
merged 6 commits into from
Oct 21, 2024
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();
});
});
Loading