From 5d1f24e9b57bdecde4d4c947c83f376be8f3720a Mon Sep 17 00:00:00 2001 From: Dennis Won Date: Mon, 4 Dec 2023 07:04:15 -0800 Subject: [PATCH] feat: add optional param account loupe address to msca --- .../accounts/src/msca/abis/IAccountLoupe.ts | 142 ++++++++++++++++++ .../src/msca/account-loupe/decorator.ts | 48 ++++++ .../accounts/src/msca/account-loupe/types.ts | 56 +++++++ packages/accounts/src/msca/builder.ts | 17 +++ .../accounts/src/msca/multi-owner-account.ts | 10 +- 5 files changed, 270 insertions(+), 3 deletions(-) create mode 100644 packages/accounts/src/msca/abis/IAccountLoupe.ts create mode 100644 packages/accounts/src/msca/account-loupe/decorator.ts create mode 100644 packages/accounts/src/msca/account-loupe/types.ts diff --git a/packages/accounts/src/msca/abis/IAccountLoupe.ts b/packages/accounts/src/msca/abis/IAccountLoupe.ts new file mode 100644 index 0000000000..22171e86e0 --- /dev/null +++ b/packages/accounts/src/msca/abis/IAccountLoupe.ts @@ -0,0 +1,142 @@ +export const IAccountLoupeAbi = [ + { + inputs: [ + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + ], + name: "getExecutionFunctionConfig", + outputs: [ + { + components: [ + { + internalType: "address", + name: "plugin", + type: "address", + }, + { + internalType: "FunctionReference", + name: "userOpValidationFunction", + type: "bytes21", + }, + { + internalType: "FunctionReference", + name: "runtimeValidationFunction", + type: "bytes21", + }, + ], + internalType: "struct IAccountLoupe.ExecutionFunctionConfig", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + ], + name: "getExecutionHooks", + outputs: [ + { + components: [ + { + internalType: "FunctionReference", + name: "preExecHook", + type: "bytes21", + }, + { + internalType: "FunctionReference", + name: "postExecHook", + type: "bytes21", + }, + ], + internalType: "struct IAccountLoupe.ExecutionHooks[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getInstalledPlugins", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "callingPlugin", + type: "address", + }, + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + ], + name: "getPermittedCallHooks", + outputs: [ + { + components: [ + { + internalType: "FunctionReference", + name: "preExecHook", + type: "bytes21", + }, + { + internalType: "FunctionReference", + name: "postExecHook", + type: "bytes21", + }, + ], + internalType: "struct IAccountLoupe.ExecutionHooks[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "selector", + type: "bytes4", + }, + ], + name: "getPreValidationHooks", + outputs: [ + { + internalType: "FunctionReference[]", + name: "preUserOpValidationHooks", + type: "bytes21[]", + }, + { + internalType: "FunctionReference[]", + name: "preRuntimeValidationHooks", + type: "bytes21[]", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/packages/accounts/src/msca/account-loupe/decorator.ts b/packages/accounts/src/msca/account-loupe/decorator.ts new file mode 100644 index 0000000000..45787a1900 --- /dev/null +++ b/packages/accounts/src/msca/account-loupe/decorator.ts @@ -0,0 +1,48 @@ +import type { Address, ISmartContractAccount } from "@alchemy/aa-core"; +import type { Hash } from "viem"; +import { IAccountLoupeAbi } from "../abis/IAccountLoupe.js"; +import type { FunctionReference, IAccountLoupe } from "./types.js"; + +export const accountLoupeDecorators = ( + account: ISmartContractAccount, + accountLoupeAddress: Address +): IAccountLoupe => ({ + getExecutionFunctionConfig: async (selector: FunctionReference) => + account.rpcProvider.readContract({ + address: accountLoupeAddress, + abi: IAccountLoupeAbi, + functionName: "getExecutionFunctionConfig", + args: [selector], + }), + + getExecutionHooks: async (selector: FunctionReference) => + account.rpcProvider.readContract({ + address: accountLoupeAddress, + abi: IAccountLoupeAbi, + functionName: "getExecutionHooks", + args: [selector], + }), + + getPermittedCallHooks: async (callingPlugin: Address, selector: Hash) => + account.rpcProvider.readContract({ + address: accountLoupeAddress, + abi: IAccountLoupeAbi, + functionName: "getPermittedCallHooks", + args: [callingPlugin, selector], + }), + + getPreValidationHooks: async (selector: Hash) => + account.rpcProvider.readContract({ + address: accountLoupeAddress, + abi: IAccountLoupeAbi, + functionName: "getPreValidationHooks", + args: [selector], + }), + + getInstalledPlugins: async () => + account.rpcProvider.readContract({ + address: accountLoupeAddress, + abi: IAccountLoupeAbi, + functionName: "getInstalledPlugins", + }), +}); diff --git a/packages/accounts/src/msca/account-loupe/types.ts b/packages/accounts/src/msca/account-loupe/types.ts new file mode 100644 index 0000000000..97c76cb55f --- /dev/null +++ b/packages/accounts/src/msca/account-loupe/types.ts @@ -0,0 +1,56 @@ +import type { Address, Hash, Hex } from "viem"; + +// Treats the first 20 bytes as an address, and the last byte as a identifier. +export type FunctionReference = Hex; + +export type ExecutionFunctionConfig = { + plugin: Address; + userOpValidationFunction: FunctionReference; + runtimeValidationFunction: FunctionReference; +}; + +export type ExecutionHooks = { + preExecHook: FunctionReference; + postExecHook: FunctionReference; +}; + +export type PreValidationHooks = [ + readonly FunctionReference[], + readonly FunctionReference[] +]; + +export interface IAccountLoupe { + /// @notice Gets the validation functions and plugin address for a selector + /// @dev If the selector is a native function, the plugin address will be the address of the account + /// @param selector The selector to get the configuration for + /// @return The configuration for this selector + getExecutionFunctionConfig( + selector: FunctionReference + ): Promise; + + /// @notice Gets the pre and post execution hooks for a selector + /// @param selector The selector to get the hooks for + /// @return The pre and post execution hooks for this selector + getExecutionHooks( + selector: FunctionReference + ): Promise>; + + /// @notice Gets the pre and post permitted call hooks applied for a plugin calling this selector + /// @param callingPlugin The plugin that is calling the selector + /// @param selector The selector the plugin is calling + /// @return The pre and post permitted call hooks for this selector + getPermittedCallHooks( + callingPlugin: Address, + selector: Hash + ): Promise>; + + /// @notice Gets the pre user op and runtime validation hooks associated with a selector + /// @param selector The selector to get the hooks for + /// @return preUserOpValidationHooks The pre user op validation hooks for this selector + /// @return preRuntimeValidationHooks The pre runtime validation hooks for this selector + getPreValidationHooks(selector: Hash): Promise>; + + /// @notice Gets an array of all installed plugins + /// @return The addresses of all installed plugins + getInstalledPlugins(): Promise>; +} diff --git a/packages/accounts/src/msca/builder.ts b/packages/accounts/src/msca/builder.ts index 950c703482..b93a49b179 100644 --- a/packages/accounts/src/msca/builder.ts +++ b/packages/accounts/src/msca/builder.ts @@ -15,6 +15,8 @@ import { } from "viem"; import { z } from "zod"; import { IStandardExecutorAbi } from "./abis/IStandardExecutor.js"; +import { accountLoupeDecorators } from "./account-loupe/decorator.js"; +import type { IAccountLoupe } from "./account-loupe/types.js"; import { pluginManagerDecorator } from "./plugin-manager/decorator.js"; import type { Plugin } from "./plugins/types"; @@ -30,6 +32,10 @@ export interface IMSCA< plugin: Plugin ) => IMSCA & AD; + extendWithAccountLoupeMethods: ( + accountLoupeAddress: Address + ) => IMSCA & AD & IAccountLoupe; + addProviderDecorator: < PD, TProvider extends ISmartAccountProvider & { account: IMSCA } @@ -190,6 +196,17 @@ export class MSCABuilder { return result as unknown as DynamicMSCA & AD; }; + extendWithAccountLoupeMethods = ( + accountLoupeAddress: Address + ): DynamicMSCA & AD & IAccountLoupe => { + return Object.assign( + this, + accountLoupeDecorators(this, accountLoupeAddress) + ) as unknown as DynamicMSCA & + AD & + IAccountLoupe; + }; + addProviderDecorator = < PD, TProvider extends ISmartAccountProvider & { account: IMSCA } diff --git a/packages/accounts/src/msca/multi-owner-account.ts b/packages/accounts/src/msca/multi-owner-account.ts index 62008ab0a8..774bb135dc 100644 --- a/packages/accounts/src/msca/multi-owner-account.ts +++ b/packages/accounts/src/msca/multi-owner-account.ts @@ -4,6 +4,7 @@ import { type SignTypedDataParams, type SupportedTransports, } from "@alchemy/aa-core"; +import { Address as zAddress } from "abitype/zod"; import { concatHex, encodeFunctionData, @@ -26,6 +27,7 @@ export const createMultiOwnerMSCASchema = < >() => createBaseSmartAccountParamsSchema().extend({ owner: SignerSchema, + accountLoupeAddress: zAddress.optional(), index: z.bigint().optional().default(0n), }); @@ -120,9 +122,11 @@ export const createMultiOwnerMSCA = < const params = createMultiOwnerMSCASchema().parse(params_); const builder = createMultiOwnerMSCABuilder(params); - const account = builder - .build(params) - .extendWithPluginMethods(MultiOwnerPlugin); + let account = builder.build(params).extendWithPluginMethods(MultiOwnerPlugin); + + if (params.accountLoupeAddress != null) { + account = account.extendWithAccountLoupeMethods(params.accountLoupeAddress); + } return account; };