Skip to content

Commit

Permalink
feat: add view functions and user op calldata encodings
Browse files Browse the repository at this point in the history
  • Loading branch information
howydev committed Dec 19, 2024
1 parent 5f23c26 commit 55bb274
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 71 deletions.
131 changes: 124 additions & 7 deletions account-kit/smart-contracts/src/ma-v2/account/semiModularAccountV2.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,79 @@
import type {
EntryPointDef,
SmartAccountSigner,
AccountOp,
SmartContractAccountWithSigner,
ToSmartContractAccountParams,
} from "@aa-sdk/core";
import {
createBundlerClient,
getEntryPoint,
toSmartContractAccount,
InvalidEntityIdError,
InvalidNonceKeyError,
} from "@aa-sdk/core";
import {
concatHex,
encodeFunctionData,
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<ExecutionDataView>;
getValidationData: (
args: ValidationDataParams
) => Promise<ValidationDataView>;
encodeCallData: (callData: Hex) => Promise<Hex>;
};

export type CreateSMAV2AccountParams<
TTransport extends Transport = Transport,
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -110,14 +147,43 @@ export async function createSMAV2Account(
]);
};

const encodeExecute: (tx: AccountOp) => Promise<Hex> = async ({
target,
data,
value,
}) =>
await encodeCallData(
encodeFunctionData({
abi: modularAccountAbi,
functionName: "execute",
args: [target, value ?? 0n, data],
})
);

const encodeBatchExecute: (txs: AccountOp[]) => Promise<Hex> = 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),
});

Expand All @@ -144,11 +210,62 @@ export async function createSMAV2Account(
]) as Promise<bigint>;
};

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<Hex> => {
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,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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[];
Expand All @@ -39,41 +32,39 @@ export type InstallValidationParams<
hookConfig: HookConfig;
initData: Hex;
}[];
} & UserOperationOverridesParameter<GetEntryPointFromAccount<TAccount>> &
GetAccountParameter<TAccount>;
account?: SMAV2Account<TSigner> | undefined;
} & UserOperationOverridesParameter<
GetEntryPointFromAccount<SMAV2Account<TSigner>>
>;

export type UninstallValidationParams<
TAccount extends SmartContractAccount | undefined =
| SmartContractAccount
| undefined
TSigner extends SmartAccountSigner = SmartAccountSigner
> = {
moduleAddress: Address;
entityId: number;
uninstallData: Hex;
hookUninstallDatas: Hex[];
} & UserOperationOverridesParameter<GetEntryPointFromAccount<TAccount>> &
GetAccountParameter<TAccount>;
account?: SMAV2Account<TSigner> | undefined;
} & UserOperationOverridesParameter<
GetEntryPointFromAccount<SMAV2Account<TSigner>>
>;

export type InstallValidationActions<
TAccount extends SmartContractAccount | undefined =
| SmartContractAccount
| undefined
TSigner extends SmartAccountSigner = SmartAccountSigner
> = {
installValidation: (
args: InstallValidationParams<TAccount>
args: InstallValidationParams<TSigner>
) => Promise<SendUserOperationResult>;
uninstallValidation: (
args: UninstallValidationParams<TAccount>
args: UninstallValidationParams<TSigner>
) => Promise<SendUserOperationResult>;
};

export const installValidationActions: <
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined,
TAccount extends SmartContractAccount = SmartContractAccount
TSigner extends SmartAccountSigner = SmartAccountSigner
>(
client: SmartAccountClient<TTransport, TChain, TAccount>
) => InstallValidationActions<TAccount> = (client) => ({
client: SMAV2AccountClient<TSigner>
) => InstallValidationActions<TSigner> = (client) => ({
installValidation: async ({
validationConfig,
selectors,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 2 additions & 5 deletions account-kit/smart-contracts/src/ma-v2/client/client.test.ts
Original file line number Diff line number Diff line change
@@ -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";

Check warning on line 3 in account-kit/smart-contracts/src/ma-v2/client/client.test.ts

View workflow job for this annotation

GitHub Actions / Lint

'SMAV2AccountClient' is defined but never used
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;
Expand Down
Loading

0 comments on commit 55bb274

Please sign in to comment.