ERC7579ValidatorBase
and has all the needed imports:
ownerId
key first in order to be compliant with the [ERC-4337 restrictions(/erc4337-validation). We also use the SignatureCheckerLib
to check the signatures of the owners.
Then, we add the config logic of the module:
onInstall
function, we initialize the module with the given data. In this case, we set the first owner that is passed (using abi.encodePacked
) in the data. In the onUninstall
function, we de-initialize the module by removing all the owners of the account. In the isInitialized
function, we check if the module is initialized by checking if the owner count is greater than 0, ie whether an account has owners.
Next, we add the module logic:
validateUserOp
function, we validate the user operation by checking if the signature is valid. In the isValidSignatureWithSender
function, we validate the ERC-1271 signature by checking if the signature is valid. For both of these, we encode the ownerId
in the signature to use the wanted owner.
We also add the addOwner
and removeOwner
functions to add and remove owners from the smart account.
Finally, we add the metadata logic:
.t.sol
file with the following content:
ModuleKit
and Test
from forge-std
. We also import the MultiOwnerValidator
contract we just created, and the ECDSA
library from solady
as a helper. Then, we create a new contract that inherits from RhinestoneModuleKit
and Test
.
Next, we will set up the test:
ModuleKitHelpers
and ModuleKitUserOp
to help with the integration testing and the ECDSA
library to help with the signatures. We then set up the test by creating the account instance, validator and owners. During setUp
, we create the account instance, owners and install the validator.
Next, we will test the validator:
signHash
and execAndAssert
. The first of these is a helper to make it easier to create the signature of the UserOperation
. The second actually holds the majority of the testing logic, but we have extracted it as a non-test function to make the tests more readable by reusing the code. The execAndAssert
creates calldata and a UserOpData
object, sets the signature, and then executes the UserOp
. Finally, it asserts that the UserOp
was executed successfully. We then have two tests, testOwner1
and testOwner2
, which test the validator by executing a UserOp
with the first and second owner, respectively.
When you run the tests, you should see that they pass: