# Prool

This guide introduces how to setup and run tests in a mock environment using [Prool](https://github.com/wevm/prool).

## Overview

Prool is a library by [Wevm](https://github.com/wevm/viem) that lets you programmatically interact with Ethereum server instances like Nodes, Bundlers, and Paymasters.

We will be using [Vitest](https://vitest.dev/) as our testing framework and we will be testing against a fork of Base Mainnet.

## Steps

::::steps
### Setup

Install the following dependencies:

```bash
npm install permissionless viem prool @pimlico/alto @pimlico/mock-paymaster get-port
```

Installing the following dev dependencies:

```bash
npm install --save-dev vitest
```

In order to run tests, add the following section to your **package.json** file:

```json [package.json]
{
    "scripts": {
        "test": "vitest"
    }
}
```

### Create test helpers

Prool lets us run multiple test cases at once by creating separate isolated environments for each, with their own Bundler, Anvil, and Paymaster.

To make this easier, we’ll create a helper called testWithRpc that finds free ports and spins up the isolated test environment.

:::code-group
```typescript [testWithRpc.ts]
import getPort from "get-port";
import { test } from "vitest";
import { getInstances } from "./getInstances";
import { base } from "viem/chains";

let ports: number[] = [];

export const testWithRpc = test.extend<{
	rpc: {
		anvilRpc: string;
		altoRpc: string;
		paymasterRpc: string;
	};
}>({
	rpc: async ({}, use) => {
		const altoPort = await getPort({
			exclude: ports,
		});
		ports.push(altoPort);
		const paymasterPort = await getPort({
			exclude: ports,
		});
		ports.push(paymasterPort);
		const anvilPort = await getPort({
			exclude: ports,
		});
		ports.push(anvilPort);

		const anvilRpc = `http://localhost:${anvilPort}`;
		const altoRpc = `http://localhost:${altoPort}`;
		const paymasterRpc = `http://localhost:${paymasterPort}`;

		const forkUrl = process.env.FORK_RPC_URL || base.rpcUrls.default.http[0];

		const instances = await getInstances({
			forkUrl,
			anvilPort,
			altoPort,
			paymasterPort,
		});

		await use({
			anvilRpc,
			altoRpc,
			paymasterRpc,
		});

		await Promise.all([...instances.map((instance) => instance.stop())]);
		const usedPorts = [altoPort, anvilPort, paymasterPort];
		ports = ports.filter((port) => !usedPorts.includes(port));
	},
});
```

```typescript [getInstances.ts]
import { paymaster } from "@pimlico/mock-paymaster";
import { anvil, alto } from "prool/instances";
import {
	entryPoint06Address,
	entryPoint07Address,
	entryPoint08Address,
} from "viem/account-abstraction";
import { foundry } from "viem/chains";

// Private key for Anvil account 0.
const pk = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";

export const getInstances = async ({
	forkUrl,
	anvilPort,
	altoPort,
	paymasterPort,
}: {
	forkUrl: string;
	anvilPort: number;
	altoPort: number;
	paymasterPort: number;
}) => {
	const anvilRpc = `http://localhost:${anvilPort}`;
	const altoRpc = `http://localhost:${altoPort}`;

	const anvilInstance = anvil({
		port: anvilPort,
		chainId: foundry.id,
		hardfork: "Prague",
		forkUrl,
	});

	const altoInstance = alto({
		port: altoPort,
		entrypoints: [
			entryPoint06Address,
			entryPoint07Address,
			entryPoint08Address,
		],
		rpcUrl: anvilRpc,
		executorPrivateKeys: [pk],
		utilityPrivateKey: pk,
		safeMode: false,
	});

	const paymasterInstance = paymaster({
		anvilRpc,
		altoRpc,
		port: paymasterPort,
	});

	await anvilInstance.start();
	await altoInstance.start();
	await paymasterInstance.start();

	return [anvilInstance, altoInstance, paymasterInstance];
};
```
:::

:::note
Note Make sure you set the `FORK_RPC_URL` evn var before running your tests.
:::

### Writing test cases

In this example, we will mock a test by sending a simple sponsored userOperation.

We use the `testWithRpc` helper to setup our test environment. The `testWithRpc` method returns the rpc endpoints that we should use when creating the Anvil, Alto, Paymaster clients.

```typescript [basic.test.ts]
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import {
	createPublicClient,
	createTestClient,
	http,
	parseEther,
	zeroAddress,
} from "viem";
import { assert, describe } from "vitest";
import { toSimpleSmartAccount } from "permissionless/accounts";
import { createSmartAccountClient } from "permissionless";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { foundry } from "viem/chains";
import { testWithRpc } from "./testWithRpc";

describe("Basic test cases", () => {
    testWithRpc("Can send a sponsored userOperation", async ({ rpc }) => {
		const { anvilRpc, altoRpc, paymasterRpc } = rpc;

		// Setup clients.
		const publicClient = createPublicClient({
			chain: foundry,
			transport: http(anvilRpc),
		});

		const pimlicoClient = createPimlicoClient({
			chain: foundry,
			transport: http(paymasterRpc),
		});

		const account = await toSimpleSmartAccount({
			client: publicClient,
			owner: privateKeyToAccount(generatePrivateKey()),
		});

		const smartAccountClient = createSmartAccountClient({
			account,
			bundlerTransport: http(altoRpc),
			paymaster: pimlicoClient,
			userOperation: {
				estimateFeesPerGas: async () =>
					(await pimlicoClient.getUserOperationGasPrice()).fast,
			},
		});

		// Send userOperation and wait for receipt.
		const userOpHash = await smartAccountClient.sendUserOperation({
			calls: [
				{
					to: zeroAddress,
					value: 0n,
					data: "0x",
				},
			],
		});

		const receipt = await smartAccountClient.waitForUserOperationReceipt({
			hash: userOpHash,
		});

		// UserOperation should be included successfully.
		expect(receipt.success).toBe(true);
	});
});
```

### Testing with ERC-20 paymaster

We can also test the ERC-20 paymaster flow. The `@pimlico/mock-paymaster` package exposes two helper functions/variables:

* `sudoMintTokens`: Helper function to mint tokens to a given address.
* `erc20Address`: Helper constant that returns the address of the supported ERC-20 token.

```typescript [erc20.test.ts]
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import {
	createPublicClient,
	erc20Abi,
	getContract,
	http,
	parseEther,
	zeroAddress,
} from "viem";
import { expect, describe } from "vitest";
import { toSimpleSmartAccount } from "permissionless/accounts";
import { testWithRpc } from "../utils/testWithRpc";
import { createSmartAccountClient } from "permissionless";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { prepareUserOperationForErc20Paymaster } from "permissionless/experimental/pimlico";
import { foundry } from "viem/chains";
import { erc20Address, sudoMintTokens } from "@pimlico/mock-paymaster";

describe("Basic test cases", () => {
	testWithRpc("Can send a sponsored ERC20 userOperation", async ({ rpc }) => {
		const { anvilRpc, altoRpc, paymasterRpc } = rpc;

		// Setup clients.
		const publicClient = createPublicClient({
			chain: foundry,
			transport: http(anvilRpc),
		});

		const pimlicoClient = createPimlicoClient({
			chain: foundry,
			transport: http(paymasterRpc),
		});

		const account = await toSimpleSmartAccount({
			client: publicClient,
			owner: privateKeyToAccount(generatePrivateKey()),
		});

		// Creating smart account client based on this page
		// http://docs.pimlico.io/guides/how-to/erc20-paymaster/how-to/use-paymaster
		const smartAccountClient = createSmartAccountClient({
			chain: foundry,
			account,
			bundlerTransport: http(altoRpc),
			paymaster: pimlicoClient,
			userOperation: {
				estimateFeesPerGas: async () =>
					(await pimlicoClient.getUserOperationGasPrice()).fast,
				prepareUserOperation:
					prepareUserOperationForErc20Paymaster(pimlicoClient),
			},
		});

		// Fund the SmartAccount with ERC-20 tokens.
		await sudoMintTokens({
			amount: parseEther("1"),
			to: account.address,
			anvilRpc,
		});

		// Check smartAccount balance before and after sending transaction to confirm balance decreased.
		const erc20 = getContract({
			address: erc20Address,
			abi: erc20Abi,
			client: publicClient,
		});

		const balanceBefore = await erc20.read.balanceOf([account.address]);

		// Send a transaction and wait for receipt.
		const userOpHash = await smartAccountClient.sendUserOperation({
			calls: [
				{
					to: zeroAddress,
					value: 0n,
					data: "0x",
				},
			],
			paymasterContext: {
				token: erc20Address,
			},
		});

		const receipt = await smartAccountClient.waitForUserOperationReceipt({
			hash: userOpHash,
		});

		const balanceAfter = await erc20.read.balanceOf([account.address]);

		// UserOperation should be included successfully.
		expect(receipt.success).toBe(true);
		expect(balanceAfter).toBeLessThan(balanceBefore);
	});
});
```

:::note
Note The ERC-20 token has a 18 decimal places.
:::
::::

## Combined code

If you want to see the complete code that combines all of the previous steps, we uploaded it to a [separate repository](https://github.com/pimlicolabs/testing-with-prool). If you're looking to run it, remember to replace the API key with your own!

## Tips and tricks

* The `testWithRpc` helper creates isolated instances for a specific test case meaning vitest will run multiple tests at once.
* The setup outlined in this guide is the same one used in permissionless.js's E2E tests. for more detailed examples, refer to the test cases in permissionless.js. For example this [test case](https://github.com/pimlicolabs/permissionless.js/blob/main/packages/permissionless/actions/pimlico/sponsorUserOperation.test.ts) for the pm\_sponsorUserOperation endpoint.
* To debug the Alto/Anvil/Paymaster instance, you can print logs to stdout by calling the `.on` function. For an example of this, please refere to [this snippet](https://github.com/pimlicolabs/testing-with-prool/blob/c0b3d448e2fff1ca4a3d2829dc8585e708728f41/utils/getInstances.ts#L53-L74).
