Skip to content

Commit

Permalink
refactor(aa-core): complete migration to viem based approach in aa-core
Browse files Browse the repository at this point in the history
BREAKING CHANGE: all interfaces have been migrated to use the new viem style clients and accounts
  • Loading branch information
moldy530 committed Jan 30, 2024
1 parent ed3cf08 commit 9bff625
Show file tree
Hide file tree
Showing 48 changed files with 844 additions and 2,077 deletions.
5 changes: 0 additions & 5 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@
"import": "./dist/esm/index.js",
"default": "./dist/cjs/index.js"
},
"./viem": {
"types": "./dist/types/viem/index.d.ts",
"import": "./dist/esm/viem/index.js",
"default": "./dist/cjs/viem/index.js"
},
"./package.json": "./package.json"
},
"typesVersions": {
Expand Down
107 changes: 57 additions & 50 deletions packages/core/src/account/__tests__/simple.test.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,50 @@
import type { Address } from "viem";
import { createPublicClient, custom, http, type Address } from "viem";
import { polygonMumbai, sepolia, type Chain } from "viem/chains";
import { describe, it } from "vitest";
import { SmartAccountProvider } from "../../provider/base.js";
import { createPublicErc4337FromClient } from "../../client/publicErc4337Client.js";
import { createSmartAccountClient } from "../../client/smartAccountClient.js";
import { LocalAccountSigner } from "../../signer/local-account.js";
import { type SmartAccountSigner } from "../../signer/types.js";
import type { BatchUserOperationCallData } from "../../types.js";
import { getDefaultSimpleAccountFactoryAddress } from "../../utils/index.js";
import { SimpleSmartContractAccount } from "../simple.js";
import { createSimpleSmartAccount } from "../simple.js";

describe("Account Simple Tests", () => {
describe("Account Simple Tests", async () => {
const dummyMnemonic =
"test test test test test test test test test test test test";
const owner: SmartAccountSigner =
LocalAccountSigner.mnemonicToAccountSigner(dummyMnemonic);

const chain = polygonMumbai;
const publicClient = createPublicErc4337FromClient(
createPublicClient({
chain,
transport: custom({
request: async ({ method }) => {
if (method === "eth_getCode") {
return "0x" as Address;
}
return;
},
}),
})
);

it("should correctly sign the message", async () => {
const provider = givenConnectedProvider({ owner, chain });
const provider = await givenConnectedProvider({ owner, chain });
expect(
await provider.signMessage(
"0xa70d0af2ebb03a44dcd0714a8724f622e3ab876d0aa312f0ee04823285d6fb1b"
)
await provider.account.signMessage({
message: {
raw: "0xa70d0af2ebb03a44dcd0714a8724f622e3ab876d0aa312f0ee04823285d6fb1b",
},
})
).toBe(
"0x33b1b0d34ba3252cd8abac8147dc08a6e14a6319462456a34468dd5713e38dda3a43988460011af94b30fa3efefcf9d0da7d7522e06b7bd8bff3b65be4aee5b31c"
);
});

it("should correctly encode batch transaction data", async () => {
const provider = givenConnectedProvider({ owner, chain });
const provider = await givenConnectedProvider({ owner, chain });
const data = [
{
target: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
Expand All @@ -47,17 +63,16 @@ describe("Account Simple Tests", () => {
);
});

it("should correctly do base runtime validation when entrypoint are invalid", () => {
expect(
() =>
new SimpleSmartContractAccount({
entryPointAddress: 1 as unknown as Address,
chain,
owner,
factoryAddress: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
rpcClient: "ALCHEMY_RPC_URL",
})
).toThrowErrorMatchingInlineSnapshot(`
it("should correctly do base runtime validation when entrypoint are invalid", async () => {
await expect(
createSimpleSmartAccount({
entryPointAddress: 1 as unknown as Address,
chain,
owner,
factoryAddress: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
rpcClient: "ALCHEMY_RPC_URL",
})
).rejects.toThrowErrorMatchingInlineSnapshot(`
"[
{
\\"code\\": \\"invalid_type\\",
Expand All @@ -72,17 +87,16 @@ describe("Account Simple Tests", () => {
`);
});

it("should correctly do base runtime validation when multiple inputs are invalid", () => {
expect(
() =>
new SimpleSmartContractAccount({
entryPointAddress: 1 as unknown as Address,
chain: "0x1" as unknown as Chain,
owner,
factoryAddress: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
rpcClient: "ALCHEMY_RPC_URL",
})
).toThrowErrorMatchingInlineSnapshot(`
it("should correctly do base runtime validation when multiple inputs are invalid", async () => {
await expect(
createSimpleSmartAccount({
entryPointAddress: 1 as unknown as Address,
chain: "0x1" as unknown as Chain,
owner,
factoryAddress: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
rpcClient: "ALCHEMY_RPC_URL",
})
).rejects.toThrowErrorMatchingInlineSnapshot(`
"[
{
\\"code\\": \\"invalid_type\\",
Expand All @@ -106,47 +120,40 @@ describe("Account Simple Tests", () => {
});

it("should correctly use the account init code override", async () => {
const account = new SimpleSmartContractAccount({
const account = await createSimpleSmartAccount({
chain: sepolia,
owner: owner,
factoryAddress: getDefaultSimpleAccountFactoryAddress(sepolia),
rpcClient: `${sepolia.rpcUrls.alchemy.http[0]}/${"test"}`,
rpcClient: publicClient,
// override the account address here so we don't have to resolve the address from the entrypoint
accountAddress: "0x1234567890123456789012345678901234567890",
initCode: "0xdeadbeef",
});

// @ts-expect-error this object is protected
vi.spyOn(account.rpcProvider, "getBytecode").mockImplementation(() => {
return Promise.resolve("0x");
vi.spyOn(publicClient, "getBytecode").mockImplementation(() => {
return Promise.resolve("0x" as Address);
});

const initCode = await account.getInitCode();
expect(initCode).toMatchInlineSnapshot('"0xdeadbeef"');
});

const givenConnectedProvider = ({
const givenConnectedProvider = async ({
owner,
chain,
}: {
owner: SmartAccountSigner;
chain: Chain;
}) =>
new SmartAccountProvider({
rpcProvider: `${chain.rpcUrls.alchemy.http[0]}/${"test"}`,
chain,
}).connect((provider) => {
const account = new SimpleSmartContractAccount({
createSmartAccountClient({
transport: http(`${chain.rpcUrls.alchemy.http[0]}/${"test"}`),
chain: chain,
account: await createSimpleSmartAccount({
chain,
owner,
accountAddress: "0x1234567890123456789012345678901234567890",
factoryAddress: getDefaultSimpleAccountFactoryAddress(chain),
rpcClient: provider,
});

account.getAddress = vi.fn(
async () => "0xb856DBD4fA1A79a46D426f537455e7d3E79ab7c4"
);

return account;
rpcClient: publicClient,
}),
});
});
14 changes: 8 additions & 6 deletions packages/core/src/account/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import {
type Transport,
} from "viem";
import { EntryPointAbi } from "../abis/EntryPointAbi.js";
import { createPublicErc4337Client } from "../client/create-client.js";
import type {
PublicErc4337Client,
SupportedTransports,
} from "../client/types.js";
import {
createPublicErc4337Client,
type PublicErc4337Client,
} from "../client/publicErc4337Client.js";
import { Logger } from "../logger.js";
import type { SmartAccountSigner } from "../signer/types.js";
import { wrapSignatureWith6492 } from "../signer/utils.js";
Expand All @@ -34,8 +33,11 @@ export enum DeploymentState {
DEPLOYED = "0x2",
}

/**
* @deprecated use `toSmartContractAccount` instead for creating SmartAccountInstances
*/
export abstract class BaseSmartContractAccount<
TTransport extends SupportedTransports = Transport,
TTransport extends Transport = Transport,
TOwner extends SmartAccountSigner | undefined = SmartAccountSigner | undefined
> implements ISmartContractAccount<TTransport, TOwner>
{
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/account/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import { Address } from "abitype/zod";
import { isHex, type Transport } from "viem";
import z from "zod";
import { createPublicErc4337ClientSchema } from "../client/schema.js";
import type { SupportedTransports } from "../client/types";
import { isSigner } from "../signer/schema.js";
import type { SmartAccountSigner } from "../signer/types.js";
import { ChainSchema } from "../utils/index.js";

export const createBaseSmartAccountParamsSchema = <
TTransport extends SupportedTransports = Transport,
TTransport extends Transport = Transport,
TOwner extends SmartAccountSigner | undefined = SmartAccountSigner | undefined
>() =>
z.object({
Expand All @@ -33,7 +32,7 @@ export const createBaseSmartAccountParamsSchema = <
});

export const SimpleSmartAccountParamsSchema = <
TTransport extends SupportedTransports = Transport,
TTransport extends Transport = Transport,
TOwner extends SmartAccountSigner = SmartAccountSigner
>() =>
createBaseSmartAccountParamsSchema<TTransport, TOwner>().extend({
Expand Down
54 changes: 52 additions & 2 deletions packages/core/src/account/simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ import {
} from "viem";
import { SimpleAccountAbi } from "../abis/SimpleAccountAbi.js";
import { SimpleAccountFactoryAbi } from "../abis/SimpleAccountFactoryAbi.js";
import type { PublicErc4337Client } from "../client/publicErc4337Client.js";
import type { SmartAccountSigner } from "../signer/types.js";
import type { BatchUserOperationCallData } from "../types.js";
import { BaseSmartContractAccount } from "./base.js";
import { SimpleSmartAccountParamsSchema } from "./schema.js";
import {
toSmartContractAccount,
type OwnedSmartContractAccount,
} from "./smartContractAccount.js";
import type { SimpleSmartAccountParams } from "./types.js";

export class SimpleSmartContractAccount<
class SimpleSmartContractAccount<
TTransport extends Transport | FallbackTransport = Transport
> extends BaseSmartContractAccount<TTransport, SmartAccountSigner> {
protected index: bigint;
Expand Down Expand Up @@ -74,7 +79,7 @@ export class SimpleSmartContractAccount<
return this.owner.signMessage(msg);
}

protected async getAccountInitCode(): Promise<`0x${string}`> {
public async getAccountInitCode(): Promise<`0x${string}`> {
return concatHex([
this.factoryAddress,
encodeFunctionData({
Expand All @@ -85,3 +90,48 @@ export class SimpleSmartContractAccount<
]);
}
}

export type SimpleSmartAccount<TOwner extends SmartAccountSigner> =
OwnedSmartContractAccount<"SimpleAccount", TOwner>;

export const createSimpleSmartAccount = async <
TTransport extends Transport = Transport,
TOwner extends SmartAccountSigner = SmartAccountSigner
>(
params: SimpleSmartAccountParams<TTransport, TOwner>
): Promise<SimpleSmartAccount<TOwner>> => {
if (!params.owner) throw new Error("Owner must be provided.");

// @ts-expect-error base account allows for optional owners, but simple account requires it
const simpleAccount = new SimpleSmartContractAccount<TTransport>(params);
const parsedParams = SimpleSmartAccountParamsSchema<
TTransport,
TOwner
>().parse(params);

const base = await toSmartContractAccount({
source: "SimpleAccount",
client: simpleAccount.rpcProvider as PublicErc4337Client<TTransport>,
encodeBatchExecute: simpleAccount.encodeBatchExecute.bind(simpleAccount),
encodeExecute: (tx) =>
simpleAccount.encodeExecute(tx.target, tx.value ?? 0n, tx.data),
entrypointAddress: simpleAccount.getEntryPointAddress(),
getAccountInitCode: async () => {
if (parsedParams.initCode) return parsedParams.initCode;
return simpleAccount.getAccountInitCode();
},
getDummySignature: simpleAccount.getDummySignature.bind(simpleAccount),
signMessage: ({ message }) =>
simpleAccount.signMessage(
typeof message === "string" ? message : message.raw
),
// @ts-expect-error these types still represent the same thing, but they're just a little off in there definitions
signTypedData: (typedData) => simpleAccount.signTypedData(typedData),
accountAddress: parsedParams.accountAddress,
});

return {
...base,
owner: parsedParams.owner as TOwner,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import {
} from "viem";
import { toAccount } from "viem/accounts";
import { EntryPointAbi } from "../abis/EntryPointAbi.js";
import { DeploymentState } from "../account/base.js";
import type { PublicErc4337Client } from "../client/types.js";
import type { PublicErc4337Client } from "../client/publicErc4337Client.js";
import type { SmartAccountSigner } from "../signer/types.js";
import { wrapSignatureWith6492 } from "../signer/utils.js";
import type { IsUndefined } from "../utils/types.js";
import { DeploymentState } from "./base.js";

type Tx = {
target: Address;
Expand Down
21 changes: 7 additions & 14 deletions packages/core/src/account/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import type { Address } from "abitype";
import type { Hash, Hex, HttpTransport, Transport } from "viem";
import type { SignTypedDataParameters } from "viem/accounts";
import type { z } from "zod";
import type { PublicErc4337Client, SupportedTransports } from "../client/types";
import type { ISmartAccountProvider } from "../provider/types";
import type { PublicErc4337Client } from "../client/publicErc4337Client";
import type { SmartAccountSigner } from "../signer/types";
import type { BatchUserOperationCallData } from "../types";
import type {
Expand All @@ -14,21 +13,24 @@ import type {
export type SignTypedDataParams = Omit<SignTypedDataParameters, "privateKey">;

export type BaseSmartAccountParams<
TTransport extends SupportedTransports = Transport,
TTransport extends Transport = Transport,
TOwner extends SmartAccountSigner | undefined = SmartAccountSigner | undefined
> = z.input<
ReturnType<typeof createBaseSmartAccountParamsSchema<TTransport, TOwner>>
>;

export type SimpleSmartAccountParams<
TTransport extends SupportedTransports = Transport,
TTransport extends Transport = Transport,
TOwner extends SmartAccountSigner = SmartAccountSigner
> = z.input<
ReturnType<typeof SimpleSmartAccountParamsSchema<TTransport, TOwner>>
>;

/**
* @deprecated use `toSmartContractAccount` instead for creating instances of smart accounts
*/
export interface ISmartContractAccount<
TTransport extends SupportedTransports = Transport,
TTransport extends Transport = Transport,
TOwner extends SmartAccountSigner | undefined = SmartAccountSigner | undefined
> {
/**
Expand All @@ -38,15 +40,6 @@ export interface ISmartContractAccount<
| PublicErc4337Client<TTransport>
| PublicErc4337Client<HttpTransport>;

/**
* Optional property that will be used to augment the provider on connect with methods (leveraging the provider's extend method)
*
* @param provider - the provider being connected
* @returns an object with methods that will be added to the provider
*/
providerDecorators?: <P extends ISmartAccountProvider<TTransport>>(
provider: P
) => unknown;
/**
* @returns the init code for the account
*/
Expand Down
Loading

0 comments on commit 9bff625

Please sign in to comment.