Skip to content

Commit

Permalink
Merge pull request #201 from zerodevapp/feat/pluginMigrations
Browse files Browse the repository at this point in the history
feat: added pluginMigrations feature and test
  • Loading branch information
SahilVasava authored Jan 15, 2025
2 parents 01ad5a2 + 4b5f44c commit 5828663
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 13 deletions.
6 changes: 6 additions & 0 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @zerodev/sdk

## 5.4.10

### Patch Changes

- feat: add pluginMigrations feature

## 5.4.9

### Patch Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,42 @@ export const KernelModuleIsInitializedAbi = [
stateMutability: "view"
}
] as const

export const KernelModuleInstallAbi = [
{
inputs: [
{
internalType: "uint256",
name: "moduleType",
type: "uint256"
},
{ internalType: "address", name: "module", type: "address" },
{ internalType: "bytes", name: "initData", type: "bytes" }
],
stateMutability: "payable",
type: "function",
name: "installModule"
}
] as const

export const KernelModuleIsModuleInstalledAbi = [
{
inputs: [
{
internalType: "uint256",
name: "moduleType",
type: "uint256"
},
{ internalType: "address", name: "module", type: "address" },
{
internalType: "bytes",
name: "additionalContext",
type: "bytes"
}
],
stateMutability: "view",
type: "function",
name: "isModuleInstalled",
outputs: [{ internalType: "bool", name: "", type: "bool" }]
}
] as const
67 changes: 64 additions & 3 deletions packages/core/accounts/kernel/createKernelAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import { getChainId } from "viem/actions"
import { getAction } from "viem/utils"
import {
getAccountNonce,
getSenderAddress
getSenderAddress,
isPluginInstalled
} from "../../actions/public/index.js"
import {
KernelVersionToAddressesMap,
Expand All @@ -42,7 +43,8 @@ import type {
GetEntryPointAbi,
GetKernelVersion,
KernelPluginManager,
KernelPluginManagerParams
KernelPluginManagerParams,
PluginMigrationData
} from "../../types/kernel.js"
import { KERNEL_FEATURES, hasKernelFeature } from "../../utils.js"
import { validateKernelVersionWithEntryPoint } from "../../utils.js"
Expand All @@ -61,6 +63,7 @@ import { encodeCallData as encodeCallDataEpV07 } from "./utils/account/ep0_7/enc
import { encodeDeployCallData as encodeDeployCallDataV07 } from "./utils/account/ep0_7/encodeDeployCallData.js"
import { accountMetadata } from "./utils/common/accountMetadata.js"
import { eip712WrapHash } from "./utils/common/eip712WrapHash.js"
import { getPluginInstallCallData } from "./utils/plugins/ep0_7/getPluginInstallCallData.js"

type SignMessageParameters = {
message: SignableMessage
Expand Down Expand Up @@ -117,6 +120,7 @@ export type CreateKernelAccountParameters<
kernelVersion: GetKernelVersion<entryPointVersion>
initConfig?: KernelVerion extends "0.3.1" ? Hex[] : never
useMetaFactory?: boolean
pluginMigrations?: PluginMigrationData[]
}

/**
Expand Down Expand Up @@ -329,6 +333,11 @@ const getDefaultAddresses = <entryPointVersion extends EntryPointVersion>(
}
}

type PluginInstallationCache = {
pendingPlugins: PluginMigrationData[]
allInstalled: boolean
}

/**
* Build a kernel smart account from a private key, that use the ECDSA signer behind the scene
* @param client
Expand All @@ -355,7 +364,8 @@ export async function createKernelAccount<
address,
kernelVersion,
initConfig,
useMetaFactory = true
useMetaFactory = true,
pluginMigrations
}: CreateKernelAccountParameters<entryPointVersion, KernelVersion>
): Promise<CreateKernelAccountReturnType<entryPointVersion>> {
const { accountImplementationAddress, factoryAddress, metaFactoryAddress } =
Expand Down Expand Up @@ -449,6 +459,38 @@ export async function createKernelAccount<
version: entryPoint?.version ?? "0.7"
} as const

// Cache for plugin installation status
const pluginCache: PluginInstallationCache = {
pendingPlugins: pluginMigrations || [],
allInstalled: false
}

const checkPluginInstallationStatus = async () => {
// Skip if no plugins or all are installed
if (!pluginCache.pendingPlugins.length || pluginCache.allInstalled) {
pluginCache.allInstalled = true
return
}

// Check all pending plugins in parallel
const installationResults = await Promise.all(
pluginCache.pendingPlugins.map((plugin) =>
isPluginInstalled(client, {
address: accountAddress,
plugin
})
)
)

// Filter out installed plugins
pluginCache.pendingPlugins = pluginCache.pendingPlugins.filter(
(_, index) => !installationResults[index]
)
pluginCache.allInstalled = pluginCache.pendingPlugins.length === 0
}

await checkPluginInstallationStatus()

return toSmartAccount<KernelSmartAccountImplementation<entryPointVersion>>({
kernelVersion,
kernelPluginManager,
Expand Down Expand Up @@ -487,6 +529,25 @@ export async function createKernelAccount<
return encodeDeployCallDataV07(_tx)
},
async encodeCalls(calls, callType) {
// Check plugin status only if we have pending plugins
await checkPluginInstallationStatus()

// Add plugin installation calls if needed
if (
pluginCache.pendingPlugins.length > 0 &&
entryPoint.version === "0.7" &&
kernelPluginManager.activeValidatorMode === "sudo"
) {
const pluginInstallCalls = pluginCache.pendingPlugins.map(
(plugin) => getPluginInstallCallData(accountAddress, plugin)
)
return encodeCallDataEpV07(
[...calls, ...pluginInstallCalls],
callType,
plugins.hook ? true : undefined
)
}

if (
calls.length === 1 &&
(!callType || callType === "call") &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { type Address, encodeFunctionData } from "viem"
import type { PluginMigrationData } from "../../../../../types/kernel.js"
import { KernelModuleInstallAbi } from "../../../abi/kernel_v_3_0_0/KernelModuleAbi.js"
import type { CallArgs } from "../../types.js"

export const getPluginInstallCallData = (
accountAddress: Address,
plugin: PluginMigrationData
): CallArgs => {
const data = encodeFunctionData({
abi: KernelModuleInstallAbi,
functionName: "installModule",
args: [plugin.type, plugin.address, plugin.data]
})
return {
to: accountAddress,
data
}
}
1 change: 1 addition & 0 deletions packages/core/accounts/utils/toKernelPluginManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ export async function toKernelPluginManager<
return {
sudoValidator: sudo,
regularValidator: regular,
activeValidatorMode: sudo && !regular ? "sudo" : "regular",
...activeValidator,
hook,
getIdentifier,
Expand Down
1 change: 1 addition & 0 deletions packages/core/actions/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export {
} from "./getSenderAddress.js"
export { isSmartAccountDeployed } from "./isSmartAccountDeployed.js"
export { getKernelImplementationAddress } from "./getKernelImplementationAddress.js"
export { isPluginInstalled } from "./isPluginInstalled.js"
62 changes: 62 additions & 0 deletions packages/core/actions/public/isPluginInstalled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Address, Client, Hex } from "viem"
import { readContract } from "viem/actions"
import { getAction } from "viem/utils"
import { KernelModuleIsModuleInstalledAbi } from "../../accounts/kernel/abi/kernel_v_3_0_0/KernelModuleAbi.js"
import type { PLUGIN_TYPE } from "../../constants.js"

export type IsPluginInstalledParams = {
address: Address
plugin: {
type: (typeof PLUGIN_TYPE)[keyof typeof PLUGIN_TYPE]
address: Address
data?: Hex
}
}

/**
* Returns whether a plugin is installed on the account.
*
* @param client - Client to use
* @param args - {@link IsPluginInstalledParams} address and plugin details
* @returns boolean indicating if plugin is installed
*
* @example
* import { createPublicClient } from "viem"
* import { isPluginInstalled } from "@zerodev/sdk"
*
* const client = createPublicClient({
* chain: mainnet,
* transport: http()
* })
*
* const isInstalled = await isPluginInstalled(client, {
* address: accountAddress,
* plugin: {
* type: PLUGIN_TYPE.VALIDATOR,
* address: validatorAddress,
* data: "0x"
* }
* })
*/
export const isPluginInstalled = async (
client: Client,
args: IsPluginInstalledParams
): Promise<boolean> => {
const { address, plugin } = args
const { type, address: pluginAddress, data = "0x" } = plugin

try {
return await getAction(
client,
readContract,
"readContract"
)({
address,
abi: KernelModuleIsModuleInstalledAbi,
functionName: "isModuleInstalled",
args: [BigInt(type), pluginAddress, data]
})
} catch (error) {
return false
}
}
9 changes: 9 additions & 0 deletions packages/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ export enum EXEC_TYPE {
TRY_EXEC = "0x01"
}

export const PLUGIN_TYPE = {
VALIDATOR: 1,
EXECUTOR: 2,
FALLBACK: 3,
HOOK: 4,
POLICY: 5,
SIGNER: 6
}

// Safe's library for create and create2: https://github.com/safe-global/safe-contracts/blob/0acdd35a203299585438f53885df630f9d486a86/contracts/libraries/CreateCall.sol
// Address was found here: https://github.com/safe-global/safe-deployments/blob/926ec6bbe2ebcac3aa2c2c6c0aff74aa590cbc6a/src/assets/v1.4.1/create_call.json
export const safeCreateCallAddress =
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zerodev/sdk",
"version": "5.4.9",
"version": "5.4.10",
"author": "ZeroDev",
"main": "./_cjs/index.js",
"module": "./_esm/index.js",
Expand Down
3 changes: 2 additions & 1 deletion packages/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export type {
KERNEL_VERSION_TYPE,
GetKernelVersion,
GetEntryPointAbi,
EntryPointType
EntryPointType,
PluginMigrationData
} from "./kernel.js"

export { ValidatorMode } from "./kernel.js"
Expand Down
11 changes: 10 additions & 1 deletion packages/core/types/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
entryPoint06Abi,
entryPoint07Abi
} from "viem/account-abstraction"
import type { VALIDATOR_TYPE } from "../constants.js"
import type { PLUGIN_TYPE, VALIDATOR_TYPE } from "../constants.js"
export type ZeroDevPaymasterRpcSchema<
entryPointVersion extends EntryPointVersion
> = [
Expand Down Expand Up @@ -154,10 +154,13 @@ export type ValidatorInitData = {
initConfig?: Hex[]
}

export type ActiveValidatorMode = "sudo" | "regular"

export type KernelPluginManager<entryPointVersion extends EntryPointVersion> =
KernelValidator & {
sudoValidator?: KernelValidator
regularValidator?: KernelValidator
activeValidatorMode: ActiveValidatorMode
hook?: KernelValidatorHook
getPluginEnableSignature(accountAddress: Address): Promise<Hex>
getValidatorInitData(): Promise<ValidatorInitData>
Expand Down Expand Up @@ -221,6 +224,12 @@ export enum ValidatorMode {
enable = "0x00000002"
}

export type PluginMigrationData = {
type: (typeof PLUGIN_TYPE)[keyof typeof PLUGIN_TYPE]
address: Address
data: Hex
}

export type CallType = "call" | "delegatecall"

export type KernelEncodeCallDataArgs = {
Expand Down
Loading

0 comments on commit 5828663

Please sign in to comment.