From 35523352be6d068fb2b0b0a5b85fc9916857033c Mon Sep 17 00:00:00 2001 From: moldy Date: Mon, 13 Nov 2023 19:08:28 -0500 Subject: [PATCH] feat: add initial skeleton for 6900 account support --- .vscode/settings.json | 3 + packages/accounts/src/index.ts | 8 + .../src/msca/abis/IStandardExecutor.ts | 74 ++++++ .../src/msca/abis/MultiOwnerMSCAFactory.ts | 237 ++++++++++++++++++ packages/accounts/src/msca/account.ts | 102 ++++++++ .../src/msca/plugins/multi-owner/index.ts | 2 + .../src/msca/plugins/multi-owner/plugin.ts | 16 ++ .../src/msca/plugins/multi-owner/signer.ts | 78 ++++++ yarn.lock | 12 - 9 files changed, 520 insertions(+), 12 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 packages/accounts/src/msca/abis/IStandardExecutor.ts create mode 100644 packages/accounts/src/msca/abis/MultiOwnerMSCAFactory.ts create mode 100644 packages/accounts/src/msca/account.ts create mode 100644 packages/accounts/src/msca/plugins/multi-owner/index.ts create mode 100644 packages/accounts/src/msca/plugins/multi-owner/plugin.ts create mode 100644 packages/accounts/src/msca/plugins/multi-owner/signer.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..25fa6215fd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index 15e958c25e..663b70c574 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -20,3 +20,11 @@ export type { KernelBaseValidatorParams } from "./kernel-zerodev/validator/base. //light-account exports export { LightSmartContractAccount } from "./light-account/account.js"; export { getDefaultLightAccountFactoryAddress } from "./light-account/utils.js"; + +// msca exports +export { ModularSmartContractAccount } from "./msca/account.js"; +export type { ModularSmartContractAccountParams } from "./msca/account.js"; +export { + MultiOwnerPlugin, + MultiOwnerPluginSigner, +} from "./msca/plugins/multi-owner/index.js"; diff --git a/packages/accounts/src/msca/abis/IStandardExecutor.ts b/packages/accounts/src/msca/abis/IStandardExecutor.ts new file mode 100644 index 0000000000..c6c4165e3c --- /dev/null +++ b/packages/accounts/src/msca/abis/IStandardExecutor.ts @@ -0,0 +1,74 @@ +export const IStandardExecutorAbi = [ + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + internalType: "struct Execution", + name: "execution", + type: "tuple", + }, + ], + name: "execute", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + internalType: "struct Execution[]", + name: "executions", + type: "tuple[]", + }, + ], + name: "executeBatch", + outputs: [ + { + internalType: "bytes[]", + name: "", + type: "bytes[]", + }, + ], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/packages/accounts/src/msca/abis/MultiOwnerMSCAFactory.ts b/packages/accounts/src/msca/abis/MultiOwnerMSCAFactory.ts new file mode 100644 index 0000000000..8d0a047839 --- /dev/null +++ b/packages/accounts/src/msca/abis/MultiOwnerMSCAFactory.ts @@ -0,0 +1,237 @@ +export const MultiOwnerMSCAFactoryAbi = [ + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "address", + name: "multiOwnerPlugin", + type: "address", + }, + { + internalType: "address", + name: "implementation", + type: "address", + }, + { + internalType: "bytes32", + name: "manifestHash", + type: "bytes32", + }, + { + internalType: "contract IEntryPoint", + name: "entryPoint", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + inputs: [], + name: "ENTRYPOINT", + outputs: [ + { + internalType: "contract IEntryPoint", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "IMPL", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MULTI_OWNER_PLUGIN", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint32", + name: "unstakeDelay", + type: "uint32", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "addStake", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "salt", + type: "uint256", + }, + { + internalType: "address[]", + name: "owners", + type: "address[]", + }, + ], + name: "createAccount", + outputs: [ + { + internalType: "address", + name: "addr", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "salt", + type: "uint256", + }, + { + internalType: "address[]", + name: "owners", + type: "address[]", + }, + ], + name: "getAddress", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "unlockStake", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address payable", + name: "to", + type: "address", + }, + { + internalType: "address", + name: "token", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "withdraw", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address payable", + name: "to", + type: "address", + }, + ], + name: "withdrawStake", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + stateMutability: "payable", + type: "receive", + }, +] as const; diff --git a/packages/accounts/src/msca/account.ts b/packages/accounts/src/msca/account.ts new file mode 100644 index 0000000000..136fa7cc87 --- /dev/null +++ b/packages/accounts/src/msca/account.ts @@ -0,0 +1,102 @@ +import { + BaseSmartContractAccount, + SignerSchema, + createBaseSmartAccountParamsSchema, + type BatchUserOperationCallData, + type SignTypedDataParams, + type SmartAccountSigner, + type SupportedTransports, +} from "@alchemy/aa-core"; +import { + concatHex, + encodeFunctionData, + type Address, + type FallbackTransport, + type Hex, + type Transport, +} from "viem"; +import { z } from "zod"; +import { IStandardExecutorAbi } from "./abis/IStandardExecutor.js"; +import { MultiOwnerMSCAFactoryAbi } from "./abis/MultiOwnerMSCAFactory.js"; + +export const createModularSmartContractAccountSchema = < + TTransport extends SupportedTransports = Transport +>() => + createBaseSmartAccountParamsSchema().extend({ + owner: SignerSchema, + index: z.bigint().optional().default(0n), + }); + +export type ModularSmartContractAccountParams = z.input< + ReturnType +>; + +export class ModularSmartContractAccount< + TTransport extends Transport | FallbackTransport = Transport +> extends BaseSmartContractAccount { + protected owner: SmartAccountSigner; + protected index: bigint; + + constructor(params_: ModularSmartContractAccountParams) { + const params = + createModularSmartContractAccountSchema().parse(params_); + + super(params); + + this.owner = params.owner; + this.index = params.index; + } + + getDummySignature(): Hex { + throw new Error("Method not implemented."); + } + + async encodeExecute(target: Address, value: bigint, data: Hex): Promise { + return encodeFunctionData({ + abi: IStandardExecutorAbi, + functionName: "execute", + args: [ + { + target, + data, + value, + }, + ], + }); + } + + async encodeBatchExecute(txs: BatchUserOperationCallData): Promise { + return encodeFunctionData({ + abi: IStandardExecutorAbi, + functionName: "executeBatch", + args: [ + txs.map((tx) => ({ + target: tx.target, + data: tx.data, + value: tx.value ?? 0n, + })), + ], + }); + } + + signMessage(msg: string | Uint8Array): Promise { + return this.owner.signMessage(msg); + } + + signTypedData(params: SignTypedDataParams): Promise { + return this.owner.signTypedData(params); + } + + protected async getAccountInitCode(): Promise<`0x${string}`> { + // TODO: this needs to be configured differently later because it assumes everything uses this factory (which is not the case) + return concatHex([ + this.factoryAddress, + encodeFunctionData({ + abi: MultiOwnerMSCAFactoryAbi, + functionName: "createAccount", + // TODO: this needs to support creating accounts with multiple owners + args: [this.index, [await this.owner.getAddress()]], + }), + ]); + } +} diff --git a/packages/accounts/src/msca/plugins/multi-owner/index.ts b/packages/accounts/src/msca/plugins/multi-owner/index.ts new file mode 100644 index 0000000000..bd58282ef7 --- /dev/null +++ b/packages/accounts/src/msca/plugins/multi-owner/index.ts @@ -0,0 +1,2 @@ +export { MultiOwnerPlugin } from "./plugin.js"; +export { MultiOwnerPluginSigner } from "./signer.js"; diff --git a/packages/accounts/src/msca/plugins/multi-owner/plugin.ts b/packages/accounts/src/msca/plugins/multi-owner/plugin.ts new file mode 100644 index 0000000000..d5fb3db150 --- /dev/null +++ b/packages/accounts/src/msca/plugins/multi-owner/plugin.ts @@ -0,0 +1,16 @@ +import type { TypedDataDomain } from "viem"; + +export class MultiOwnerPlugin { + pluginAddress: string; + + constructor(pluginAddress: string) { + this.pluginAddress = pluginAddress; + } + + // TODO: need to call the plugin contract's view method for the domain separator + async getDomainSeparator(): Promise { + return {}; + } + + // TODO: Support public methods listed in plugin contract definition (ask moldy for specifics) +} diff --git a/packages/accounts/src/msca/plugins/multi-owner/signer.ts b/packages/accounts/src/msca/plugins/multi-owner/signer.ts new file mode 100644 index 0000000000..5de843f3e8 --- /dev/null +++ b/packages/accounts/src/msca/plugins/multi-owner/signer.ts @@ -0,0 +1,78 @@ +import { + SignerSchema, + type SignTypedDataParams, + type SmartAccountSigner, +} from "@alchemy/aa-core"; +import { Address as zAddress } from "abitype/zod"; +import { toBytes } from "viem"; +import { z } from "zod"; +import { MultiOwnerPlugin } from "./plugin.js"; + +export const zMultiOwnerPluginParams = z.object({ + pluginAddress: zAddress, + signer: SignerSchema, +}); + +export type MultiOwnerPluginParams = z.infer; + +export class MultiOwnerPluginSigner + implements SmartAccountSigner +{ + signerType: string; + inner: SmartAccountSigner; + plugin: MultiOwnerPlugin; + + constructor(params_: MultiOwnerPluginParams) { + const { signer, pluginAddress } = zMultiOwnerPluginParams.parse(params_); + this.signerType = `multi-owner:${signer.signerType}`; + this.inner = signer; + this.plugin = new MultiOwnerPlugin(pluginAddress); + } + + // This returns the address of the current owner, not all owners + getAddress: () => Promise<`0x${string}`> = () => { + return this.inner.getAddress(); + }; + + /** + * This format will only work for validating user op signatures + * 1271 validation is handled differently, so signatures for 1271 validation + * should use the method `signMessageFor1271` + * + * @param msg the UO hash + * @returns a Promise containing the signature in Hex format + */ + signMessage: (msg: string | Uint8Array) => Promise<`0x${string}`> = async ( + msg + ) => { + return this.inner.signMessage(msg); + }; + + /** + * This format is required for all 1271 validation + * + * @param msg the message being signed for personal_sign + * @returns a Promise containing the signature in Hex format + */ + signMessageFor1271: (msg: string | Uint8Array) => Promise<`0x${string}`> = + async (msg) => { + return this.signTypedData({ + domain: await this.plugin.getDomainSeparator(), + types: { + ERC6900Message: [{ name: "message", type: "bytes" }], + }, + message: { + ERC6900Message: { + message: typeof msg === "string" ? toBytes(msg) : msg, + }, + }, + primaryType: "ERC6900Message", + }); + }; + + signTypedData: (params: SignTypedDataParams) => Promise<`0x${string}`> = ( + params + ) => { + return this.inner.signTypedData(params); + }; +} diff --git a/yarn.lock b/yarn.lock index c765ba9f96..2bb3cc7dce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11870,11 +11870,6 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -sitka@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/sitka/-/sitka-1.1.1.tgz#3f93ccc84160e9c7a233d7afb3ec00630f92423c" - integrity sha512-GjvAdRLNH1eJZoTGtB2dHg1CGqZ3qoeBNbF5oWZL7tlWxMZ1WGnsPpJDmrcc302NA46qRUQZMXk0hiXW6HV+qA== - slash@3.0.0, slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -12676,13 +12671,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript-template@*: - version "1.0.7" - resolved "https://registry.yarnpkg.com/typescript-template/-/typescript-template-1.0.7.tgz#04d68ca33483bcbd89d5ed7ea2a3e3e1d2f6fb6c" - integrity sha512-h/RPRYJdzXoAKo6YDvh6v9Vr3JVcOb+DoXI4owCQttcHKEqevz67mWyIn2zXe7uDlo8VJEtzuk/QfRGB+Q6b8A== - dependencies: - sitka "^1.1.1" - "typescript@^3 || ^4": version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"