From 76be2c0ce623e5a16bc333ed7b8f2dcb2f3b9a43 Mon Sep 17 00:00:00 2001 From: Vasile Gabriel Marian <56271768+VGabriel45@users.noreply.github.com> Date: Mon, 13 May 2024 16:03:34 +0300 Subject: [PATCH] feat: transfer_ownership_v2 (#488) * feat/transfer_ownership(in progress) * cleanup * fix linting * added test for transferOwnership with session key manager module * Fix linting * refactor: refactor based on PR review * improve ts doc + refactor tests * added "moduleAddress" param to transferOwnership() * fix module tests * removed console.logs * fixed lint + removed unused import * remove unused import * added argument type for module address --------- Co-authored-by: GabiDev --- src/account/BiconomySmartAccountV2.ts | 50 ++++++++++++----- src/account/utils/Types.ts | 4 ++ tests/account/write.test.ts | 79 ++++++++++++++++----------- tests/modules/write.test.ts | 17 ++++-- 4 files changed, 96 insertions(+), 54 deletions(-) diff --git a/src/account/BiconomySmartAccountV2.ts b/src/account/BiconomySmartAccountV2.ts index 75984044d..048a05e68 100644 --- a/src/account/BiconomySmartAccountV2.ts +++ b/src/account/BiconomySmartAccountV2.ts @@ -28,7 +28,6 @@ import { } from "../bundler/index.js" import { BaseValidationModule, - ECDSA_OWNERSHIP_MODULE_ADDRESSES_BY_VERSION, type ModuleInfo, type SendUserOpParams, createECDSAOwnershipValidationModule @@ -83,6 +82,7 @@ import type { SimulationType, SupportedToken, Transaction, + TransferOwnershipCompatibleModule, WithdrawalRequest } from "./utils/Types.js" import { @@ -1372,26 +1372,46 @@ export class BiconomySmartAccountV2 extends BaseSmartContractAccount { /** * Transfers ownership of the smart account to a new owner. * @param newOwner The address of the new owner. + * @param moduleAddress {@link TransferOwnershipCompatibleModule} The address of the validation module (ECDSA Ownership Module or Multichain Validation Module). * @param buildUseropDto {@link BuildUserOpOptions}. Optional parameter - * @returns A Promise that resolves to a TransferOwnershipResponse or rejects with an Error. + * @returns A Promise that resolves to a UserOpResponse or rejects with an Error. + * @description This function will transfer ownership of the smart account to a new owner. If you use session key manager module, after transferring the ownership + * you will need to re-create a session for the smart account with the new owner (signer) and specify "accountAddress" in "createSmartAccountClient" function. * @example * ```typescript - * const walletClient = createWalletClient({ - * account, - * chain: baseSepolia, - * transport: http() - * }); - * const smartAccount = await createSmartAccountClient({ - * signer: walletClient, - * paymasterUrl: "https://paymaster.biconomy.io/api/v1/...", - * bundlerUrl: `https://bundler.biconomy.io/api/v2/84532/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44`, - * chainId: 84532 - * }); - * const response = await smartAccount.transferOwnership(newOwner, {paymasterServiceData: {mode: PaymasterMode.SPONSORED}}); + * + * let walletClient = createWalletClient({ + account, + chain: baseSepolia, + transport: http() + }); + + let smartAccount = await createSmartAccountClient({ + signer: walletClient, + paymasterUrl: "https://paymaster.biconomy.io/api/v1/...", + bundlerUrl: `https://bundler.biconomy.io/api/v2/84532/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44`, + chainId: 84532 + }); + const response = await smartAccount.transferOwnership(newOwner, DEFAULT_ECDSA_OWNERSHIP_MODULE, {paymasterServiceData: {mode: PaymasterMode.SPONSORED}}); + + walletClient = createWalletClient({ + newOwnerAccount, + chain: baseSepolia, + transport: http() + }) + + smartAccount = await createSmartAccountClient({ + signer: walletClient, + paymasterUrl: "https://paymaster.biconomy.io/api/v1/...", + bundlerUrl: `https://bundler.biconomy.io/api/v2/84532/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44`, + chainId: 84532, + accountAddress: await smartAccount.getAccountAddress() + }) * ``` */ async transferOwnership( newOwner: Address, + moduleAddress: TransferOwnershipCompatibleModule, buildUseropDto?: BuildUserOpOptions ): Promise { const encodedCall = encodeFunctionData({ @@ -1400,7 +1420,7 @@ export class BiconomySmartAccountV2 extends BaseSmartContractAccount { args: [newOwner] }) const transaction = { - to: ECDSA_OWNERSHIP_MODULE_ADDRESSES_BY_VERSION.V1_0_0, + to: moduleAddress, data: encodedCall } const userOpResponse: UserOpResponse = await this.sendTransaction( diff --git a/src/account/utils/Types.ts b/src/account/utils/Types.ts index 7a1f2d1fd..e28ed8b3a 100644 --- a/src/account/utils/Types.ts +++ b/src/account/utils/Types.ts @@ -609,3 +609,7 @@ export interface ISmartContractAccount< upgradeToInitData: Hex ) => Promise } + +export type TransferOwnershipCompatibleModule = + | "0x0000001c5b32F37F5beA87BDD5374eB2aC54eA8e" + | "0x000000824dc138db84FD9109fc154bdad332Aa8E" diff --git a/tests/account/write.test.ts b/tests/account/write.test.ts index b4862b100..72e28d643 100644 --- a/tests/account/write.test.ts +++ b/tests/account/write.test.ts @@ -22,7 +22,8 @@ import { EntryPointAbi } from "../../src/account/abi/EntryPointAbi" import { getAAError } from "../../src/bundler/utils/getAAError" import { DEFAULT_ECDSA_OWNERSHIP_MODULE, - DEFAULT_SESSION_KEY_MANAGER_MODULE + DEFAULT_MULTICHAIN_MODULE, + createMultiChainValidationModule } from "../../src/modules" import { PaymasterMode } from "../../src/paymaster" import { testOnlyOnOptimism } from "../setupFiles" @@ -503,44 +504,50 @@ describe("Account:Write", () => { }, 60000) }) - describe("Transfer ownership", () => { - test("should transfer ownership of smart account to accountTwo", async () => { - const newOwner = accountTwo.address - const _smartAccount = await createSmartAccountClient({ - signer: walletClient, - paymasterUrl, - bundlerUrl, - accountAddress: "0xe6dBb5C8696d2E0f90B875cbb6ef26E3bBa575AC" - }) + describe("Transfer ownership", async () => { + const firstOwner = account.address + const newOwner = accountTwo.address + let _smartAccount = await createSmartAccountClient({ + signer: walletClient, + paymasterUrl, + bundlerUrl + // accountAddress: "0xe6dBb5C8696d2E0f90B875cbb6ef26E3bBa575AC" + }) + + const smartAccountAddress = await _smartAccount.getAccountAddress() + test("should transfer ownership of smart account to accountTwo", async () => { const signerOfAccount = walletClient.account.address const ownerOfAccount = await publicClient.readContract({ - address: "0x0000001c5b32F37F5beA87BDD5374eB2aC54eA8e", + address: DEFAULT_ECDSA_OWNERSHIP_MODULE, abi: ECDSAModuleAbi, functionName: "getOwner", args: [await _smartAccount.getAccountAddress()] }) expect(ownerOfAccount).toBe(signerOfAccount) - const response = await _smartAccount.transferOwnership(newOwner, { - paymasterServiceData: { mode: PaymasterMode.SPONSORED } - }) + const response = await _smartAccount.transferOwnership( + newOwner, + DEFAULT_ECDSA_OWNERSHIP_MODULE, + { + paymasterServiceData: { mode: PaymasterMode.SPONSORED } + } + ) const receipt = await response.wait() expect(receipt.success).toBe("true") }, 35000) test("should revert transfer ownership with signer that is not the owner", async () => { - const newOwner = accountTwo.address - const _smartAccount = await createSmartAccountClient({ + _smartAccount = await createSmartAccountClient({ signer: walletClient, paymasterUrl, bundlerUrl, - accountAddress: "0xe6dBb5C8696d2E0f90B875cbb6ef26E3bBa575AC" + accountAddress: smartAccountAddress }) const signerOfAccount = walletClient.account.address const ownerOfAccount = await publicClient.readContract({ - address: "0x0000001c5b32F37F5beA87BDD5374eB2aC54eA8e", + address: DEFAULT_ECDSA_OWNERSHIP_MODULE, abi: ECDSAModuleAbi, functionName: "getOwner", args: [await _smartAccount.getAccountAddress()] @@ -548,20 +555,23 @@ describe("Account:Write", () => { expect(ownerOfAccount).not.toBe(signerOfAccount) expect( - _smartAccount.transferOwnership(newOwner, { - paymasterServiceData: { mode: PaymasterMode.SPONSORED } - }) + _smartAccount.transferOwnership( + newOwner, + DEFAULT_ECDSA_OWNERSHIP_MODULE, + { + paymasterServiceData: { mode: PaymasterMode.SPONSORED } + } + ) ).rejects.toThrowError() }, 35000) test("send an user op with the new owner", async () => { - const _smartAccount = await createSmartAccountClient({ + _smartAccount = await createSmartAccountClient({ signer: walletClientTwo, paymasterUrl, bundlerUrl, - accountAddress: "0xe6dBb5C8696d2E0f90B875cbb6ef26E3bBa575AC" + accountAddress: smartAccountAddress }) - const newOwner = accountTwo.address const currentSmartAccountInstanceSigner = await _smartAccount .getSigner() .getAddress() @@ -582,11 +592,11 @@ describe("Account:Write", () => { }, 35000) test("should revert if sending an user op with the old owner", async () => { - const _smartAccount = await createSmartAccountClient({ + _smartAccount = await createSmartAccountClient({ signer: walletClient, paymasterUrl, bundlerUrl, - accountAddress: "0xe6dBb5C8696d2E0f90B875cbb6ef26E3bBa575AC" + accountAddress: smartAccountAddress }) const tx = { to: nftAddress, @@ -606,17 +616,16 @@ describe("Account:Write", () => { }, 35000) test("should transfer ownership of smart account back to EOA 1", async () => { - const newOwner = account.address - const _smartAccount = await createSmartAccountClient({ + _smartAccount = await createSmartAccountClient({ signer: walletClientTwo, paymasterUrl, bundlerUrl, - accountAddress: "0xe6dBb5C8696d2E0f90B875cbb6ef26E3bBa575AC" // account address at index 0 of EOA 1 + accountAddress: smartAccountAddress }) const signerOfAccount = walletClientTwo.account.address const ownerOfAccount = await publicClient.readContract({ - address: "0x0000001c5b32F37F5beA87BDD5374eB2aC54eA8e", + address: DEFAULT_ECDSA_OWNERSHIP_MODULE, abi: ECDSAModuleAbi, functionName: "getOwner", args: [await _smartAccount.getAccountAddress()] @@ -624,9 +633,13 @@ describe("Account:Write", () => { expect(ownerOfAccount).toBe(signerOfAccount) - const response = await _smartAccount.transferOwnership(newOwner, { - paymasterServiceData: { mode: PaymasterMode.SPONSORED } - }) + const response = await _smartAccount.transferOwnership( + firstOwner, + DEFAULT_ECDSA_OWNERSHIP_MODULE, + { + paymasterServiceData: { mode: PaymasterMode.SPONSORED } + } + ) const receipt = await response.wait() expect(receipt.success).toBe("true") }, 45000) diff --git a/tests/modules/write.test.ts b/tests/modules/write.test.ts index 3407dd1a7..9f1e32357 100644 --- a/tests/modules/write.test.ts +++ b/tests/modules/write.test.ts @@ -621,6 +621,7 @@ describe("Modules:Write", () => { // Transfer ownership back to walletClient 1 await smartAccountWithOtherOwner.transferOwnership( walletClient.account.address, + DEFAULT_ECDSA_OWNERSHIP_MODULE, { paymasterServiceData: { mode: PaymasterMode.SPONSORED } } ) } @@ -723,13 +724,17 @@ describe("Modules:Write", () => { Logger.log("Tx Hash: ", transactionDetails.receipt.transactionHash) // Transfer ownership back to walletClient - const resp = await smartAccount.transferOwnership(newOwner, { - paymasterServiceData: { mode: PaymasterMode.SPONSORED }, - params: { - sessionSigner: sessionSigner, - sessionValidationModule: abiSvmAddress + const resp = await smartAccount.transferOwnership( + newOwner, + DEFAULT_ECDSA_OWNERSHIP_MODULE, + { + paymasterServiceData: { mode: PaymasterMode.SPONSORED }, + params: { + sessionSigner: sessionSigner, + sessionValidationModule: abiSvmAddress + } } - }) + ) }, 60000) }) })