From 1cb8fb7a0f0d2e2e8227cc6da5ded455433de83e Mon Sep 17 00:00:00 2001 From: moldy Date: Mon, 19 Jun 2023 20:01:07 -0700 Subject: [PATCH] fix: support contract deployment by a smart contract account --- .../core/src/__tests__/simple-account.test.ts | 10 +++++ packages/core/src/account/simple.ts | 3 +- packages/core/src/provider/base.ts | 39 +++++++++++++------ packages/core/src/provider/types.ts | 17 +++++++- packages/core/src/types.ts | 2 +- .../src/__tests__/simple-account.test.ts | 17 ++++++++ 6 files changed, 72 insertions(+), 16 deletions(-) diff --git a/packages/core/src/__tests__/simple-account.test.ts b/packages/core/src/__tests__/simple-account.test.ts index e9289a69ff..fa65e88089 100644 --- a/packages/core/src/__tests__/simple-account.test.ts +++ b/packages/core/src/__tests__/simple-account.test.ts @@ -108,4 +108,14 @@ describe("Simple Account Tests", () => { '"0x18dfb3c7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000deadbeefdeadbeefdeadbeefdeadbeefdeadbeef0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004deadbeef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004cafebabe00000000000000000000000000000000000000000000000000000000"' ); }); + + it("should successfully deploy a contract", async () => { + const result = signer.deployContract({ + abi: [], + bytecode: + "0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220e5284ae00a989ec593ad1aebb69c9082d4820ccd98f6b493e4c442fa44a950c364736f6c63430008110033", + }); + + await expect(result).resolves.not.toThrowError(); + }); }); diff --git a/packages/core/src/account/simple.ts b/packages/core/src/account/simple.ts index 26a4f61bff..69203c7d23 100644 --- a/packages/core/src/account/simple.ts +++ b/packages/core/src/account/simple.ts @@ -3,6 +3,7 @@ import { concatHex, encodeFunctionData, hexToBytes, + zeroAddress, type FallbackTransport, type Hex, type Transport, @@ -64,7 +65,7 @@ export class SimpleSmartContractAccount< ): Promise<`0x${string}`> { const [targets, datas] = _txs.reduce( (accum, curr) => { - accum[0].push(curr.target); + accum[0].push(curr.target ?? zeroAddress); accum[1].push(curr.data); return accum; diff --git a/packages/core/src/provider/base.ts b/packages/core/src/provider/base.ts index 926b7b4cc8..ff1c8607ea 100644 --- a/packages/core/src/provider/base.ts +++ b/packages/core/src/provider/base.ts @@ -1,7 +1,10 @@ import { + encodeDeployData, fromHex, + zeroAddress, type Address, type Chain, + type EncodeDeployDataParameters, type Hash, type RpcTransactionRequest, type Transport, @@ -141,10 +144,6 @@ export class SmartAccountProvider< }; sendTransaction = async (request: RpcTransactionRequest): Promise => { - if (!request.to) { - throw new Error("transaction is missing to address"); - } - // TODO: need to add support for overriding gas prices const { hash } = await this.sendUserOperation({ target: request.to, @@ -157,12 +156,6 @@ export class SmartAccountProvider< sendTransactions = async (requests: RpcTransactionRequest[]) => { const batch = requests.map((request) => { - if (!request.to) { - throw new Error( - "one transaction in the batch is missing a target address" - ); - } - // TODO: need to add support for overriding gas prices return { target: request.to, @@ -222,8 +215,18 @@ export class SmartAccountProvider< sender: this.getAddress(), nonce: this.account.getNonce(), callData: Array.isArray(data) - ? this.account.encodeBatchExecute(data) - : this.account.encodeExecute(data.target, data.value ?? 0n, data.data), + ? this.account.encodeBatchExecute( + data.map((x) => ({ + ...x, + target: x.target ?? zeroAddress, + value: x.value ?? 0n, + })) + ) + : this.account.encodeExecute( + data.target ?? zeroAddress, + data.value ?? 0n, + data.data + ), signature: this.account.getDummySignature(), } as UserOperationStruct); @@ -256,6 +259,18 @@ export class SmartAccountProvider< }; }; + deployContract = async ( + params: EncodeDeployDataParameters + ): Promise => { + const deployData = encodeDeployData(params); + const { hash } = await this.sendUserOperation({ + target: zeroAddress, + data: deployData, + }); + + return hash; + }; + // These are dependent on the specific paymaster being used // You should implement your own middleware to override these // or extend this class and provider your own implemenation diff --git a/packages/core/src/provider/types.ts b/packages/core/src/provider/types.ts index 90fbb83051..e52e8cffc4 100644 --- a/packages/core/src/provider/types.ts +++ b/packages/core/src/provider/types.ts @@ -1,5 +1,10 @@ import type { Address } from "abitype"; -import type { Hash, RpcTransactionRequest, Transport } from "viem"; +import type { + EncodeDeployDataParameters, + Hash, + RpcTransactionRequest, + Transport, +} from "viem"; import type { BaseSmartContractAccount } from "../account/base.js"; import type { PublicErc4337Client, @@ -18,7 +23,7 @@ type WithRequired = Required>; type WithOptional = Pick, K>; export type SendUserOperationResult = { - hash: string; + hash: Hash; request: UserOperationRequest; }; @@ -128,6 +133,14 @@ export interface ISmartAccountProvider< */ sendTransactions: (request: RpcTransactionRequest[]) => Promise; + /** + * This takes contract deploy parameters and crafts a UserOperation to deploy the contract. + * + * @param params the parameters for the contract deploy call + * @returns the hash of the user operation + */ + deployContract: (params: EncodeDeployDataParameters) => Promise; + /** * EIP-1193 compliant request method * diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 2c0b0b379b..3b84999f83 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -10,7 +10,7 @@ export type BytesLike = Uint8Array | string; export interface UserOperationCallData { /* the target of the call */ - target: Address; + target?: Address; /* the data passed to the target */ data: Hex; /* the amount of native token to send to the target (default: 0) */ diff --git a/packages/ethers/src/__tests__/simple-account.test.ts b/packages/ethers/src/__tests__/simple-account.test.ts index 6113c1d16f..df7f1d38da 100644 --- a/packages/ethers/src/__tests__/simple-account.test.ts +++ b/packages/ethers/src/__tests__/simple-account.test.ts @@ -1,4 +1,6 @@ import { getChain, SimpleSmartContractAccount } from "@alchemy/aa-core"; +import { Interface } from "@ethersproject/abi"; +import { ContractFactory } from "@ethersproject/contracts"; import { Wallet } from "@ethersproject/wallet"; import { Alchemy, Network } from "alchemy-sdk"; import { EthersProviderAdapter } from "../provider-adapter.js"; @@ -80,4 +82,19 @@ describe("Simple Account Tests", async () => { await expect(result).rejects.toThrowError(); }); + + it("should successfully deploy a contract", async () => { + const factory = new ContractFactory( + new Interface([]), + { + object: + "0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220e5284ae00a989ec593ad1aebb69c9082d4820ccd98f6b493e4c442fa44a950c364736f6c63430008110033", + }, + signer + ); + + const result = factory.deploy(); + + await expect(result).resolves.not.toThrowError(); + }); });