Skip to content

Commit

Permalink
feat: add install validation, add tests for session key
Browse files Browse the repository at this point in the history
  • Loading branch information
howydev committed Dec 18, 2024
1 parent 47a7d71 commit be95010
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 9 deletions.
16 changes: 16 additions & 0 deletions aa-sdk/core/src/errors/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,19 @@ export class InvalidNonceKeyError extends BaseError {
);
}
}

/**
* Error class denoting that the provided entity id is invalid because it's overriding the native entity id.
*/
export class EntityIdOverrideError extends BaseError {
override name = "InvalidNonceKeyError";

/**
* Initializes a new instance of the error message with a default message indicating that the nonce key is invalid.
*/
constructor() {
super(
`Installing entityId of 0 overrides the owner's entity id in the account`
);
}
}
1 change: 1 addition & 0 deletions aa-sdk/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export {
InvalidRpcUrlError,
InvalidEntityIdError,
InvalidNonceKeyError,
EntityIdOverrideError,
} from "./errors/client.js";
export {
EntryPointNotFoundError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
createBundlerClient,
getEntryPoint,
toSmartContractAccount,
getAccountAddress,

Check warning on line 11 in account-kit/smart-contracts/src/ma-v2/account/semiModularAccountV2.ts

View workflow job for this annotation

GitHub Actions / Lint

'getAccountAddress' is defined but never used
} from "@aa-sdk/core";
import {
concatHex,
Expand Down
37 changes: 37 additions & 0 deletions account-kit/smart-contracts/src/ma-v2/actions/common/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Address, Hex } from "viem";

export type ModuleEntity = {
moduleAddress: Address;
entityId: number;
};

export type ValidationConfig = {
moduleAddress: Address;
entityId: number; // uint32
isGlobal: boolean;
isSignatureValidation: boolean;
isUserOpValidation: boolean;
};

export enum HookType {
EXECUTION = "0x00",
VALIDATION = "0x01",
}

export type HookConfig = {
address: Address;
entityId: number; // uint32
hookType: HookType;
hasPreHooks: boolean;
hasPostHooks: boolean;
};

// maps to type ValidationStorage in MAv2 implementation
export type ValidationData = {
isGlobal: boolean; // validation flag
isSignatureValidation: boolean; // validation flag
isUserOpValidation: boolean;
validationHooks: HookConfig[];
executionHooks: Hex[];
selectors: Hex[];
};
32 changes: 32 additions & 0 deletions account-kit/smart-contracts/src/ma-v2/actions/common/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { type Hex, toHex, concatHex } from "viem";
import type { ValidationConfig, HookConfig, ModuleEntity } from "./types";
import { HookType } from "./types.js";

export function serializeValidationConfig(config: ValidationConfig): Hex {
const isUserOpValidationBit = config.isUserOpValidation ? 1 : 0;
const isSignatureValidationBit = config.isSignatureValidation ? 2 : 0;
const isGlobalBit = config.isGlobal ? 4 : 0;
return concatHex([
serializeModuleEntity(config),
toHex(isUserOpValidationBit + isSignatureValidationBit + isGlobalBit, {
size: 1,
}),
]);
}

export function serializeHookConfig(config: HookConfig): Hex {
const hookTypeBit = config.hookType === HookType.VALIDATION ? 1 : 0;
const hasPostHooksBit = config.hasPostHooks ? 2 : 0;
const hasPreHooksBit = config.hasPreHooks ? 4 : 0;
return concatHex([
config.address,
toHex(config.entityId, { size: 4 }),
toHex(hookTypeBit + hasPostHooksBit + hasPreHooksBit, {
size: 1,
}),
]);
}

export function serializeModuleEntity(config: ModuleEntity): Hex {
return concatHex([config.moduleAddress, toHex(config.entityId, { size: 4 })]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import {
AccountNotFoundError,
IncompatibleClientError,
isSmartAccountClient,
EntityIdOverrideError,
type GetAccountParameter,
type GetEntryPointFromAccount,
type SendUserOperationResult,
type SmartContractAccount,
type SmartAccountClient,
type UserOperationOverridesParameter,
} from "@aa-sdk/core";
import {
type Address,
type Hex,
type Chain,
type Transport,
encodeFunctionData,
concatHex,
} from "viem";

import { semiModularAccountBytecodeAbi } from "../../abis/semiModularAccountBytecodeAbi.js";
import type { HookConfig, ValidationConfig } from "../common/types.js";
import {
serializeValidationConfig,
serializeHookConfig,
serializeModuleEntity,
} from "../common/utils.js";

export type InstallValidationParams<
TAccount extends SmartContractAccount | undefined =
| SmartContractAccount
| undefined
> = {
validationConfig: ValidationConfig;
selectors: Hex[];
installData: Hex;
hooks: {
hookConfig: HookConfig;
initData: Hex;
}[];
} & UserOperationOverridesParameter<GetEntryPointFromAccount<TAccount>> &
GetAccountParameter<TAccount>;

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

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

export const installValidationActions: <
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined,
TAccount extends SmartContractAccount = SmartContractAccount
>(
client: SmartAccountClient<TTransport, TChain, TAccount>
) => InstallValidationActions<TAccount> = (client) => ({
installValidation: async ({
validationConfig,
selectors,
installData,
hooks,
account = client.account,
overrides,
}) => {
if (!account) {
throw new AccountNotFoundError();
}

if (!isSmartAccountClient(client)) {
throw new IncompatibleClientError(
"SmartAccountClient",
"installValidation",
client
);
}

if (validationConfig.entityId === 0) {
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])
),
],
});

return client.sendUserOperation({
uo: callData,
account,
overrides,
});
},

uninstallValidation: async ({
moduleAddress,
entityId,
uninstallData,
hookUninstallDatas,
account = client.account,
overrides,
}) => {
if (!account) {
throw new AccountNotFoundError();
}

if (!isSmartAccountClient(client)) {
throw new IncompatibleClientError(
"SmartAccountClient",
"uninstallValidation",
client
);
}

const callData = encodeFunctionData({
abi: semiModularAccountBytecodeAbi,
functionName: "uninstallValidation",
args: [
serializeModuleEntity({
moduleAddress,
entityId,
}),
uninstallData,
hookUninstallDatas,
],
});

return client.sendUserOperation({
uo: callData,
account,
overrides,
});
},
});
Loading

0 comments on commit be95010

Please sign in to comment.