rhinestone

Tutorials

Testing modules

This tutorial will walk you through testing a module. You can choose what kind of account you want to test the module on.

This tutorial uses Foundry, a toolchain to simplify and speed up smart contract development. If you are not familiar with Foundry, feel free to check out their docs.

If you do not have foundry installed, run the following command and follow the onscreen instructions: bash curl -L https://foundry.paradigm.xyz | bash

Installation

  1. Get started with ModuleKit by using our template or adding it into an existing foundry project:

Installation

git clone https://github.com/rhinestonewtf/module-template.git
cd module-template
forge install

Testing the module

ValidatorExecutor

In this section, we will walk you through testing a simple validator.

  1. Duplicate the file test/validators/ValidatorTemplate.t.sol and rename to SimpleValidator.t.sol and the contract to SimpleValidatorTest.

If you added the ModuleKit to an existing project, you will need to create the SimpleValidator.t.sol file yourself and copy the code from here.

  1. Add the following import statement to the top of the file:
import { ECDSA } from "solady/utils/ECDSA.sol";

and change the following import

import { ValidatorTemplate, ERC1271_MAGICVALUE } from "../../src/validators/ValidatorTemplate.sol";

to the following:

import { SimpleValidator, ERC1271_MAGICVALUE } from "../../src/validators/SimpleValidator.sol";

and then change the code above the setup function to the following:

contract SimpleValidatorTest is Test, RhinestoneModuleKit {
    using RhinestoneModuleKitLib for RhinestoneAccount;
    using ECDSA for bytes32;

    RhinestoneAccount instance;
    SimpleValidator simpleValidator;

    ...
}
  1. Edit the contract functions to match the following code:

setUp

function setUp() public {
    // Setup account
    instance = makeRhinestoneAccount("1");
    vm.deal(instance.account, 10 ether);

    // Setup validator
    simpleValidator = new SimpleValidator();
    (address owner,) = makeAddrAndKey("owner");
    vm.prank(instance.account);
    simpleValidator.setOwner(owner);

    // Add validator to account
    instance.addValidator(address(simpleValidator));
}

This function sets up the tests by creating an instance of the account, deploying and initialising the validator, and adding the validator to the account.

testSendEth

function testSendEth() public {
    // Create userOperation fields
    address receiver = makeAddr("receiver");
    uint256 value = 10 gwei;
    bytes memory callData = "";

    // Create signature
    (, uint256 key) = makeAddrAndKey("owner");
    bytes32 hash =
        instance.getUserOpHash({ target: receiver, value: value, callData: callData });
    (uint8 v, bytes32 r, bytes32 s) = vm.sign(key, hash.toEthSignedMessageHash());
    bytes memory signature = abi.encode(abi.encodePacked(r, s, v), address(simpleValidator));

    // Create userOperation
    instance.exec4337({
        target: receiver,
        value: value,
        callData: callData,
        signature: signature
    });

    // Validate userOperation
    assertEq(receiver.balance, 10 gwei, "Receiver should have 10 gwei");
}

This function tests that the validator is setup and called correctly during the validateUserOp function by creating a UserOperation, signing it, and then executing it.

test1271Signature

function test1271Signature() public {
    // Create signature
    (, uint256 key) = makeAddrAndKey("owner");
    bytes32 hash = keccak256("signature");
    (uint8 v, bytes32 r, bytes32 s) = vm.sign(key, hash.toEthSignedMessageHash());
    bytes memory signature = abi.encodePacked(r, s, v);

    // Validate signature
    vm.prank(instance.account);
    bytes4 returnValue = simpleValidator.isValidSignature(hash, signature);

    // Validate signature success
    assertEq(
        returnValue,
        ERC1271_MAGICVALUE, // EIP1271_MAGIC_VALUE
        "Signature should be valid"
    );
}

This function tests that the validator behaves correctly during the isValidSignature function by creating a signature and then checking that the signature is validated by the validator.

  1. Run forge test to run the tests and ensure that they pass. You should see something like the following message:

response

Running 1 test for ...
[PASS] ...() (gas: ...)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 18.85ms

Ran 2 test suites: 5 tests passed, 0 failed, 0 skipped (5 total tests)

Congratulations, you just wrote your first module test!