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

Suggestion to simplify Chainlink plugin usage by importing and reexporting modules #5596

Closed
2 tasks done
Muhammad-Altabba opened this issue Nov 7, 2022 · 5 comments
Closed
2 tasks done
Assignees
Labels
4.x 4.0 related

Comments

@Muhammad-Altabba
Copy link
Contributor

Muhammad-Altabba commented Nov 7, 2022

This is not a Bug Report, Feature Request, or related to Documentation

  • I have searched the existing issues

Is there an existing issue for this?

  • I have searched the existing issues

What's up?

The current implementation of Chainlink plugin implies that the plugin user will need to do the module augmentation.
However, I suggest importing and then reexporting web3.js objects by the plugin writer (plugin author).
This would be basically by doing this:

By the plugin writer

At file named for example reexported_web3_objects:
import Web3 from 'web3';
import { Web3Context } from 'web3-core';
import Web3Eth from 'web3-eth';

export { Web3, Web3Context, Web3Eth };
At another file, chainlink_plugin.ts for example:
import Web3 from 'web3';
import { Web3Context } from 'web3-core';
import Web3Eth from 'web3-eth';
import { ChainlinkPlugin } from './chainlink_plugin';


export class ChainlinkPlugin extends Web3PluginBase {
        ....
}

// Module Augmentation
declare module './reexported_web3_objects' {
	interface Web3Context {
		chainlink: ChainlinkPlugin;
	}

	interface Web3Eth {
		chainlink: ChainlinkPlugin;
	}

	interface Web {
		chainlink: ChainlinkPlugin;
	}
}

export { Web3, Web3Context, Web3Eth };

By the plugin user

And then the plugin user (plugin consumer) needs to do the import of Web3 objects from the plugin package. And so, the plugin user would import Web3 like this:

import {
  ChainlinkPlugin,
  Web3,
} from '@chainsafe/web3.js-chainlink-plugin';

Background note: This was all needed to keep backward compatibility for how web3.js users can import Web3 object(s). But that required a trick for module augmentation. And here is more on this point: #5393 (comment)

Note: this implementation was inspired by #5393 (comment) and ChainSafe/web3.js-plugin-chainlink#1 (comment)

@Muhammad-Altabba
Copy link
Contributor Author

Muhammad-Altabba commented Nov 7, 2022

And here is the implementation of this suggestion along with a section added to README.md file on how the user can use it: ChainSafe/web3.js-plugin-chainlink#4

@Muhammad-Altabba Muhammad-Altabba self-assigned this Nov 7, 2022
@Muhammad-Altabba Muhammad-Altabba added the 4.x 4.0 related label Nov 7, 2022
@spacesailor24
Copy link
Contributor

The issue with this is that the plugin author still doesn't know what module the plugin user is going to be augmenting by registering the plugin. In your above example, Web3Context, Web3Eth, and Web3 will all be augmented to have the ChainlinkPlugin interface, but if the plugin user code looks like:

const web3Eth = new Web3Eth(someProvider);
web3Eth.registerPlugin(new ChainlinkPlugin());

Then the only module that will actually have a .chainlink object added to it is the Web3Eth instance, but TypeScript will think any instance of Web3Context and Web3 will also have access to .chainlink which they won't unless registerPlugin is called on an instance of those specific classes

@spacesailor24 spacesailor24 reopened this Nov 8, 2022
@Muhammad-Altabba
Copy link
Contributor Author

The issue with this is that the plugin author still doesn't know what module the plugin user is going to be augmenting by registering the plugin. In your above example, Web3Context, Web3Eth, and Web3 will all be augmented to have the ChainlinkPlugin interface, but if the plugin user code looks like:

const web3Eth = new Web3Eth(someProvider);
web3Eth.registerPlugin(new ChainlinkPlugin());

Then the only module that will actually have a .chainlink object added to it is the Web3Eth instance, but TypeScript will think any instance of Web3Context and Web3 will also have access to .chainlink which they won't unless registerPlugin is called on an instance of those specific classes

Exactly, the plugin writer (the plugin author) will enable the typescript intellisense for his plugin for all Web3Context, Web3Eth, and Web3 through module augmentation. Because this should never be left to the plugin user (the plugin consumer). Because the plugin user will never do it (as Marin said "trust me, they won't, they will just start using ts-ignore or casting to any").

And it is the responsibility of the plugin user to call registerPlugin on the instance that he needs to use the plugin with.

@spacesailor24
Copy link
Contributor

spacesailor24 commented Nov 9, 2022

This code:

// Module Augmentation
declare module './reexported_web3_objects' {
	interface Web3Context {
		chainlink: ChainlinkPlugin;
	}


	interface Web3Eth {
		chainlink: ChainlinkPlugin;
	}


	interface Web {
		chainlink: ChainlinkPlugin;
	}
}

can be refactored to be:

// Module Augmentation
declare module './reexported_web3_objects' {
	interface Web3Context {
		chainlink: ChainlinkPlugin;
	}
}

And this export:

export { Web3, Web3Context, Web3Eth };

can be removed as it's not needed.


The above refactoring of the module augmentation resolves my concern that the plugin author is unaware of what module/class the plugin user would be registering the plugin onto, and would therefore need to augment all Web3 modules that extend Web3Context. Augmenting Web3Context means any class that extends Web3Context will have it's interface augmented to include the plugin's interface. This however is still less than ideal because TSC (and by extention, intellisense) will think every class that extends Web3Context will have the plugin object available to access (i.e. web3Eth.chainlink), even when the plugin hasn't been registered for a class instance

After implementing the above module augmentation refactor, the following should show the type error via intellisense or TSC, but instead the error only appears when attempting to run the code:

const web3EthContract = new Contract(AggregatorV3InterfaceABI, { provider: 'http://127.0.0.1:8545' });
await web3EthContract.chainlink.getPrice(MainnetPriceFeeds.LinkEth);
    TypeError: Cannot read properties of undefined (reading 'getPrice')

      42 |              it('Should call ChainlinkPlugin.getPrice', async () => {
      43 |                      const web3EthContract = new Contract(AggregatorV3InterfaceABI, { provider: 'http://127.0.0.1:8545' });
    > 44 |                      await web3EthContract.chainlink.getPrice(MainnetPriceFeeds.LinkEth);

this is because we've augmented Web3Context to include chainlink, but have not registered the plugin like so:
web3EthContract.registerPlugin(new ChainlinkPlugin());

Even with this limitation, this solution is still probably better than passing the burden of module augmentation onto the user. Instead, the user will just have to be made aware that they must call registerPlugin on an instance of a class/module they intend to use the plugin on


Here is a PR for the above changes

@Muhammad-Altabba
Copy link
Contributor Author

Muhammad-Altabba commented Nov 10, 2022

Great to see that we are aligned on this now 😄
And, thanks for the suggestion, the plugin writer needs to augment only the Web3Context object.

However, the plugin writer cannot skip reexporting the augmented object. As I described here: ChainSafe/web3.js-plugin-chainlink#5 (review)

@spacesailor24 spacesailor24 self-assigned this Nov 10, 2022
spacesailor24 added a commit that referenced this issue Nov 29, 2022
* WIP WIP plugin doc refactors

* Add module augmentation to CustomRpcMethodsPlugin

* Remove module augmentation from CustomRpcMethodsPlugin tests

* Init reexport_web3_context.ts

* Refactor module augmentation for CustomRpcMethodsPlugin

* Remove no longer needed assets

* Finish plugin doc refactors

* Update ContractMethodWrappersPlugin and tests

* Apply suggestions from code review

Co-authored-by: Muhammad Altabba <[email protected]>

* Update example plugin version from 0.0.1 to 0.1.0. Reword part of Using the Inherited Web3Context section

* Grammer changes in A Quick Disclaimer section

* Reword Re-declaring the Module section

* Replace usage of we and our

* Update use of "side effect" for Web3Context augmentation

* Replace usage of SimplePlugin with CustomRpcMethods

* Update docs/docs/guides/web3_plugin_guide/plugin_authors.md

Co-authored-by: Muhammad Altabba <[email protected]>

* Add Extending Web3EthPluginBase section

* Grammer fix

Co-authored-by: Muhammad Altabba <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4.x 4.0 related
Projects
None yet
Development

No branches or pull requests

2 participants