-
Notifications
You must be signed in to change notification settings - Fork 25
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: [v0.8-develop] associate pre-validation hooks with validation functions #64
Conversation
e21f450
to
72be4f4
Compare
// Whether or not this validation can be used as a shared validation function. | ||
bool isShared; | ||
// Whether or not this validation is a signature validator. | ||
bool isSignatureValidation; |
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.
is it possible to add defaultValidations
as a bool field here?
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.
Yep that's what the isShared
flag is doing, it's whether or not it counts as a "default" validation. I think the word "default" implies that there's only one ("the default"), but in reality there can be many validations in the shared "default" pool, so I was kinda trying to avoid using that term. But I totally see how this is confusing, can change it or add a comment if it would help.
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.
Would be helpful yeah! How about global validators? Global validators are able to perform validation across all native selectors + installed execution selectors that opt in to global validation
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.
That's the same thing, right?
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.
yup, just a name suggestion, my contribution it to the list of names for the naming meeting :p
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 can punt, but I do like the idea of using "global" here. I think it's fine that "global validation functions" don't necessarily apply to all selectors, just like how global variables can be overriden.
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 can punt, but I do like the idea of using "global" here. I think it's fine that "global validation functions" don't necessarily apply to all selectors, just like how global variables can be overriden.
yeah that's the behavior I expect as well. We often adhere to default vs overridden
in our codebase, but the same idea.
// User operation validation and runtime validation share a function reference. | ||
// The execution hooks for this function selector. | ||
EnumerableSet.Bytes32Set executionHooks; | ||
// Which validation functions are associated with this function selector. | ||
EnumerableSet.Bytes32Set validations; |
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.
Thoughts on removing this entirely? What I'm thinking of:
- We just keep the global validation fns set, no more selector associated validation fns
- Permissions framework (pre val hooks attached to validation fns) is responsible for what selectors a val fn can operate on
If I'm not wrong this should remove the need to do an existence check across the global validations set + the selector associated validation set and simplify a lot of the checking logic around validation
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.
There's some flexibility in the shape of #2 - a simple MA could just do:
struct ValidationData {
bool isShared
bool isSignatureValidation
mapping(bytes4 selector -> bool canValidate) canValidateSelector
EnumerableSet.Bytes32Set preValidationHooks
}
to support 2 modes of validation functions - those that can be used either globally, or just for a set of selectors
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.
Without selector-associated validations, every validation would need a pre-hook just to do selector associations - it's one of those things that benefits from being builtin, but yes I agree we should be careful in what we do this for.
I actually thought about making the refactor to the format of mapping(bytes4 selector -> bool canValidate) canValidateSelector
, but there's one reason we can't do it just yet - the current interface of our loupe functions.
Right now, we have function getValidations(bytes4 selector) external view returns (FunctionReference[] memory)
, which returns the validations per selector. Even if we made the canValidateSelector
enumerable, we wouldn't be able to implement this interface efficiently, we'd need to search every single validation function.
But, if we change the view function to function getSelectors(FunctionReference validation) external view returns (bytes4[] memory)
, then we could make this refactor.
We should revisit this in the future, and decide which of these functions it actually makes sense to implement.
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 should capture this conversation somewhere. It is very meaningful, but is not in this PR's scope. @howydev Seems you proposed it. Maybe even capture as a proposal to the issues list.
In this world where there are only global validations, how are native functions' validation handled?
Would those native functions just parts of canValidateSelector
on some of the validations? (Note, right now, default/global validators can validate any native functions).
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.
Looking at the code below, seems we don't really support default/global validators can validate any native functions
natively in code. Users still need to setup by adding the validation functions to each native functions. Did I miss anything?
If the above is the case, then there is no reason to have two set of pools (default, and selector specific), we should combine them.
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 discussed this with @fangting-alchemy in a call, documenting here for others:
Users still need to setup by adding the validation functions to each native functions.
This is not necessary, once a validation function is marked as isDefault
, it will apply to any function allowed by default validation in _defaultValidationAllowed
, which includes the native functions.
The ability to pass in selectors to installValidation
is how to install the validation as a non-default validation function. This allows users to install a validation as both a default validation and as a selector-associated one for multiple selectors at once, but in practice I expect most users to do one or the other (either default, or associated with some selectors).
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.
Related thought - have we considered making allowDefaultValidation
configurable for native functions too? How would we reliably prevent validating into uninstallPlugin
if we want to enforce a time delay? I guess we can just use a pre exec hook instead of a pre validation hook.
Is there ever a case where users might want to opt out of default validation for some subset of native functions? Hmm...
73f6768
to
84b44c4
Compare
c872746
to
83a2ddb
Compare
…ys deny magic value
83a2ddb
to
4d2390b
Compare
src/account/AccountStorage.sol
Outdated
@@ -63,21 +65,14 @@ struct AccountStorage { | |||
mapping(address => PluginData) pluginData; | |||
// Execution functions and their associated functions | |||
mapping(bytes4 => SelectorData) selectorData; | |||
mapping(FunctionReference => ValidationData) validationData; |
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.
This is the defaultValidation
pool, right? Should we rename it?
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.
Let's also start using named parameters for mappings to make things clearer. For example:
mapping(FunctionReference validationFunction => ValidationData) ...
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.
It holds some other data per validation function, too. Specifically, whether or not the validation function is allowed to do signature validation (1271's isValidSignature
) and the set of pre-validation hooks that apply to the validation function.
Also I added the named mapping parameter validationFunction
.
} | ||
_storage.validationData[validationFunction].isDefault = true; | ||
} | ||
|
||
for (uint256 i = 0; i < selectors.length; ++i) { | ||
bytes4 selector = selectors[i]; | ||
if (!_storage.selectorData[selector].validations.add(toSetValue(validationFunction))) { |
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 remember native functions can be validated by any default validation.
If the selector is a native function, and the validation function is default, can we skip this step?
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.
Yes, this step can be skipped in that case. For example, the new initialization method passes an empty bytes4[] selectors
to this function:
_installValidation(validationFunction, true, new bytes4[](0), installData, bytes("")); |
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.
Love the removal of magic values 🙌
// Whether or not this validation can be used as a shared validation function. | ||
bool isShared; | ||
// Whether or not this validation is a signature validator. | ||
bool isSignatureValidation; |
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 can punt, but I do like the idea of using "global" here. I think it's fine that "global validation functions" don't necessarily apply to all selectors, just like how global variables can be overriden.
src/account/AccountStorage.sol
Outdated
@@ -63,21 +65,14 @@ struct AccountStorage { | |||
mapping(address => PluginData) pluginData; | |||
// Execution functions and their associated functions | |||
mapping(bytes4 => SelectorData) selectorData; | |||
mapping(FunctionReference => ValidationData) validationData; |
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.
Let's also start using named parameters for mappings to make things clearer. For example:
mapping(FunctionReference validationFunction => ValidationData) ...
|
||
if (initDatas[i].length > 0) { | ||
(address preValidationPlugin,) = FunctionReferenceLib.unpack(preValidationFunction); | ||
IPlugin(preValidationPlugin).onInstall(initDatas[i]); |
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 dependencies no longer need to be installed ahead of time? Do we expect the plugin to be able to handle multiple onInstall
s with different data?
Side note: It's interesting how we're going back towards the updatePlugins
pattern that we had before. 😅
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.
This workflow kinda doesn't touch dependencies at all, since it's not reading from the manifest 😅 .
As for expectations around onInstall
, this function currently allows the caller to decide whether or not to send it multiple times - if no data is provided, the onInstall
call is skipped. Not sure if that should be the final design or not.
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.
Re: dependencies - I meant in the sense that we had previously operated under the assumption that plugins can only use what is available from plugins installed already. This sort of breaks that and allows us to pull in arbitrary hooks from non-installed plugins.
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.
Not saying the previous approach is right though. I've also been thinking about what it would look like to get rid of the idea of "installing" plugins, and just "using" them instead.
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, I see what you mean. In a sense, this function is doing the same for the validation function too - the process of "installing" just becomes setting the storage in the account to allow using it, and optionally sending the onInstall
callback with some data.
It does create a discrepancy with how the account currently treats a few things related to plugin install state:
- The implementing plugins for both the validation function and pre-validation hooks won't appear in the list of installed plugins from the loupe function
getInstalledPlugins()
. - The validation functions can't be used as dependencies, because the implementing plugin isn't held on that list.
- The manifest hash is not stored (nor is the manifest even checked)
These all seem like things we should follow up on. It may not be useful for us to even have a getInstalledPlugins
function anymore, if they can be installed multiple times in different circumstances. And the extent of manifest contents and checking needs an overhaul in user-supplied install configs.
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 all worth following up on for sure. We're no longer getting value out of the integrity of the manifest. Happy to have us revisit in a future PR. We're deep into this approach already.
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.
Approving to unblock dependencies!
Yep, that's the pain point we had with time lock plugin. |
Motivation
#62 introduces switching between multiple validation functions, and called out the reasons we should associate pre-validation hooks with validation functions, but did not implement it. Further reasoning for why we should associate pre-validation hooks with validation functions instead of selectors can be found in #52.
To summarize:
Solution
installValidation
introduced in feat: [v0.8-develop, experimental] default validation #63. This is needed to be able to apply the pre validation hooks across multiple validation functions. It also shows the need for a full implementation of user-supplied install configs - we shouldn't keep expanding the temp workflow, since we'll need to rearrange it later anyways.