Closed adam-alchemy closed 6 days ago
Multiple validation function support would add great flexibility to accounts. Based on the current standard and development in Account Abstraction, it is worth consider the following:
For an executionFunction
with multiple validation functions, all the validation function should be first tier citizens, aka, they should be assumed to have the auth to validate a call to the execution function. Once they validated the call, the execution function should carry out the execution without having to customize its logic based on which validation function is used. If the execution function execute different logic based on which validation function is used, then it should be split into different execution functions (as plugin installed etc.).
A default validation function concept is also very interesting and can be quite useful given the above point being true. It can greatly simplify the common and simple use case where clients don't need to even be aware of the available validation functions.
In general for security reasons, the validation functions should not require extra data to determine the validity of the call without that piece of data being included in the UO and signed by the key (whatever key that maybe). Therefore, if a specific validation function require extra data, it will need to be attached in the non-signature UO struct, probably calldata
.
A feasible solution might be:
userOp.signature
userOp.calldata
(as pointed out below to accommodate the runtime call path) selects the validation function (empty bytes means default validation function) of an execution function. A rule can be enforced on the last x bytes if a signature is reserved for this.userOp.calldata
supply optional extra data to the validation function to help determine the validity of the call.Great thoughts!
- A default validation function concept is also very interesting and can be quite useful given the above point being true. It can greatly simplify the common and simple use case where clients don't need to even be aware of the available validation functions.
This is interesting and probably worth exploring further. If the scope grows we can move this bit to a separate issue.
userOp.signature
selects the validation function (empty bytes means default validation function) of an execution function. A rule can be enforced on the last x bytes if a signature is reserved for this.
The runtime path doesn't have access to a signature, so it may be worth exploring the calldata wrapper approach, which is available both to user ops and runtime calls.
The runtime path doesn't have access to a signature, so it may be worth exploring the calldata wrapper approach, which is available both to user ops and runtime calls.
Good point. Agree, in that case, userOp.calldata
can be the house for the selection.
Closing as completed in https://github.com/erc6900/reference-implementation/pull/62.
(As discussed in Community Call 1)
It would be really nice to allow multiple validation functions to be defined for the same execution function. This would simplify the implementation of plugins like session keys and social recovery, and could replicate the functionality of
IPluginExecutor
in a simpler way.An idea we’ve discussed with multiple validation functions is to define a way to interpret / combine them using OR and AND operations. However, without a “validation selector”, the only way to handle OR operations is to run all of them, which is inefficient and potentially dangerous due to side effects.
One proposed strategy is to have a “validation selector” within
userOp.signature
. This is a piece of data used to determine which validation function should be run, provided it is a previously configured and allowed validation function. We do not want to directly use a mechanism like this, because it makes it impossible for the account to determine which validation was used during the execution phase, barringsstore
/tstore
usage which is complicated and expensive (must be a queue data structure, because of multi-op bundles). This strategy is also less compatible with other validation functions like what is used in RIP-7560.A strategy we’ve tried in the past was brought up by Yoav in the community call: to specify which validation “scheme” should be used within the execution phase’s calldata. This field is necessarily accessible to both the validation and execution phases. There are a lot of possibilities of how to pursue this.
execute
andexecuteBatch
. This was seemingly on the right track, but it caused some problems when we made the split that specified only these two functions could have multiple validators, not other functions, and that the standard execute functions could not be used to call to plugins. This led to us removing this call path and instead introducingIPluginExecutor
, but in hindsight that may have been the wrong call.One potential way to address this in a generalizable way is to define a standard for a “calldata wrapper” that specifies which validation function to run. This would be similar to the interface we provide in
executeFromPlugin
, where the underlying call is provided as an ABI-encoded bytes and some special execution context happens within it. An idea would be:Another thing to consider is that there may be some other data specific to the validation beyond just “which validation should be run”. For instance, with session keys, you may want to know which session key is being used, not just the fact that “session key validation” is being used. If we take inspiration from ERC-4337’s
paymasterAndData
field, we can just use abytes
field that encodes the function selector in the first N bytes and the other context data in the rest, like:This could also be used for potentially other “types” of validation like our existing plugin call permissions system, that uses an account-native permissions check rather than running a validation function.
Needs more exploration in how this change could fit together with the other proposed changes.