Skip to content

Commit

Permalink
feat: add plugin manager decorator for MSCA
Browse files Browse the repository at this point in the history
  • Loading branch information
moldy530 committed Nov 27, 2023
1 parent dc4c98b commit 4c1b14b
Show file tree
Hide file tree
Showing 6 changed files with 343 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/accounts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export {
createMultiOwnerMSCASchema,
type MultiOwnerMSCAParams,
} from "./msca/multi-owner-account.js";
export { pluginManagerDecorator } from "./msca/plugins/manager/decorator.js";
export type * from "./msca/plugins/manager/installPlugin.js";
export type * from "./msca/plugins/manager/types.js";
export type * from "./msca/plugins/manager/uninstallPlugin.js";
export {
MultiOwnerPlugin,
MultiOwnerPluginExecutionFunctionAbi,
Expand Down
223 changes: 223 additions & 0 deletions packages/accounts/src/msca/abis/IPluginManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
export const IPluginManagerAbi = [
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "plugin",
type: "address",
},
{
indexed: true,
internalType: "address",
name: "providingPlugin",
type: "address",
},
],
name: "PluginIgnoredHookUnapplyCallbackFailure",
type: "event",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "plugin",
type: "address",
},
],
name: "PluginIgnoredUninstallCallbackFailure",
type: "event",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "plugin",
type: "address",
},
{
indexed: false,
internalType: "bytes32",
name: "manifestHash",
type: "bytes32",
},
{
indexed: false,
internalType: "FunctionReference[]",
name: "dependencies",
type: "bytes21[]",
},
{
components: [
{
internalType: "address",
name: "providingPlugin",
type: "address",
},
{
internalType: "bytes4",
name: "selector",
type: "bytes4",
},
{
components: [
{
internalType: "uint8",
name: "preExecHookFunctionId",
type: "uint8",
},
{
internalType: "bool",
name: "isPostHookUsed",
type: "bool",
},
{
internalType: "uint8",
name: "postExecHookFunctionId",
type: "uint8",
},
],
internalType: "struct IPluginManager.InjectedHooksInfo",
name: "injectedHooksInfo",
type: "tuple",
},
{
internalType: "bytes",
name: "hookApplyData",
type: "bytes",
},
],
indexed: false,
internalType: "struct IPluginManager.InjectedHook[]",
name: "injectedHooks",
type: "tuple[]",
},
],
name: "PluginInstalled",
type: "event",
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: "address",
name: "plugin",
type: "address",
},
{
indexed: true,
internalType: "bool",
name: "callbacksSucceeded",
type: "bool",
},
],
name: "PluginUninstalled",
type: "event",
},
{
inputs: [
{
internalType: "address",
name: "plugin",
type: "address",
},
{
internalType: "bytes32",
name: "manifestHash",
type: "bytes32",
},
{
internalType: "bytes",
name: "pluginInitData",
type: "bytes",
},
{
internalType: "FunctionReference[]",
name: "dependencies",
type: "bytes21[]",
},
{
components: [
{
internalType: "address",
name: "providingPlugin",
type: "address",
},
{
internalType: "bytes4",
name: "selector",
type: "bytes4",
},
{
components: [
{
internalType: "uint8",
name: "preExecHookFunctionId",
type: "uint8",
},
{
internalType: "bool",
name: "isPostHookUsed",
type: "bool",
},
{
internalType: "uint8",
name: "postExecHookFunctionId",
type: "uint8",
},
],
internalType: "struct IPluginManager.InjectedHooksInfo",
name: "injectedHooksInfo",
type: "tuple",
},
{
internalType: "bytes",
name: "hookApplyData",
type: "bytes",
},
],
internalType: "struct IPluginManager.InjectedHook[]",
name: "injectedHooks",
type: "tuple[]",
},
],
name: "installPlugin",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "plugin",
type: "address",
},
{
internalType: "bytes",
name: "config",
type: "bytes",
},
{
internalType: "bytes",
name: "pluginUninstallData",
type: "bytes",
},
{
internalType: "bytes[]",
name: "hookUnapplyData",
type: "bytes[]",
},
],
name: "uninstallPlugin",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
] as const;
18 changes: 18 additions & 0 deletions packages/accounts/src/msca/plugins/manager/decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ISmartAccountProvider } from "@alchemy/aa-core";
import type { MSCA } from "../../builder";
import { installPlugin, type InstallPluginParams } from "./installPlugin.js";
import {
uninstallPlugin,
type UninstallPluginParams,
} from "./uninstallPlugin.js";

export const pluginManagerDecorator = <
P extends ISmartAccountProvider & { account: MSCA }
>(
provider: P
) => ({
installPlugin: (params: InstallPluginParams) =>
installPlugin<P>(provider, params),
uninstallPlugin: (params: UninstallPluginParams) =>
uninstallPlugin<P>(provider, params),
});
54 changes: 54 additions & 0 deletions packages/accounts/src/msca/plugins/manager/installPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { ISmartAccountProvider } from "@alchemy/aa-core";
import {
encodeFunctionData,
encodeFunctionResult,
keccak256,
type Address,
type Hash,
} from "viem";
import { IPluginAbi } from "../../abis/IPlugin.js";
import { IPluginManagerAbi } from "../../abis/IPluginManager.js";
import type { MSCA } from "../../builder";
import type { InjectedHook } from "./types";

export type InstallPluginParams = {
pluginAddress: Address;
manifestHash?: Hash;
pluginInitData?: Hash;
dependencies?: Address[];
injectedHooks?: InjectedHook[];
};

export async function installPlugin<
P extends ISmartAccountProvider & { account: MSCA }
>(provider: P, params: InstallPluginParams) {
const pluginManifest = await provider.rpcClient.readContract({
abi: IPluginAbi,
address: params.pluginAddress,
functionName: "pluginManifest",
});
// use the manifest hash passed in or get it from the plugin
const manifestHash: Hash =
params.manifestHash ??
keccak256(
encodeFunctionResult({
abi: IPluginAbi,
functionName: "pluginManifest",
result: pluginManifest,
})
);

const callData = encodeFunctionData({
abi: IPluginManagerAbi,
functionName: "installPlugin",
args: [
params.pluginAddress,
manifestHash,
params.pluginInitData ?? "0x",
params.dependencies ?? [],
params.injectedHooks ?? [],
],
});

return provider.sendUserOperation(callData);
}
16 changes: 16 additions & 0 deletions packages/accounts/src/msca/plugins/manager/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Address, Hash } from "viem";

export type InjectedHooksInfo = {
preExecHookFunctionId: number;
isPostHookUsed: boolean;
postExecHookFunctionId: number;
};

export type InjectedHook = {
// The plugin that provides the hook
providingPlugin: Address;
// Either a plugin-defined execution function, or the native function executeFromPluginExternal
selector: Hash;
injectedHooksInfo: InjectedHooksInfo;
hookApplyData: Hash;
};
28 changes: 28 additions & 0 deletions packages/accounts/src/msca/plugins/manager/uninstallPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { ISmartAccountProvider } from "@alchemy/aa-core";
import { encodeFunctionData, type Address, type Hash } from "viem";
import { IPluginManagerAbi } from "../../abis/IPluginManager.js";
import type { MSCA } from "../../builder";

export type UninstallPluginParams = {
pluginAddress: Address;
config?: Hash;
pluginUninstallData?: Hash;
hookUnapplyData?: Hash[];
};

export async function uninstallPlugin<
P extends ISmartAccountProvider & { account: MSCA }
>(provider: P, params: UninstallPluginParams) {
const callData = encodeFunctionData({
abi: IPluginManagerAbi,
functionName: "uninstallPlugin",
args: [
params.pluginAddress,
params.config ?? "0x",
params.pluginUninstallData ?? "0x",
params.hookUnapplyData ?? [],
],
});

return provider.sendUserOperation(callData);
}

0 comments on commit 4c1b14b

Please sign in to comment.