Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an E2E test for nested Safes in 4337 context (#273)
This PR adds an E2E test case for nested Safe transaction execution in the 4337 Context. The test uses the following ownership structure: ![image](https://github.com/safe-global/safe-modules/assets/16622558/3e420068-3d3b-4db4-91f6-3da4febc350e) I implemented helper classes representing the Safe ownership tree with two node types: Safe and EOA. The tests also required functions to find an execution path and build the user operation from the execution path. The alternative would've been to do everything imperatively, but using those functions, we could theoretically represent arbitrary ownership structure, find an execution path (currently, the function doesn't find the most optimal one) and generate a user operation. The current algorithm works as follows (thanks to @nlordell for the help): 1. **Finding an execution path:** It does a DFS on the tree until it finds the first Executor node. We consider an executor node a leaf safe owned by an EOA. In the context of the ownership structure on the screenshot, the executor would be the leftmost Leaf Safe. The execution path is represented by an array ordered from the root node to the executor node. 2. **Building the user operation:** Using the execution path and our desired `SafeTransaction` (the one executed with `execTransaction`), we recursively sign the transaction with all the node owners in the execution path. After that, we re-encode the `SafeTransaction` for the next node with a call to the preceding node in the execution path and do the signing again. It stops when we reach the starting node (the node before the executor 4337 Safe). After that, we encode and send the SafeTransaction in a User Operation. - Implements #251 - I also had to bring quite some code for executing regular Safe transactions from the `safe-smart-account` repo I tried to document the code as much as possible, but somehow, I feel it still wasn't enough - please feel free to point me to the parts that are unclear, and I'll do my best to explain them with comments. I'll quote the Slack message from @nlordell that describes the idea implemented in this PR: > As you may already be aware, nested Safe ownership structures are problematic in the context of 4337 user operations because of storage access restrictions. However, I think it is technically possible to accomplish at a UX cost. > Specifically, imagine an acyclic Safe ownership tree, where we want to execute a transaction with the root. The idea would be to pick a path to a "leaf" Safe (that is, a Safe with only EOA owners). Since we assume an acyclic Safe ownership tree, this must always be possible. The idea would then be to have the "leaf" Safe sign a user operation to execute parent.execTransaction(parent.execTransaction(...(root.execTransaction(payload)))) on its parent and make use of "approved hash from executor" signatures (i.e. v = 1). All other Safes/owners sign regular SafeTransactions (i.e. NOT 4337 user operation signature) for executing the root transaction. > The problem with this scheme is that the UX is not great: > - You only know what signatures are needed once you know the signers, so signature collection is super messy (for example, imagine a 2 of 3 Safe where two owners are EOAs and one is a Safe, you can't know what kind of signatures to collect without knowing who the signers will be). There may be a way to work around this by expanding the Safe4337Module to allow SafeUserOperation signatures to be used for calling 4337-enabled parent Safes, but I would need to think about it a bit more... > - The user operation would appears as if it was from the "leaf" Safe in 4337-user-operation-explorers.
- Loading branch information