Skip to content

Commit

Permalink
feat: add install/uninstall validation (#1221)
Browse files Browse the repository at this point in the history
* feat: add install validation, add tests for session key

* fix: lint

* fix: review fixes

* fix: tests

* feat: add view functions and user op calldata encodings (#1223)
  • Loading branch information
howydev authored Dec 20, 2024
1 parent 338577d commit 4677003
Show file tree
Hide file tree
Showing 10 changed files with 533 additions and 20 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 @@ -87,3 +87,19 @@ export class InvalidNonceKeyError extends BaseError {
super(`Nonce key is ${nonceKey} but has to be less than 2**152`);
}
}

/**
* 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
126 changes: 121 additions & 5 deletions account-kit/smart-contracts/src/ma-v2/account/semiModularAccountV2.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,78 @@
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<
Expand Down Expand Up @@ -110,14 +146,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,10 +209,61 @@ 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,
signerEntity,
getExecutionData,
getValidationData,
encodeCallData,
};
}
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 })]);
}
Loading

0 comments on commit 4677003

Please sign in to comment.