Building the module
To get started building the executor, we will first of all create a new contract that inherits fromERC7579ExecutorBase and has all the needed imports:
ExecutionConfig to hold the execution configuration, and a mapping _executionLog to keep track of the executions. We also create a mapping _accountJobCount to keep track of the number of jobs for a given smart account. The latter is used to be able to remove all jobs when the module is uninstalled.
Then, we add the config logic of the module:
onInstall function, we initialize the module with the given initial execution. In the onUninstall function, we de-initialize the module by removing all active jobs. We also add the isInitialized function to check if the module is initialized, and the addOrder and removeOrder functions to add and remove orders from the executor.
Next, we add the module logic:
executeOrder function, we execute a given order. We decode the execution data, approve and swap the tokens, update the execution log, and emit an event. We also add the canExecute modifier to check if the order can be executed.
Next, we add some internal functions that have already been used above:
_createExecution function, we create an execution. In the canExecute modifier, we check if the order can be executed, by routing to the _isExecutionValid function, which checks the conditions on the order are met.
Finally, we add the metadata logic:
Testing the module
First, we will create a new.t.sol file with the following content:
AutoSwapExecutorTest contract. We also define the WETH and USDC addresses, which we will use in the tests. These are the tokens that we will swap between.
Next, we will set up the test:
ModuleKitHelpers and ModuleKitUserOp to help with the integration testing. We also define the instance and executor variables, which we will use in the tests. We also define the usdc and weth variables, which we will use to interact with the tokens. We define the mainnetFork variable, which we will use to create a fork for testing as well as the jobId variable to keep track of the execution job ids we create.
We then define the setUp function, which creates a fork, initializes the RhinestoneModuleKit, creates the executor, labels the tokens, creates the account and deals tokens, creates the execution data to initialize the executor, and installs the executor.
The main point of interest here is that we are creating a fork to test the executor on a specific block. The reason for doing this is that it allows us to use the existing token and Uniswap contracts on Mainnet to facilitate the swaps. To complete setting up this fork test, you will need to create a .env file in the root of your project with the following content:
testExec function, we get the current balance of the target, execute the call, and check if the balance of the target has increased in the target token. In the testExecMultiple function, we execute the swap, skip 1 day to the next execution time, and execute the swap again. One thing to note here is that we are increasing the default gas limit set by the ModuleKit to ensure there is enough gas for the swap.
When you run the tests, you should see that they pass: