-
Notifications
You must be signed in to change notification settings - Fork 29
feat: [v0.8-develop] remove plugin dependencies #86
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,62 +3,38 @@ pragma solidity ^0.8.25; | |
|
|
||
| import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; | ||
| import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; | ||
| import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; | ||
|
|
||
| import {FunctionReferenceLib} from "../helpers/FunctionReferenceLib.sol"; | ||
| import { | ||
| IPlugin, | ||
| ManifestExecutionHook, | ||
| ManifestFunction, | ||
| ManifestAssociatedFunctionType, | ||
| ManifestAssociatedFunction, | ||
| PluginManifest | ||
| } from "../interfaces/IPlugin.sol"; | ||
| import {IPlugin, ManifestExecutionHook, ManifestValidation, PluginManifest} from "../interfaces/IPlugin.sol"; | ||
| import {ExecutionHook} from "../interfaces/IAccountLoupe.sol"; | ||
| import {FunctionReference, IPluginManager} from "../interfaces/IPluginManager.sol"; | ||
| import {KnownSelectors} from "../helpers/KnownSelectors.sol"; | ||
| import {AccountStorage, getAccountStorage, SelectorData, toSetValue} from "./AccountStorage.sol"; | ||
|
|
||
| abstract contract PluginManagerInternals is IPluginManager { | ||
| using EnumerableSet for EnumerableSet.Bytes32Set; | ||
| using EnumerableSet for EnumerableSet.AddressSet; | ||
| using EnumerableMap for EnumerableMap.AddressToUintMap; | ||
| using FunctionReferenceLib for FunctionReference; | ||
|
|
||
| error ArrayLengthMismatch(); | ||
| error Erc4337FunctionNotAllowed(bytes4 selector); | ||
| error ExecutionFunctionAlreadySet(bytes4 selector); | ||
| error InvalidDependenciesProvided(); | ||
| error InvalidPluginManifest(); | ||
| error IPluginFunctionNotAllowed(bytes4 selector); | ||
| error MissingPluginDependency(address dependency); | ||
| error NativeFunctionNotAllowed(bytes4 selector); | ||
| error NullFunctionReference(); | ||
| error NullPlugin(); | ||
| error PluginAlreadyInstalled(address plugin); | ||
| error PluginDependencyViolation(address plugin); | ||
| error PluginInstallCallbackFailed(address plugin, bytes revertReason); | ||
| error PluginInterfaceNotSupported(address plugin); | ||
| error PluginNotInstalled(address plugin); | ||
| error ValidationFunctionAlreadySet(bytes4 selector, FunctionReference validationFunction); | ||
|
|
||
| modifier notNullFunction(FunctionReference functionReference) { | ||
| if (functionReference.isEmpty()) { | ||
| revert NullFunctionReference(); | ||
| } | ||
| _; | ||
| } | ||
|
|
||
| modifier notNullPlugin(address plugin) { | ||
| if (plugin == address(0)) { | ||
| revert NullPlugin(); | ||
| } | ||
| _; | ||
| } | ||
|
|
||
| // Storage update operations | ||
|
|
||
| function _setExecutionFunction(bytes4 selector, bool isPublic, bool allowDefaultValidation, address plugin) | ||
| internal | ||
| notNullPlugin(plugin) | ||
| { | ||
| SelectorData storage _selectorData = getAccountStorage().selectorData[selector]; | ||
|
|
||
|
|
@@ -99,25 +75,40 @@ abstract contract PluginManagerInternals is IPluginManager { | |
| _selectorData.allowDefaultValidation = false; | ||
| } | ||
|
|
||
| function _addValidationFunction(bytes4 selector, FunctionReference validationFunction) | ||
| internal | ||
| notNullFunction(validationFunction) | ||
| { | ||
| // Fail on duplicate validation functions. Otherwise, dependency validation functions could shadow | ||
| // non-depdency validation functions. Then, if a either plugin is uninstalled, it would cause a partial | ||
| // uninstall of the other. | ||
| if (!getAccountStorage().validationData[validationFunction].selectors.add(toSetValue(selector))) { | ||
| revert ValidationFunctionAlreadySet(selector, validationFunction); | ||
| function _addValidationFunction(address plugin, ManifestValidation memory mv) internal { | ||
| AccountStorage storage _storage = getAccountStorage(); | ||
|
|
||
| FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId); | ||
|
|
||
| if (mv.isDefault) { | ||
| _storage.validationData[validationFunction].isDefault = true; | ||
| } | ||
|
|
||
| if (mv.isSignatureValidation) { | ||
|
Comment on lines
+83
to
+87
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to keep validation installation in plugin installation? If not, not worth adding new code in. We talked about only keeping it in one place. While experimenting with validationId, validation installation within plugin installation is almost impossible.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's worth considering removing validation install via the manifest completely - this PR doesn't do that yet though, it just removes dependencies.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant that we don't need to add more code in here like checking |
||
| _storage.validationData[validationFunction].isSignatureValidation = true; | ||
| } | ||
|
|
||
| // Add the validation function to the selectors. | ||
| uint256 length = mv.selectors.length; | ||
| for (uint256 i = 0; i < length; ++i) { | ||
| bytes4 selector = mv.selectors[i]; | ||
| _storage.validationData[validationFunction].selectors.add(toSetValue(selector)); | ||
| } | ||
| } | ||
|
|
||
| function _removeValidationFunction(bytes4 selector, FunctionReference validationFunction) | ||
| internal | ||
| notNullFunction(validationFunction) | ||
| { | ||
| // May ignore return value, as the manifest hash is validated to ensure that the validation function | ||
| // exists. | ||
| getAccountStorage().validationData[validationFunction].selectors.remove(toSetValue(selector)); | ||
| function _removeValidationFunction(address plugin, ManifestValidation memory mv) internal { | ||
| AccountStorage storage _storage = getAccountStorage(); | ||
|
|
||
| FunctionReference validationFunction = FunctionReferenceLib.pack(plugin, mv.functionId); | ||
|
|
||
| _storage.validationData[validationFunction].isDefault = false; | ||
| _storage.validationData[validationFunction].isSignatureValidation = false; | ||
|
|
||
| // Clear the selectors | ||
| while (_storage.validationData[validationFunction].selectors.length() > 0) { | ||
| bytes32 selector = _storage.validationData[validationFunction].selectors.at(0); | ||
| _storage.validationData[validationFunction].selectors.remove(selector); | ||
| } | ||
| } | ||
|
|
||
| function _addExecHooks( | ||
|
|
@@ -146,16 +137,15 @@ abstract contract PluginManagerInternals is IPluginManager { | |
| ); | ||
| } | ||
|
|
||
| function _installPlugin( | ||
| address plugin, | ||
| bytes32 manifestHash, | ||
| bytes memory pluginInstallData, | ||
| FunctionReference[] memory dependencies | ||
| ) internal { | ||
| function _installPlugin(address plugin, bytes32 manifestHash, bytes memory pluginInstallData) internal { | ||
| AccountStorage storage _storage = getAccountStorage(); | ||
|
|
||
| if (plugin == address(0)) { | ||
| revert NullPlugin(); | ||
| } | ||
|
|
||
| // Check if the plugin exists. | ||
| if (!_storage.plugins.add(plugin)) { | ||
| if (_storage.pluginManifestHashes.contains(plugin)) { | ||
| revert PluginAlreadyInstalled(plugin); | ||
| } | ||
|
|
||
|
|
@@ -170,37 +160,11 @@ abstract contract PluginManagerInternals is IPluginManager { | |
| revert InvalidPluginManifest(); | ||
| } | ||
|
|
||
| // Check that the dependencies match the manifest. | ||
| if (dependencies.length != manifest.dependencyInterfaceIds.length) { | ||
| revert InvalidDependenciesProvided(); | ||
| } | ||
|
|
||
| uint256 length = dependencies.length; | ||
| for (uint256 i = 0; i < length; ++i) { | ||
| // Check the dependency interface id over the address of the dependency. | ||
| (address dependencyAddr,) = dependencies[i].unpack(); | ||
|
|
||
| // Check that the dependency is installed. | ||
| if (_storage.pluginData[dependencyAddr].manifestHash == bytes32(0)) { | ||
| revert MissingPluginDependency(dependencyAddr); | ||
| } | ||
|
|
||
| // Check that the dependency supports the expected interface. | ||
| if (!ERC165Checker.supportsInterface(dependencyAddr, manifest.dependencyInterfaceIds[i])) { | ||
| revert InvalidDependenciesProvided(); | ||
| } | ||
|
|
||
| // Increment the dependency's dependents counter. | ||
| _storage.pluginData[dependencyAddr].dependentCount += 1; | ||
| } | ||
|
|
||
| // Add the plugin metadata to the account | ||
| _storage.pluginData[plugin].manifestHash = manifestHash; | ||
| _storage.pluginData[plugin].dependencies = dependencies; | ||
| _storage.pluginManifestHashes.set(plugin, uint256(manifestHash)); | ||
|
|
||
| // Update components according to the manifest. | ||
|
|
||
| length = manifest.executionFunctions.length; | ||
| uint256 length = manifest.executionFunctions.length; | ||
| for (uint256 i = 0; i < length; ++i) { | ||
| bytes4 selector = manifest.executionFunctions[i].executionSelector; | ||
| bool isPublic = manifest.executionFunctions[i].isPublic; | ||
|
|
@@ -210,17 +174,9 @@ abstract contract PluginManagerInternals is IPluginManager { | |
|
|
||
| length = manifest.validationFunctions.length; | ||
| for (uint256 i = 0; i < length; ++i) { | ||
| ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; | ||
| _addValidationFunction( | ||
| mv.executionSelector, _resolveManifestFunction(mv.associatedFunction, plugin, dependencies) | ||
| ); | ||
| } | ||
|
|
||
| length = manifest.signatureValidationFunctions.length; | ||
| for (uint256 i = 0; i < length; ++i) { | ||
| FunctionReference signatureValidationFunction = | ||
| FunctionReferenceLib.pack(plugin, manifest.signatureValidationFunctions[i]); | ||
| _storage.validationData[signatureValidationFunction].isSignatureValidation = true; | ||
| // Todo: limit this to only "direct runtime call" validation path (old EFP), | ||
| // and add a way for the user to specify permission/pre-val hooks here. | ||
| _addValidationFunction(plugin, manifest.validationFunctions[i]); | ||
| } | ||
|
|
||
| length = manifest.executionHooks.length; | ||
|
|
@@ -243,7 +199,7 @@ abstract contract PluginManagerInternals is IPluginManager { | |
| revert PluginInstallCallbackFailed(plugin, revertReason); | ||
| } | ||
|
|
||
| emit PluginInstalled(plugin, manifestHash, dependencies); | ||
| emit PluginInstalled(plugin, manifestHash); | ||
| } | ||
|
|
||
| function _uninstallPlugin(address plugin, PluginManifest memory manifest, bytes memory uninstallData) | ||
|
|
@@ -252,54 +208,29 @@ abstract contract PluginManagerInternals is IPluginManager { | |
| AccountStorage storage _storage = getAccountStorage(); | ||
|
|
||
| // Check if the plugin exists. | ||
| if (!_storage.plugins.remove(plugin)) { | ||
| if (!_storage.pluginManifestHashes.contains(plugin)) { | ||
| revert PluginNotInstalled(plugin); | ||
| } | ||
|
|
||
| // Check manifest hash. | ||
| bytes32 manifestHash = _storage.pluginData[plugin].manifestHash; | ||
| bytes32 manifestHash = bytes32(_storage.pluginManifestHashes.get(plugin)); | ||
| if (!_isValidPluginManifest(manifest, manifestHash)) { | ||
| revert InvalidPluginManifest(); | ||
| } | ||
|
|
||
| // Ensure that there are no dependent plugins. | ||
| if (_storage.pluginData[plugin].dependentCount != 0) { | ||
| revert PluginDependencyViolation(plugin); | ||
| } | ||
|
|
||
| // Remove this plugin as a dependent from its dependencies. | ||
| FunctionReference[] memory dependencies = _storage.pluginData[plugin].dependencies; | ||
| uint256 length = dependencies.length; | ||
| for (uint256 i = 0; i < length; ++i) { | ||
| FunctionReference dependency = dependencies[i]; | ||
| (address dependencyAddr,) = dependency.unpack(); | ||
|
|
||
| // Decrement the dependent count for the dependency function. | ||
| _storage.pluginData[dependencyAddr].dependentCount -= 1; | ||
| } | ||
|
|
||
| // Remove components according to the manifest, in reverse order (by component type) of their installation. | ||
| length = manifest.executionHooks.length; | ||
|
|
||
| uint256 length = manifest.executionHooks.length; | ||
| for (uint256 i = 0; i < length; ++i) { | ||
| ManifestExecutionHook memory mh = manifest.executionHooks[i]; | ||
| FunctionReference hookFunction = FunctionReferenceLib.pack(plugin, mh.functionId); | ||
| EnumerableSet.Bytes32Set storage execHooks = _storage.selectorData[mh.executionSelector].executionHooks; | ||
| _removeExecHooks(execHooks, hookFunction, mh.isPreHook, mh.isPostHook); | ||
| } | ||
|
|
||
| length = manifest.signatureValidationFunctions.length; | ||
| for (uint256 i = 0; i < length; ++i) { | ||
| FunctionReference signatureValidationFunction = | ||
| FunctionReferenceLib.pack(plugin, manifest.signatureValidationFunctions[i]); | ||
| _storage.validationData[signatureValidationFunction].isSignatureValidation = false; | ||
| } | ||
|
|
||
| length = manifest.validationFunctions.length; | ||
| for (uint256 i = 0; i < length; ++i) { | ||
| ManifestAssociatedFunction memory mv = manifest.validationFunctions[i]; | ||
| _removeValidationFunction( | ||
| mv.executionSelector, _resolveManifestFunction(mv.associatedFunction, plugin, dependencies) | ||
| ); | ||
| _removeValidationFunction(plugin, manifest.validationFunctions[i]); | ||
| } | ||
|
|
||
| length = manifest.executionFunctions.length; | ||
|
|
@@ -314,7 +245,7 @@ abstract contract PluginManagerInternals is IPluginManager { | |
| } | ||
|
|
||
| // Remove the plugin metadata from the account. | ||
| delete _storage.pluginData[plugin]; | ||
| _storage.pluginManifestHashes.remove(plugin); | ||
|
|
||
| // Clear the plugin storage for the account. | ||
| bool onUninstallSuccess = true; | ||
|
|
@@ -334,20 +265,4 @@ abstract contract PluginManagerInternals is IPluginManager { | |
| { | ||
| return manifestHash == keccak256(abi.encode(manifest)); | ||
| } | ||
|
|
||
| function _resolveManifestFunction( | ||
| ManifestFunction memory manifestFunction, | ||
| address plugin, | ||
| FunctionReference[] memory dependencies | ||
| ) internal pure returns (FunctionReference) { | ||
| if (manifestFunction.functionType == ManifestAssociatedFunctionType.SELF) { | ||
| return FunctionReferenceLib.pack(plugin, manifestFunction.functionId); | ||
| } else if (manifestFunction.functionType == ManifestAssociatedFunctionType.DEPENDENCY) { | ||
| if (manifestFunction.dependencyIndex >= dependencies.length) { | ||
| revert InvalidPluginManifest(); | ||
| } | ||
| return dependencies[manifestFunction.dependencyIndex]; | ||
| } | ||
| return FunctionReferenceLib._EMPTY_FUNCTION_REFERENCE; // Empty checks are done elsewhere | ||
| } | ||
| } | ||
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.
Why not
AddressToBytes32Map?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.
Unfortunately, it doesn't exist: https://docs.openzeppelin.com/contracts/5.x/api/utils#EnumerableMap
This is the only map that has
addressas a key type.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.
We need to use a newer version!
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 think it is the newest version, haha
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.
Cannot be, it is in v5.1.
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.
Ah that's not released yet, check under the releases tab. I'd prefer to avoid using pre-release content, but we should update this when 5.1 is released.