-
Notifications
You must be signed in to change notification settings - Fork 495
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
feat(@embark/plugins): introduce API to register test contract factories #1131
Conversation
Another thing to be aware of: I realized that the plugin doesn't get executed when written in fat arrow syntax. E.g.:
|
@@ -120,6 +121,11 @@ Plugin.prototype.registerContractsGeneration = function(cb) { | |||
this.addPluginType('contractGeneration'); | |||
}; | |||
|
|||
Plugin.prototype.registerTestContractFactory = function(cb) { | |||
this.testContractFactory = cb; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So there's something to keep in mind and probably needs further discussion:
The current plugin API doesn't work very well for use cases like registering a contract factory for testing scenario. The reason for that is that there could be multiple plugins and use this API. Then the question is: which one is going to win? There can be only one factory function at a time.
The way it is done right now is that we always only take the last one that is registered (which could confuse people).
Another thing that wasn't clear to me, if this factory should affect both, embark test
and embark console
.
I could imagine we probably don't want to blindly use the same factory for both scenarios, which is why in a follow up commit, there'd be a registerConsoleContractFactory()
API. The same problems exist there as well though.
Over all this API addition feels like it needs more thought. Probably better done in an iteration where we rethink our entire Plugin API where needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the plugin API is really made so that the last one gets applied. It works well in the case of internal vs plugin, as the plugin will always win, which makes sense.
However, like you pointed, if there are multiple plugins that do the same thing, then it gets confusing.
We never had that issue before because the number of plugins is still small and no one tried to use two plugins that do the same thing at the same time (eg: two plugins to compile solidity files).
One thing to check is if the order in which we put the plugins in embark.json affects the order of application of the plugin, if so, it can be a workaround if we document it.
@@ -268,7 +269,9 @@ class Test { | |||
self.contracts[contract.className] = {}; | |||
} | |||
|
|||
const newContract = Test.getWeb3Contract(contract, web3); | |||
const testContractFactoryPlugin = self.plugins.getPluginsFor('testContractFactory').slice(-1)[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, we're always taking the last one that has been registered as there can be only one. Hacky feelings coming up here when reading this code. =/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there's more than one, could we chain them together, e.g. the return value of the first becomes input to the second, and so on (or reverse order)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually believe that could break many scenarios.
This factory function allows you to do whatever you want. So if we pipe the return value in to other functions, they can never know what exactly they'll be getting.
I'm also not sure whether there would be a reasonable scenario to have multiple plugins that would alter this behaviour. I'm pretty close to saying that this should be a singleton-last-one-wins setting..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, perhaps in a later refactoring we should make it an error to supply more than one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make this plugin API generic (console and test and whetever)
The user himself can control the context in which he wants to execute the plugin using the context api.
This API was crated a while back, but it never was super used I think, but this here is agreat use case for it. Here's the docs for it: https://embark.status.im/docs/installing_plugins.html
Basicall,y it lets the user put an array of when the plguin should be executed. Something like
context: ['test']
Would enable it only in the tests
@@ -120,6 +121,11 @@ Plugin.prototype.registerContractsGeneration = function(cb) { | |||
this.addPluginType('contractGeneration'); | |||
}; | |||
|
|||
Plugin.prototype.registerTestContractFactory = function(cb) { | |||
this.testContractFactory = cb; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the plugin API is really made so that the last one gets applied. It works well in the case of internal vs plugin, as the plugin will always win, which makes sense.
However, like you pointed, if there are multiple plugins that do the same thing, then it gets confusing.
We never had that issue before because the number of plugins is still small and no one tried to use two plugins that do the same thing at the same time (eg: two plugins to compile solidity files).
One thing to check is if the order in which we put the plugins in embark.json affects the order of application of the plugin, if so, it can be a workaround if we document it.
84a8c3d
to
b9b34db
Compare
@jrainville I've updated the PR and now use the same function for both scenarios, console and testing. This still comes with a couple of characteristics we should document and make ppl aware of (I still think two different APIs would be better here). Those characteristics are:
So not only is the usage of that API different, depending on where it's used (console and testing), but it also behaves entirely different. I don't have much better ideas now apart from better introducing two APIs for this, but overall this feels rather sloppy to me. Hope we can iron this out with an API redesign in the future.. |
b9b34db
to
635c5cf
Compare
I just checked what the console actually uses and it's not using So knowing that, we can either:
There is probably a new solution, but I agree that things aren't super clean with either solutions. |
@jrainville we might be talking about two different consoles here, but I've tested it with |
So just to clarify, here's what I understood when we talked "making it work inside embark console as well":
I have to admit that I was wondering how that use case is feasible, considering that the function expects parameters that one has to pass manually in that case. But maybe that's exactly where we're talking about two different things... |
@PascalPrecht For the console case, what we want is the global contracts available in the console to be generated using the new generator. |
Okay, I clearly misunderstood the desired outcome of this then. Will update the PR. |
Quick update here @jrainville: after some further digging I found out that templates created using So functionality-wise, we already have that. The problem however is that, whatever is generated in global scope is undefined within the console. I then found out that the code that Embark generates by default is actually not the one you've linked, but this one: https://github.com/embark-framework/embark/blob/master/src/lib/modules/code_generator/code_templates/embarkjs-contract.js.ejs The crucial difference here seems to be that this template attaches the contract instances to
If you write a plugin that looks like this:
The code does end up in the generated ABI, but it's not attached to |
Actually, turns out that even putting it on I changed
To
But executing that generated functions still causes an undefined error... need to dig further here.. |
Ah, so it turns out there's a little bit more happening by default:
I assume that |
Scratch that too. Generating the same code within a plugin still doesn't do it. Digging further.. |
Quick update here for transparency:
I'm on it and hopefully have this done tomorrow. |
782c599
to
4aa2451
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright this is updated now and I think needs another iteration for feedback and review.
Here's what currently going on here:
- we introduce two APIs:
registerTestContractFactory()
andregisterCustomContractGenerator()
- Two APIs because Embark is doing two different things for different scenarios. Contract instances using test factory are created through execution of the factory. Contract instances created through generator are eval'd and made available inside
embark console
- None of these APIs actually affect how contract instances are created inside the dapp. This is handled with yet another routine in our pipeline, where JS code is generated and written to disk.
- It's also important to keep in mind that, when using deployment hooks with the string API, contract instances created for those will be the ones generated from the custom generator as well, however, when function APIs are used, those will be different objects controlled by Embark.
Overall I have to say that I'm rather unhappy with how these APIs behave at the moment. It probably needs a bit more thought to figure out how this can be streamlined and made more predictable. At the time being, I'd almost vote for not adding this feature here to avoid introducing a lot of confusion.
Happy to discuss this with the team.
self.events.request('runcode:eval', contractCode, () => {}, true); | ||
return callback(); | ||
self.events.request('code-generator:contract:custom', contract, (customContractCode) => { | ||
if (!customContractCode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that we first check if there's a custom contract code generator registered that returns dedicated code. We fallback to the vanilla contract creation in case non had been registered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also notice that this gets executed for all contracts, including the ones that Embark comes with (e.g. ENSRegistry
etc).
We might want to check for built-in vs owned contracts and only do this for the ones owned by the developer.
Thoughts?
Welp, I think my review didn't publish for some obscure reason. Here is is again: I think the API should take care of calling a fallback. Example: we calling the custom contracts API, the API should get the vanilla one if there is no custom. In an ideal world, we use the plugin API too so that the vanilla contract comes from us through the plugin API, so that way the fallback is automatically handled, but we'll need to refactor the code generator first. |
@jrainville are you referring to
I read this part a couple of times now but it doesn't really make sense to me, sorry. Would you mind elaborating on what you mean by "in an ideal world we use the plugin API too" ? |
Yes
Sorry, it is indeed unclear. What I mean is that we should have used a plugin ourselves when we did the vanilla contract generation. |
d1ca610
to
05218c3
Compare
// just need to figure out the gasLimit coupling issue | ||
self.events.request('code-generator:contract:vanilla', contract, contract._gasLimit || false, (contractCode) => { | ||
|
||
self.events.request('code-generator:contract:custom', contract, (contractCode) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made the change as requested. Notice that we now always ask for custom generated code and then fallback to vanilla generated code behind the scenes. I personally don't like it so much because it's less clear what's going on, e.g. I'll have to dig in the code generator to find out that :custom
actually returns code from the vanilla generator.
Anyways, something we might want to reconsider in the future.
This commit introduces two new plugin APIs `registerTestContractFactory()` and `registerCustomContractGenerator()`, which can be used to register a factory function for the creation of web3 contract instances within tests, and custom code generation for `embark console` respectively. Example: ``` // some.plugin.js module.exports = function (embark) { embark.registerTestContractFactory(function (contractRecipe, web3) { // do something with web3 and contractRecipe and return contract instance here }); }; ``` **Notice that**: - This factory function is used for contract instance creation within tests. A `contractRecipe` and a `web3` instance is accessible within the factory. Example: ``` // some.plugin.js module.exports = function (embark) { embark.registerCustomContractGenerator(function (contractRecipe) { // returns code string that will be eval'ed }); }; ``` **Notice that**: - Once registered, this generator will be used for **all** contract instances that will be created for `embark console`, including built-in once like ENSRegistry. - While this does affect contract creation in client-side code, it doesn't actually affect the instances created for deployment hooks **if** deployment hooks are written as functions. Closes #1066 Always use custom generator and fallback to vanilla
05218c3
to
b81361d
Compare
This commit introduces a new API
registerTestContractFactory()
which can beused to register a factory function for the creation of web3 contract instances
in the testing environment.
Example:
Closes #1066