diff --git a/plugins/permission/policies/toCallPolicy.ts b/plugins/permission/policies/toCallPolicy.ts index 5d106dc4..f22a0224 100644 --- a/plugins/permission/policies/toCallPolicy.ts +++ b/plugins/permission/policies/toCallPolicy.ts @@ -1,4 +1,4 @@ -import type { Abi, Address, Hex } from "viem" +import type { Abi, Address, Hex, Narrow } from "viem" import { concatHex, pad } from "viem" import { CALL_POLICY_CONTRACT_V0_0_1, @@ -13,7 +13,7 @@ import { encodePermissionData, getPermissionFromABI } from "./callPolicyUtils.js" -import { CallType, type Permission } from "./types.js" +import { CallType, type InferPermissions, type Permission } from "./types.js" export enum CallPolicyVersion { V0_0_1 = "0.0.1", @@ -43,31 +43,32 @@ export const getCallPolicyAddress = ( } export type CallPolicyParams< - TAbi extends Abi | readonly unknown[], - TFunctionName extends string | undefined = string + permissions extends readonly Permission< + Abi, + string + >[] = readonly Permission[] > = PolicyParams & { policyVersion: CallPolicyVersion - permissions?: Permission[] + permissions?: InferPermissions> } export function toCallPolicy< - TAbi extends Abi | readonly unknown[], - TFunctionName extends string | undefined = string + const permissions extends readonly Permission[] >({ policyAddress, policyFlag = PolicyFlags.FOR_ALL_VALIDATION, policyVersion, - permissions = [] -}: CallPolicyParams): Policy { + permissions: inputPermissions +}: CallPolicyParams): Policy { const callPolicyAddress = getCallPolicyAddress(policyVersion, policyAddress) - const generatedPermissionParams = permissions?.map((perm) => { + const generatedPermissionParams = inputPermissions?.map((perm) => { // Natural discrimination: if abi and functionName are present, do ABI-based validation if (perm.abi && perm.functionName) { return getPermissionFromABI({ abi: perm.abi as Abi, functionName: perm.functionName as string, - args: perm.args as [], + args: perm.args, policyAddress: callPolicyAddress, selector: perm.selector }) @@ -80,8 +81,8 @@ export function toCallPolicy< } }) - permissions = - permissions?.map((perm, index) => ({ + const processedPermissions = + inputPermissions?.map((perm, index) => ({ ...perm, callType: perm.callType ?? CallType.CALL, selector: @@ -96,7 +97,7 @@ export function toCallPolicy< })) ?? [] const encodedPermissionData = encodePermissionData( - permissions, + processedPermissions, callPolicyAddress ) @@ -112,8 +113,8 @@ export function toCallPolicy< policyVersion, policyAddress, policyFlag, - permissions - } as unknown as CallPolicyParams & { + permissions: processedPermissions + } as unknown as CallPolicyParams & { type: "call" } } diff --git a/plugins/permission/policies/types.ts b/plugins/permission/policies/types.ts index 587b9293..2c3396b9 100644 --- a/plugins/permission/policies/types.ts +++ b/plugins/permission/policies/types.ts @@ -7,8 +7,10 @@ import type { AbiParameterToPrimitiveType, AbiStateMutability, Address, + ContractFunctionName, Hex, - Narrow + Narrow, + Prettify } from "viem" export enum CallType { @@ -156,3 +158,43 @@ export type GeneratePermissionFromArgsParameters< : _FunctionName extends string ? { abi: [Narrow] } & GetFunctionArgs : never) + +// infer permission parameters from `unknown` - similar to GetMulticallContractParameters +export type GetPermissionParameters = permission extends { + abi: infer abi extends Abi +} // 1. Check if `abi` is const-asserted or defined inline + ? // 1a. Check if `functionName` is valid for `abi` + permission extends { + functionName: infer functionName extends ContractFunctionName + } + ? // Use PermissionWithABI which supports CombinedArgs + PermissionWithABI + : // 1b. `functionName` is invalid, check if `abi` is declared as `Abi` + Abi extends abi + ? PermissionWithABI // `abi` declared as `Abi`, unable to infer types further + : // `abi` is const-asserted or defined inline, infer types for `functionName` and `args` + PermissionWithABI + : permission extends { target: Address } // manual permission + ? PermissionManual + : never // invalid permission + +// Process permissions array - similar to MulticallContracts +export type InferPermissions< + permissions extends readonly unknown[], + result extends readonly unknown[] = [] +> = permissions extends readonly [] // no permissions, return empty + ? readonly [] + : permissions extends readonly [infer permission] // one permission left before returning `result` + ? readonly [...result, Prettify>] + : permissions extends readonly [infer permission, ...infer rest] // grab first permission and recurse through `rest` + ? InferPermissions< + [...rest], + [...result, Prettify>] + > + : readonly unknown[] extends permissions + ? permissions + : // If `permissions` is *some* array but we couldn't assign `unknown[]` to it, then it must hold some known/homogenous type! + permissions extends readonly (infer permission)[] + ? readonly Prettify>[] + : // Fallback + readonly Permission[] diff --git a/plugins/permission/types.ts b/plugins/permission/types.ts index 4b6a7771..edc15637 100644 --- a/plugins/permission/types.ts +++ b/plugins/permission/types.ts @@ -5,7 +5,7 @@ import type { GetKernelVersion, PluginValidityData } from "@zerodev/sdk/types" -import type { Abi, Address, Hex, LocalAccount } from "viem" +import type { Address, Hex, LocalAccount } from "viem" import type { EntryPointVersion } from "viem/account-abstraction" import type { SignAuthorizationReturnType } from "viem/accounts" import type { PolicyFlags } from "./constants.js" @@ -42,7 +42,7 @@ export type Policy = { getPolicyInfoInBytes: () => Hex // return params directly to serialize/deserialize Policy policyParams: - | (CallPolicyParams & { + | (CallPolicyParams & { type: "call" }) | (GasPolicyParams & { type: "gas" })