-
-
Notifications
You must be signed in to change notification settings - Fork 48
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
Add Plugin support #53
Conversation
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.
Thanks very much @cleanunicorn, this is looking great so far. I left my feedback below.
Additionally, some notes:
- I didn't finish the migration to Foundry in this repo, so wherever you feel like a component is missing, please do take a look at prb-math and prb-contracts for inspiration, because both are already shipped.
- It looks like you have committed the
artifacts
in version control - perhaps this was a left-over from a previous checkout in which you had compiled the PRBProxy contracts with Hardhat. The Hardhat artifacts should be deleted. - The tests are not structured correctly.
- There is no need for a base
PluginTest
because the plugin functionality is incorporated inPRBProxy
, and so the plugin-related events and variables should be added in the existingPRBProxyTest
contract. - Crucially, this repo follows the testing trees/ branching approach. A fellow Ethereum developer called Sabnock who likes this testing approach wrote an article about it (he calls it "state building").
- The easiest way to understand what I mean by this would be to take a look at the PRBMath test trees and the PRBContract test trees
- To ensure a smooth developer experience while writing the tests trees, I recommend installing the vscode-tree-language VSCode extension to get syntax highlighting for the
*.tree
files. - The tests are meant to be separated with empty modifiers.
- There is no need for a base
- While the Angular Commit Message Format doesn't have many hard-and-fast rules, I would have used different commit types than the ones that you have used so far:
- Implementing the plugin is a change of type
feat
instead ofchore
. - Adding events in a smart contract is a change of type
feat
instead ofchore
(because you add functionality); also, when bundling lots of charge under the roof of one commit, it's helpful to add more information in the git commit body, e.g. in commit 306d0ef an additionaldocs: document plugin functions with NatSpec
would have been helpful. - Adding tests is a change of type
test
instead ofchore
. - Fixing linting/ styling errors is a change of type
chore
orstyle
instead offix
(the latter is reserved for smart contract bug fixes).
- Implementing the plugin is a change of type
And some questions:
- I'm just thinking out loud, but doesn't this plugin functionality introduce a vulnerability whereby a malicious target contract can call any arbitrary plugin by calling the fallback function on the proxy itself, after the user has called the target contract via the normal
execute
pathway? - May I have your permission to edit the git commit messages based on the feedback above? Or, alternatively, you could do it. The idea would be to git push with
--force
.
956daf9
to
8ef8223
Compare
We will need to think deeply about security issues that might appear since this does a delegatecall. We should properly think about attack vectors once the plugin system is closer to finality. Currently there is no permission schema for plugins. I don't understand the example you're explaining. I'm not sure this is an issue since the executed code should still be trusted (is this right?). Could you add more details? Also, it's important to understand trusted and untrusted actors in the system.
I tried rewriting, but feel free to push commits (rewrite history), I gave you permission. |
Excuse my interjection here :) Re:
It might be worth having a plugin (or method) type which gets only invoked with a staticcall instead of delegatecall. |
Open questions:
|
Assurance of a reduced attack surface with non state changing calls would allow a more modular security policy for plugin installers. i.e. being way more relaxed with read only plugins. Several use cases could benefit, first and foremost EIP1271 implementations ofc. But also I suppose most callbacks for ERC721/1155 safe transfers. |
Replies
I think you're right - if you give permission to a target contract and you delegate-call to it, then if they are malicious they can do other things directly. They don't need to jump through hoops and use the plugin system. Target contracts are indeed trusted actors under the current design of PRBProxy, and the plugin system you have implemented is orthogonal to it.
Thank you! I pushed with NOTE: you might have to delete your local
Good catch, but I think that this is fine. Plugins should normally not overlap, just like ERCs don't usually overlap. The complexity of implementing an overwrite check, and the maintenance cost do not seem worth it to me. Similarly, with the Also, this is a rectifiable scenario - in case a user accidentally overwrites a plugin, they can simply uninstall it, and then install a new one that doesn't have the overwritten methods.
In a similar vein to the above, I would classify this as a rectifiable issue that can easily be patched manually by the user. I would have been open to implementing these checks if we had a different access control schema that allowed third-parties to call the But, since in our implementation, it is the user only who can install and uninstall plugins, they should be well aware of what they are installing. Also worth mentioning is that the "user" referred to above wouldn't actually be the end user, i.e. the owner of the proxy - but rather a frontend developer who would parse the proxy API on behalf of the actual end user. FeedbackOn top of what I left in the comments above. 1. Test Function NamesThis is something that I learned in the meantime - a few Foundry contributors are working on a new Foundry-based linter for Solidity called scopelint, which will use a specific regex for linting test function names. See point 2 in General Test Guidance section of the Foundry Book for more detail; for now, what matters is that I have updated your test names to adhere to these best practices. I will also update the base branch 2. Name of
|
Also, @cleanunicorn, FYI - I have pushed a big commit to address all of the feedback left in the code review comments. I am still due to refactor the tests to adhere to the testing tree approach - but once that's done, we can merge this in, after you review my feedback, too, of course. Update: I have managed to complete the migration to Foundry on the |
c17b588
to
682ae1f
Compare
b308058
to
15d23fd
Compare
682ae1f
to
980f8e2
Compare
c928a6c
to
23ac929
Compare
23ac929
to
89e6e7f
Compare
After pushing a few commits these days, we are now almost ready to merge this PR. @cleanunicorn, could you take a final look at the latest changes to confirm that you're happy with the aesthetic changes I made (e.g. rename |
89e6e7f
to
54aea42
Compare
Two final security-related points from my end. Strange LoopsIs it possible to install the proxy itself as a plugin by running the following steps:
Now, the fun part: calling any method not defined in the proxy itself will lead to infinite recursion in the fallback function, triggering a stack overflow. However, calling a method defined in the proxy would result in a successful transaction. The question is whether this can lead to any exploit. I don't think it can because all essential functions are protected by Therefore, adding an explicit check in the Empty DataWhen the calldata is empty, the |
There are conflicts again .. I will fix them tomorrow. |
54aea42
to
650a85a
Compare
feat: implements Plugin Install and Uninstall feat: add events when installing uninstalling plugins feat: add plugin execute tests fix: handle lint errors chore: remove artifacts folder refactor: change error names to match overall style refactor: reorder imports and methods to be alphabetically sorted refactor: reorder storage to be alphabetically refactor: move plugin tests and include plugin test setup in PRBProxyTest fix: use correct errors for plugin chore: remove unnecessary solhint ignore refactor: optimize loops by removing overflow checks chore: mark `methodList` as `pure` to silence linting warning test: check only owner can uninstall plugins test: split plugin install tests with modifiers fix: installing should ensure at least one method is installed docs: document plugin interface with NatSpec and update license to MIT test: reoder imports in execute test test: remove commented out code in TargetPluginRevert refactor: use "IPlugin" type for plugin addresses chore: fix formatting in ".gitmodules" docs: improve wording in NatSpec comments perf: cache array length refactor: add check for empty method list in "uninstallPlugin" refactor: add "plugin" param to "PRBProxy__PluginExecutionReverted" error refactor: organize functions in "PRBProxy" refactor: rename "getPluginForSignature" to "getPluginForMethod" refactor: rename "PRBProxy__PluginMethodNotFound" to "PRBProxy__NoPluginMethods" test: add "Plugins" struct test: add assertion to compare two "IPlugin" addresses test: move plugins to the "Plugins" struct entity test: rename "TargetPluginRevert" to just "PluginRevert" test: refactor plugin test names to conform to Foundry best practices test: separate "installPlugin" from "uninstallPlugin" tests refactor: fix rebase conflicts and issues docs: improve wording in NatSpec comments chore: improve wording in code comments docs: add "Notes" section in NatSpec comments of plugin functions test: add modifiers in "installPlugin" and "uninstallPlugin" tests test: add state trees for "installPlugin" and "uninstallPlugin" test: add test when plugin has not methods in "uninstallPlugin" tests test: add test when plugin installed in "installPlugin" tests test: add test when plugin not installed in "uninstallPlugin" tests test: explicitly define "actualPlugin" and "expectedPlugini" test: improve wording in comments and functions test: use "eve" instead of "bob" for "caller not owner" tests refactor: rename "IPlugin" to "IPRBProxyPlugin" feat: add "RunPlugin" event in fallback function chore: ignore "no-complex-fallback" in Solhint config chore: nest "ignored_error_codes" on multiple lines in Foundry config docs: enhance NatSpec code comments refactor: dry-ify delegate call in internal "_safeDelegateCall" function refactor: rename "PluginExecutionReverted" to "PluginReverted" refactor: rename "NoPluginFound" to "PluginNotInstalledForMethod" test: add "<0.9.0" pragma bound in all test files test: add more plugin helpers test: fix variable names in eth transfer tests test: fix typos in test descriptions test: fully test plugin runs test: improve wording in tests test: inherit from targets in plugins test: remove arithmetic underflow tests test: rename "revert" to "reverter" test: rename "Struct" to "SomeStruct" test: rename "TargetError" to "SomeError" test: rename "TargetRevert" to "TargetReverter" test: rename "TargetSelfDestruct" to "TargetSelfDestructer" test: test index OOB in "execute" function
650a85a
to
cb5d6ca
Compare
There were many conflicts and frankly I was unsure what was the fix for several of them - to avoid the risk of deleting something accidentally, I have squashed all commits in one commit (was going to do this anyway, before merging), and rebased. PR ready for your final review, @cleanunicorn! |
It is possible for the plugin to try to overwrite
Any method defined in
This should be regarded as receiving ether. I believe we desire this behavior. |
Thanks for the final review, @cleanunicorn! I agree with all the points you made. |
Implements #42.