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..8f91c8407b 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 @@ -17,6 +17,7 @@ import { type Transport, encodeFunctionData, concatHex, + zeroAddress, } from "viem"; import { semiModularAccountBytecodeAbi } from "../../abis/semiModularAccountBytecodeAbi.js"; @@ -95,7 +96,9 @@ export const installValidationActions: < } if (validationConfig.entityId === 0) { - throw new EntityIdOverrideError(); + if (validationConfig.moduleAddress !== zeroAddress) { + throw new EntityIdOverrideError(); + } } const callData = encodeFunctionData({ 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 60329eb8a2..c1284f7086 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,4 +1,4 @@ -import { custom, parseEther, publicActions } from "viem"; +import { custom, parseEther, publicActions, zeroAddress } from "viem"; import { LocalAccountSigner, type SmartAccountSigner } from "@aa-sdk/core"; @@ -10,6 +10,8 @@ import { accounts } from "~test/constants.js"; import { installValidationActions } from "../actions/install-validation/installValidation.js"; import { addresses } from "../utils.js"; import { SingleSignerValidationModule } from "../modules/single-signer-validation/module.js"; +import { allowlistModule } from "../modules/allowlist-module/module.js"; +import { HookType } from "../actions/common/types.js"; describe("MA v2 Tests", async () => { const instance = local070Instance; @@ -30,8 +32,8 @@ describe("MA v2 Tests", async () => { const target = "0x000000000000000000000000000000000000dEaD"; const sendAmount = parseEther("1"); - const getTargetBalance = async (): Promise => { - return client.getBalance({ + const getTargetBalance = async (): Promise => { + return (client as any).getBalance({ address: target, }); }; @@ -169,15 +171,91 @@ describe("MA v2 Tests", async () => { isGlobalValidation: true, }); - result = sessionKeyClient.sendUserOperation({ - uo: { - target: target, - value: sendAmount, - data: "0x", + await expect( + sessionKeyClient.sendUserOperation({ + uo: { + target: target, + value: sendAmount, + data: "0x", + }, + }) + ).rejects.toThrowError(); + }); + + it("installs allowlist module, then uninstalls", async () => { + let provider = (await givenConnectedProvider({ signer })).extend( + installValidationActions + ); + + await setBalance(client, { + address: provider.getAddress(), + value: parseEther("2"), + }); + + const hookInstallData = allowlistModule.encodeOnInstallData({ + entityId: 1, + inputs: [ + { + target, + hasSelectorAllowlist: false, + hasERC20SpendLimit: false, + erc20SpendLimit: 0n, + selectors: [], + }, + ], + }); + + const installResult = await provider.installValidation({ + validationConfig: { + moduleAddress: zeroAddress, + entityId: 0, + isGlobal: true, + isSignatureValidation: true, + isUserOpValidation: true, }, + selectors: [], + installData: "0x", + hooks: [ + { + hookConfig: { + address: addresses.allowlistModule, + entityId: 0, // uint32 + hookType: HookType.VALIDATION, + hasPreHooks: true, + hasPostHooks: true, + }, + initData: hookInstallData, + }, + ], + }); + + await expect( + provider.waitForUserOperationTransaction(installResult) + ).resolves.not.toThrowError(); + + const hookUninstallData = allowlistModule.encodeOnUninstallData({ + entityId: 0, + inputs: [ + { + target, + hasSelectorAllowlist: false, + hasERC20SpendLimit: false, + erc20SpendLimit: 0n, + selectors: [], + }, + ], + }); + + const uninstallResult = await provider.uninstallValidation({ + moduleAddress: zeroAddress, + entityId: 0, + uninstallData: "0x", + hookUninstallDatas: [hookUninstallData], }); - await expect(result).rejects.toThrowError(); + await expect( + provider.waitForUserOperationTransaction(uninstallResult) + ).resolves.not.toThrowError(); }); const givenConnectedProvider = async ({ diff --git a/account-kit/smart-contracts/src/ma-v2/modules/allowlist-module/module.ts b/account-kit/smart-contracts/src/ma-v2/modules/allowlist-module/module.ts new file mode 100644 index 0000000000..1a9a5d9867 --- /dev/null +++ b/account-kit/smart-contracts/src/ma-v2/modules/allowlist-module/module.ts @@ -0,0 +1,99 @@ +import { encodeAbiParameters, type Address, type Hex } from "viem"; + +import { allowlistModuleAbi } from "./abis/allowlistModuleAbi.js"; + +const addresses: Record = { + default: "0xE46ca4a98c485caEE2Abb6ef5116292B8c78a868", +}; + +const meta = { + name: "AllowlistModule", + version: "alpha.1", + addresses, +}; + +export const allowlistModule = { + meta, + abi: allowlistModuleAbi, + encodeOnInstallData: (args: { + entityId: number; + inputs: Array<{ + target: Address; + hasSelectorAllowlist: boolean; + hasERC20SpendLimit: boolean; + erc20SpendLimit: bigint; + selectors: Array; + }>; + }): Hex => { + const { entityId, inputs } = args; + return encodeAbiParameters( + [ + { type: "uint32" }, + { + type: "tuple[]", + components: [ + { type: "address" }, + { type: "bool" }, + { type: "bool" }, + { type: "uint256" }, + { type: "bytes4[]" }, + ], + }, + ], + [ + entityId, + inputs.map( + (input) => + [ + input.target, + input.hasSelectorAllowlist, + input.hasERC20SpendLimit, + input.erc20SpendLimit, + input.selectors, + ] as const + ), + ] + ); + }, + + encodeOnUninstallData: (args: { + entityId: number; + inputs: Array<{ + target: Address; + hasSelectorAllowlist: boolean; + hasERC20SpendLimit: boolean; + erc20SpendLimit: bigint; + selectors: Array; + }>; + }): Hex => { + const { entityId, inputs } = args; + return encodeAbiParameters( + [ + { type: "uint32" }, + { + type: "tuple[]", + components: [ + { type: "address" }, + { type: "bool" }, + { type: "bool" }, + { type: "uint256" }, + { type: "bytes4[]" }, + ], + }, + ], + [ + entityId, + inputs.map( + (input) => + [ + input.target, + input.hasSelectorAllowlist, + input.hasERC20SpendLimit, + input.erc20SpendLimit, + input.selectors, + ] as const + ), + ] + ); + }, +}; diff --git a/account-kit/smart-contracts/src/ma-v2/modules/single-signer-validation/module.ts b/account-kit/smart-contracts/src/ma-v2/modules/single-signer-validation/module.ts index c865569eaf..8dc6ed7e5e 100644 --- a/account-kit/smart-contracts/src/ma-v2/modules/single-signer-validation/module.ts +++ b/account-kit/smart-contracts/src/ma-v2/modules/single-signer-validation/module.ts @@ -2,9 +2,9 @@ import { encodeAbiParameters, type Address, type Hex } from "viem"; import { singleSignerValidationModuleAbi } from "./abis/singleSignerValidationModuleAbi.js"; -const addresses = { +const addresses: Record = { default: "0xEa3a0b544d517f6Ed3Dc2186C74D869c702C376e", -} as Record; +}; const meta = { name: "SingleSignerValidation",