From 55bb274b7a0a455535461947db8c8e56e03d0a0a Mon Sep 17 00:00:00 2001 From: howydev <132113803+howydev@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:58:19 -0500 Subject: [PATCH] feat: add view functions and user op calldata encodings --- .../src/ma-v2/account/semiModularAccountV2.ts | 131 +++++++++++++++++- .../install-validation/installValidation.ts | 111 ++++++++------- .../src/ma-v2/client/client.test.ts | 7 +- .../src/ma-v2/client/client.ts | 10 +- .../smart-contracts/src/ma-v2/utils.ts | 2 + 5 files changed, 190 insertions(+), 71 deletions(-) diff --git a/account-kit/smart-contracts/src/ma-v2/account/semiModularAccountV2.ts b/account-kit/smart-contracts/src/ma-v2/account/semiModularAccountV2.ts index 2d3aee96a3..090fc7b11d 100644 --- a/account-kit/smart-contracts/src/ma-v2/account/semiModularAccountV2.ts +++ b/account-kit/smart-contracts/src/ma-v2/account/semiModularAccountV2.ts @@ -1,6 +1,7 @@ import type { EntryPointDef, SmartAccountSigner, + AccountOp, SmartContractAccountWithSigner, ToSmartContractAccountParams, } from "@aa-sdk/core"; @@ -8,6 +9,8 @@ import { createBundlerClient, getEntryPoint, toSmartContractAccount, + InvalidEntityIdError, + InvalidNonceKeyError, } from "@aa-sdk/core"; import { concatHex, @@ -15,28 +18,62 @@ import { getContract, maxUint32, maxUint152, + zeroAddress, type Address, type Chain, type Hex, type Transport, } from "viem"; import { accountFactoryAbi } from "../abis/accountFactoryAbi.js"; -import { getDefaultMAV2FactoryAddress } from "../utils.js"; -import { standardExecutor } from "../../msca/account/standardExecutor.js"; +import { + getDefaultMAV2FactoryAddress, + DEFAULT_OWNER_ENTITY_ID, +} from "../utils.js"; import { singleSignerMessageSigner } from "../modules/single-signer-validation/signer.js"; -import { InvalidEntityIdError, InvalidNonceKeyError } from "@aa-sdk/core"; +import { modularAccountAbi } from "../abis/modularAccountAbi.js"; +import { serializeModuleEntity } from "../actions/common/utils.js"; -export const DEFAULT_OWNER_ENTITY_ID = 0; +const executeUserOpSelector: Hex = "0x8DD7712F"; export type SignerEntity = { isGlobalValidation: boolean; entityId: number; }; +export type ExecutionDataView = { + module: Address; + skipRuntimeValidation: boolean; + allowGlobalValidation: boolean; + executionHooks: readonly Hex[]; +}; + +export type ValidationDataView = { + validationHooks: readonly Hex[]; + executionHooks: readonly Hex[]; + selectors: readonly Hex[]; + validationFlags: number; +}; + +export type ValidationDataParams = + | { + validationModuleAddress: Address; + entityId?: never; + } + | { + validationModuleAddress?: never; + entityId: number; + }; + export type SMAV2Account< TSigner extends SmartAccountSigner = SmartAccountSigner > = SmartContractAccountWithSigner<"SMAV2Account", TSigner, "0.7.0"> & - SignerEntity; + SignerEntity & { + getExecutionData: (selector: Hex) => Promise; + getValidationData: ( + args: ValidationDataParams + ) => Promise; + encodeCallData: (callData: Hex) => Promise; + }; export type CreateSMAV2AccountParams< TTransport extends Transport = Transport, @@ -80,7 +117,7 @@ export async function createSMAV2Account( accountAddress, entryPoint = getEntryPoint(chain, { version: "0.7.0" }), isGlobalValidation = true, - entityId = 0, + entityId = DEFAULT_OWNER_ENTITY_ID, } = config; if (entityId > Number(maxUint32)) { @@ -110,14 +147,43 @@ export async function createSMAV2Account( ]); }; + const encodeExecute: (tx: AccountOp) => Promise = async ({ + target, + data, + value, + }) => + await encodeCallData( + encodeFunctionData({ + abi: modularAccountAbi, + functionName: "execute", + args: [target, value ?? 0n, data], + }) + ); + + const encodeBatchExecute: (txs: AccountOp[]) => Promise = async (txs) => + await encodeCallData( + encodeFunctionData({ + abi: modularAccountAbi, + functionName: "executeBatch", + args: [ + txs.map((tx) => ({ + target: tx.target, + data: tx.data, + value: tx.value ?? 0n, + })), + ], + }) + ); + const baseAccount = await toSmartContractAccount({ transport, chain, entryPoint, accountAddress, source: `SMAV2Account`, + encodeExecute, + encodeBatchExecute, getAccountInitCode, - ...standardExecutor, ...singleSignerMessageSigner(signer), }); @@ -144,11 +210,62 @@ export async function createSMAV2Account( ]) as Promise; }; + const accountContract = getContract({ + address: baseAccount.address, + abi: modularAccountAbi, + client, + }); + + const getExecutionData = async (selector: Hex) => { + if (!(await baseAccount.isAccountDeployed())) { + return { + module: zeroAddress, + skipRuntimeValidation: false, + allowGlobalValidation: false, + executionHooks: [], + }; + } + + return await accountContract.read.getExecutionData([selector]); + }; + + const getValidationData = async (args: ValidationDataParams) => { + if (!(await baseAccount.isAccountDeployed())) { + return { + validationHooks: [], + executionHooks: [], + selectors: [], + validationFlags: 0, + }; + } + + const { validationModuleAddress, entityId } = args; + return await accountContract.read.getValidationData([ + serializeModuleEntity({ + moduleAddress: validationModuleAddress ?? zeroAddress, + entityId: entityId ?? Number(maxUint32), + }), + ]); + }; + + const encodeCallData = async (callData: Hex): Promise => { + const validationData = await getValidationData({ + entityId: Number(entityId), + }); + + return validationData.executionHooks.length + ? concatHex([executeUserOpSelector, callData]) + : callData; + }; + return { ...baseAccount, getAccountNonce, getSigner: () => signer, isGlobalValidation, entityId, + getExecutionData, + getValidationData, + encodeCallData, }; } diff --git a/account-kit/smart-contracts/src/ma-v2/actions/install-validation/installValidation.ts b/account-kit/smart-contracts/src/ma-v2/actions/install-validation/installValidation.ts index f2d77d386c..7d9edf589c 100644 --- a/account-kit/smart-contracts/src/ma-v2/actions/install-validation/installValidation.ts +++ b/account-kit/smart-contracts/src/ma-v2/actions/install-validation/installValidation.ts @@ -3,21 +3,12 @@ import { IncompatibleClientError, isSmartAccountClient, EntityIdOverrideError, - type GetAccountParameter, type GetEntryPointFromAccount, type SendUserOperationResult, - type SmartContractAccount, - type SmartAccountClient, type UserOperationOverridesParameter, + type SmartAccountSigner, } from "@aa-sdk/core"; -import { - type Address, - type Hex, - type Chain, - type Transport, - encodeFunctionData, - concatHex, -} from "viem"; +import { type Address, type Hex, encodeFunctionData, concatHex } from "viem"; import { semiModularAccountBytecodeAbi } from "../../abis/semiModularAccountBytecodeAbi.js"; import type { HookConfig, ValidationConfig } from "../common/types.js"; @@ -27,10 +18,12 @@ import { serializeModuleEntity, } from "../common/utils.js"; +import { type SMAV2AccountClient } from "../../client/client.js"; +import { type SMAV2Account } from "../../account/semiModularAccountV2.js"; +import { DEFAULT_OWNER_ENTITY_ID } from "../../utils.js"; + export type InstallValidationParams< - TAccount extends SmartContractAccount | undefined = - | SmartContractAccount - | undefined + TSigner extends SmartAccountSigner = SmartAccountSigner > = { validationConfig: ValidationConfig; selectors: Hex[]; @@ -39,41 +32,39 @@ export type InstallValidationParams< hookConfig: HookConfig; initData: Hex; }[]; -} & UserOperationOverridesParameter> & - GetAccountParameter; + account?: SMAV2Account | undefined; +} & UserOperationOverridesParameter< + GetEntryPointFromAccount> +>; export type UninstallValidationParams< - TAccount extends SmartContractAccount | undefined = - | SmartContractAccount - | undefined + TSigner extends SmartAccountSigner = SmartAccountSigner > = { moduleAddress: Address; entityId: number; uninstallData: Hex; hookUninstallDatas: Hex[]; -} & UserOperationOverridesParameter> & - GetAccountParameter; + account?: SMAV2Account | undefined; +} & UserOperationOverridesParameter< + GetEntryPointFromAccount> +>; export type InstallValidationActions< - TAccount extends SmartContractAccount | undefined = - | SmartContractAccount - | undefined + TSigner extends SmartAccountSigner = SmartAccountSigner > = { installValidation: ( - args: InstallValidationParams + args: InstallValidationParams ) => Promise; uninstallValidation: ( - args: UninstallValidationParams + args: UninstallValidationParams ) => Promise; }; export const installValidationActions: < - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends SmartContractAccount = SmartContractAccount + TSigner extends SmartAccountSigner = SmartAccountSigner >( - client: SmartAccountClient -) => InstallValidationActions = (client) => ({ + client: SMAV2AccountClient +) => InstallValidationActions = (client) => ({ installValidation: async ({ validationConfig, selectors, @@ -94,22 +85,26 @@ export const installValidationActions: < ); } - if (validationConfig.entityId === 0) { + if (validationConfig.entityId === DEFAULT_OWNER_ENTITY_ID) { throw new EntityIdOverrideError(); } - const callData = encodeFunctionData({ - abi: semiModularAccountBytecodeAbi, - functionName: "installValidation", - args: [ - serializeValidationConfig(validationConfig), - selectors, - installData, - hooks.map((hook: { hookConfig: HookConfig; initData: Hex }) => - concatHex([serializeHookConfig(hook.hookConfig), hook.initData]) - ), - ], - }); + const { encodeCallData } = account; + + const callData = await encodeCallData( + encodeFunctionData({ + abi: semiModularAccountBytecodeAbi, + functionName: "installValidation", + args: [ + serializeValidationConfig(validationConfig), + selectors, + installData, + hooks.map((hook: { hookConfig: HookConfig; initData: Hex }) => + concatHex([serializeHookConfig(hook.hookConfig), hook.initData]) + ), + ], + }) + ); return client.sendUserOperation({ uo: callData, @@ -138,18 +133,22 @@ export const installValidationActions: < ); } - const callData = encodeFunctionData({ - abi: semiModularAccountBytecodeAbi, - functionName: "uninstallValidation", - args: [ - serializeModuleEntity({ - moduleAddress, - entityId, - }), - uninstallData, - hookUninstallDatas, - ], - }); + const { encodeCallData } = account; + + const callData = await encodeCallData( + encodeFunctionData({ + abi: semiModularAccountBytecodeAbi, + functionName: "uninstallValidation", + args: [ + serializeModuleEntity({ + moduleAddress, + entityId, + }), + uninstallData, + hookUninstallDatas, + ], + }) + ); return client.sendUserOperation({ uo: callData, diff --git a/account-kit/smart-contracts/src/ma-v2/client/client.test.ts b/account-kit/smart-contracts/src/ma-v2/client/client.test.ts index 3664af3ee8..31e59d228c 100644 --- a/account-kit/smart-contracts/src/ma-v2/client/client.test.ts +++ b/account-kit/smart-contracts/src/ma-v2/client/client.test.ts @@ -1,15 +1,12 @@ import { custom, parseEther, publicActions } from "viem"; - import { LocalAccountSigner, type SmartAccountSigner } from "@aa-sdk/core"; - -import { createSMAV2AccountClient } from "./client.js"; - +import { createSMAV2AccountClient, type SMAV2AccountClient } from "./client.js"; import { local070Instance } from "~test/instances.js"; import { setBalance } from "viem/actions"; import { accounts } from "~test/constants.js"; -import { installValidationActions } from "../actions/install-validation/installValidation.js"; import { getDefaultSingleSignerValidationModuleAddress } from "../modules/utils.js"; import { SingleSignerValidationModule } from "../modules/single-signer-validation/module.js"; +import { installValidationActions } from "../actions/install-validation/installValidation.js"; describe("MA v2 Tests", async () => { const instance = local070Instance; diff --git a/account-kit/smart-contracts/src/ma-v2/client/client.ts b/account-kit/smart-contracts/src/ma-v2/client/client.ts index 946061daa9..aba7e35df2 100644 --- a/account-kit/smart-contracts/src/ma-v2/client/client.ts +++ b/account-kit/smart-contracts/src/ma-v2/client/client.ts @@ -4,7 +4,7 @@ import { type SmartAccountSigner, type SmartAccountClientConfig, } from "@aa-sdk/core"; -import { type Chain, type CustomTransport, type Transport } from "viem"; +import { type Chain, type Transport } from "viem"; import { createSMAV2Account, @@ -12,6 +12,10 @@ import { type SMAV2Account, } from "../account/semiModularAccountV2.js"; +export type SMAV2AccountClient< + TSigner extends SmartAccountSigner = SmartAccountSigner +> = SmartAccountClient>; + export type CreateSMAV2AccountClientParams< TTransport extends Transport = Transport, TChain extends Chain = Chain, @@ -27,7 +31,7 @@ export function createSMAV2AccountClient< TSigner extends SmartAccountSigner = SmartAccountSigner >( args: CreateSMAV2AccountClientParams -): Promise>>; +): Promise>; /** * Creates a MAv2 account client using the provided configuration parameters. @@ -60,7 +64,7 @@ export function createSMAV2AccountClient< */ export async function createSMAV2AccountClient({ ...config -}: CreateSMAV2AccountClientParams): Promise { +}: CreateSMAV2AccountClientParams): Promise { const maV2Account = await createSMAV2Account({ ...config, }); diff --git a/account-kit/smart-contracts/src/ma-v2/utils.ts b/account-kit/smart-contracts/src/ma-v2/utils.ts index 241f89885b..73e241b70b 100644 --- a/account-kit/smart-contracts/src/ma-v2/utils.ts +++ b/account-kit/smart-contracts/src/ma-v2/utils.ts @@ -12,6 +12,8 @@ import { sepolia, } from "@account-kit/infra"; +export const DEFAULT_OWNER_ENTITY_ID = 0; + export type PackSignatureParams = { // orderedHookData: HookData[]; validationSignature: Hex;