Skip to content

Commit

Permalink
Merge pull request #255 from pimlicolabs/installModules
Browse files Browse the repository at this point in the history
Add installModules and uninstallModules Functions
  • Loading branch information
plusminushalf authored Jul 24, 2024
2 parents bd06cb2 + e55d304 commit 987be9b
Show file tree
Hide file tree
Showing 10 changed files with 664 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/yellow-lies-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"permissionless": patch
---

Added uninstall modules and install modules functions
6 changes: 3 additions & 3 deletions packages/permissionless/actions/erc7579/installModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ export async function installModule<
)({
userOperation: {
sender: account.address,
maxFeePerGas: maxFeePerGas || BigInt(0),
maxPriorityFeePerGas: maxPriorityFeePerGas || BigInt(0),
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
callData: installModuleCallData,
nonce: nonce ? BigInt(nonce) : undefined
nonce: nonce
},
account: account,
middleware
Expand Down
244 changes: 244 additions & 0 deletions packages/permissionless/actions/erc7579/installModules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import {
http,
type Chain,
type Transport,
encodeAbiParameters,
encodePacked,
isHash,
zeroAddress
} from "viem"
import { generatePrivateKey } from "viem/accounts"
import { describe, expect } from "vitest"
import { testWithRpc } from "../../../permissionless-test/src/testWithRpc"
import {
getCoreSmartAccounts,
getPimlicoPaymasterClient
} from "../../../permissionless-test/src/utils"
import type { SmartAccount } from "../../accounts"
import { createBundlerClient } from "../../clients/createBundlerClient"
import type { SmartAccountClient } from "../../clients/createSmartAccountClient"
import type { ENTRYPOINT_ADDRESS_V07_TYPE } from "../../types/entrypoint"
import { ENTRYPOINT_ADDRESS_V07 } from "../../utils"
import { erc7579Actions } from "../erc7579"
import { installModules } from "./installModules"

describe.each(getCoreSmartAccounts())(
"installmodules $name",
({ getErc7579SmartAccountClient, name }) => {
testWithRpc.skipIf(!getErc7579SmartAccountClient)(
"installModules",
async ({ rpc }) => {
const { anvilRpc, altoRpc, paymasterRpc } = rpc

if (!getErc7579SmartAccountClient) {
throw new Error("getErc7579SmartAccountClient not defined")
}

const privateKey = generatePrivateKey()

const smartClientWithoutExtend: SmartAccountClient<
ENTRYPOINT_ADDRESS_V07_TYPE,
Transport,
Chain,
SmartAccount<ENTRYPOINT_ADDRESS_V07_TYPE>
> = await getErc7579SmartAccountClient({
entryPoint: ENTRYPOINT_ADDRESS_V07,
privateKey: privateKey,
altoRpc: altoRpc,
anvilRpc: anvilRpc,
paymasterClient: getPimlicoPaymasterClient({
entryPoint: ENTRYPOINT_ADDRESS_V07,
paymasterRpc
})
})

const smartClient = smartClientWithoutExtend.extend(
erc7579Actions({
entryPoint: ENTRYPOINT_ADDRESS_V07
})
)

const moduleData = encodePacked(
["address"],
[smartClient.account.address]
)

const opHash = await installModules(smartClient as any, {
account: smartClient.account as any,
modules: [
{
type: "executor",
address:
"0xc98B026383885F41d9a995f85FC480E9bb8bB891",
context:
name === "Kernel 7579"
? encodePacked(
["address", "bytes"],
[
zeroAddress,
encodeAbiParameters(
[
{ type: "bytes" },
{ type: "bytes" }
],
[moduleData, "0x"]
)
]
)
: moduleData
}
]
})

const bundlerClientV07 = createBundlerClient({
transport: http(altoRpc),
entryPoint: ENTRYPOINT_ADDRESS_V07
})

expect(isHash(opHash)).toBe(true)

const userOperationReceipt =
await bundlerClientV07.waitForUserOperationReceipt({
hash: opHash,
timeout: 100000
})
expect(userOperationReceipt).not.toBeNull()
expect(userOperationReceipt?.userOpHash).toBe(opHash)
expect(
userOperationReceipt?.receipt.transactionHash
).toBeTruthy()

const receipt = await bundlerClientV07.getUserOperationReceipt({
hash: opHash
})

expect(receipt?.receipt.transactionHash).toBe(
userOperationReceipt?.receipt.transactionHash
)

const isModuleInstalled = await smartClient.isModuleInstalled({
type: "executor",
address: "0xc98B026383885F41d9a995f85FC480E9bb8bB891",
context: "0x"
})

expect(isModuleInstalled).toBe(true)
}
)
testWithRpc.skipIf(!getErc7579SmartAccountClient)(
"installModule",
async ({ rpc }) => {
const { anvilRpc, altoRpc, paymasterRpc } = rpc

if (!getErc7579SmartAccountClient) {
throw new Error("getErc7579SmartAccountClient not defined")
}

const privateKey = generatePrivateKey()

const smartClientWithoutExtend: SmartAccountClient<
ENTRYPOINT_ADDRESS_V07_TYPE,
Transport,
Chain,
SmartAccount<ENTRYPOINT_ADDRESS_V07_TYPE>
> = await getErc7579SmartAccountClient({
entryPoint: ENTRYPOINT_ADDRESS_V07,
privateKey: privateKey,
altoRpc: altoRpc,
anvilRpc: anvilRpc,
paymasterClient: getPimlicoPaymasterClient({
entryPoint: ENTRYPOINT_ADDRESS_V07,
paymasterRpc
})
})

const smartClient = smartClientWithoutExtend.extend(
erc7579Actions({
entryPoint: ENTRYPOINT_ADDRESS_V07
})
)

await smartClient.sendTransactions({
transactions: [
{
to: smartClient.account.address,
value: 0n,
data: "0x"
},
{
to: smartClient.account.address,
value: 0n,
data: "0x"
}
]
})

const moduleData = encodePacked(
["address"],
[smartClient.account.address]
)

const opHash = await installModules(smartClient as any, {
account: smartClient.account as any,
modules: [
{
type: "executor",
address:
"0xc98B026383885F41d9a995f85FC480E9bb8bB891",
context:
name === "Kernel 7579"
? encodePacked(
["address", "bytes"],
[
zeroAddress,
encodeAbiParameters(
[
{ type: "bytes" },
{ type: "bytes" }
],
[moduleData, "0x"]
)
]
)
: moduleData
}
]
})

const bundlerClientV07 = createBundlerClient({
transport: http(altoRpc),
entryPoint: ENTRYPOINT_ADDRESS_V07
})

expect(isHash(opHash)).toBe(true)

const userOperationReceipt =
await bundlerClientV07.waitForUserOperationReceipt({
hash: opHash,
timeout: 100000
})
expect(userOperationReceipt).not.toBeNull()
expect(userOperationReceipt?.userOpHash).toBe(opHash)
expect(
userOperationReceipt?.receipt.transactionHash
).toBeTruthy()

const receipt = await bundlerClientV07.getUserOperationReceipt({
hash: opHash
})

expect(receipt?.receipt.transactionHash).toBe(
userOperationReceipt?.receipt.transactionHash
)

const isModuleInstalled = await smartClient.isModuleInstalled({
type: "executor",
address: "0xc98B026383885F41d9a995f85FC480E9bb8bB891",
context: "0x"
})

expect(isModuleInstalled).toBe(true)
}
)
}
)
115 changes: 115 additions & 0 deletions packages/permissionless/actions/erc7579/installModules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
type Address,
type Chain,
type Client,
type Hex,
type Transport,
encodeFunctionData,
getAddress
} from "viem"
import { getAction } from "viem/utils"
import type { SmartAccount } from "../../accounts/types"
import type { GetAccountParameter, Prettify } from "../../types/"
import type { EntryPoint } from "../../types/entrypoint"
import { parseAccount } from "../../utils/"
import { AccountOrClientNotFoundError } from "../../utils/signUserOperationHashWithECDSA"
import type { Middleware } from "../smartAccount/prepareUserOperationRequest"
import { sendUserOperation } from "../smartAccount/sendUserOperation"
import { type ModuleType, parseModuleTypeId } from "./supportsModule"

export type InstallModulesParameters<
TEntryPoint extends EntryPoint,
TSmartAccount extends SmartAccount<TEntryPoint> | undefined
> = GetAccountParameter<TEntryPoint, TSmartAccount> &
Middleware<TEntryPoint> & {
modules: {
type: ModuleType
address: Address
context: Hex
}[]
maxFeePerGas?: bigint
maxPriorityFeePerGas?: bigint
nonce?: bigint
}

export async function installModules<
TEntryPoint extends EntryPoint,
TSmartAccount extends SmartAccount<TEntryPoint> | undefined,
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined
>(
client: Client<TTransport, TChain, TSmartAccount>,
parameters: Prettify<InstallModulesParameters<TEntryPoint, TSmartAccount>>
): Promise<Hex> {
const {
account: account_ = client.account,
maxFeePerGas,
maxPriorityFeePerGas,
nonce,
middleware,
modules
} = parameters

if (!account_) {
throw new AccountOrClientNotFoundError({
docsPath: "/docs/actions/wallet/sendTransaction"
})
}

const account = parseAccount(account_) as SmartAccount<TEntryPoint>

const installModulesCallData = await account.encodeCallData(
await Promise.all(
modules.map(({ type, address, context }) => ({
to: account.address,
value: BigInt(0),
data: encodeFunctionData({
abi: [
{
name: "installModule",
type: "function",
stateMutability: "nonpayable",
inputs: [
{
type: "uint256",
name: "moduleTypeId"
},
{
type: "address",
name: "module"
},
{
type: "bytes",
name: "initData"
}
],
outputs: []
}
],
functionName: "installModule",
args: [
parseModuleTypeId(type),
getAddress(address),
context
]
})
}))
)
)

return getAction(
client,
sendUserOperation<TEntryPoint>,
"sendUserOperation"
)({
userOperation: {
sender: account.address,
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
callData: installModulesCallData,
nonce: nonce
},
account: account,
middleware
})
}
6 changes: 3 additions & 3 deletions packages/permissionless/actions/erc7579/uninstallModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ export async function uninstallModule<
)({
userOperation: {
sender: account.address,
maxFeePerGas: maxFeePerGas || BigInt(0),
maxPriorityFeePerGas: maxPriorityFeePerGas || BigInt(0),
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
callData: uninstallModuleCallData,
nonce: nonce ? BigInt(nonce) : undefined
nonce: nonce
},
account: account,
middleware
Expand Down
Loading

0 comments on commit 987be9b

Please sign in to comment.