From c3c0141343ac6d6154f53094bb6fb844da788295 Mon Sep 17 00:00:00 2001 From: Dennis <10233439+idea404@users.noreply.github.com> Date: Sun, 12 Nov 2023 17:04:23 +0100 Subject: [PATCH] chore: refactoring and demo scripts --- ...ctions.sol => SharedRestrictedAccount.sol} | 2 +- ...sol => SharedRestrictedAccountFactory.sol} | 2 +- demo/shared-restricted-fund.ts | 36 +++++ ...deploy-shared-account-with-restrictions.ts | 16 -- deploy/deploy-shared-restricted.ts | 34 ++++ deploy/vars.json | 12 +- package.json | 4 +- test/main.test.ts | 149 +++++++++++++++++- test/shared-account-with-restrictions.test.ts | 133 ---------------- test/utils.ts | 52 +++--- utils.ts | 37 ----- 11 files changed, 260 insertions(+), 217 deletions(-) rename contracts/SharedAccountWithRestrictions/{SharedAccountWithRestrictions.sol => SharedRestrictedAccount.sol} (99%) rename contracts/SharedAccountWithRestrictions/{SharedAccountWithRestrictionsFactory.sol => SharedRestrictedAccountFactory.sol} (95%) create mode 100644 demo/shared-restricted-fund.ts delete mode 100644 deploy/deploy-shared-account-with-restrictions.ts create mode 100644 deploy/deploy-shared-restricted.ts delete mode 100644 test/shared-account-with-restrictions.test.ts delete mode 100644 utils.ts diff --git a/contracts/SharedAccountWithRestrictions/SharedAccountWithRestrictions.sol b/contracts/SharedAccountWithRestrictions/SharedRestrictedAccount.sol similarity index 99% rename from contracts/SharedAccountWithRestrictions/SharedAccountWithRestrictions.sol rename to contracts/SharedAccountWithRestrictions/SharedRestrictedAccount.sol index 61865f8..35b8e48 100644 --- a/contracts/SharedAccountWithRestrictions/SharedAccountWithRestrictions.sol +++ b/contracts/SharedAccountWithRestrictions/SharedRestrictedAccount.sol @@ -12,7 +12,7 @@ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; bytes4 constant EIP1271_SUCCESS_RETURN_VALUE = 0x1626ba7e; -contract SharedAccountWithRestrictions is IAccount, IERC1271, IERC721Receiver { +contract SharedRestrictedAccount is IAccount, IERC1271, IERC721Receiver { using TransactionHelper for Transaction; address private admin; diff --git a/contracts/SharedAccountWithRestrictions/SharedAccountWithRestrictionsFactory.sol b/contracts/SharedAccountWithRestrictions/SharedRestrictedAccountFactory.sol similarity index 95% rename from contracts/SharedAccountWithRestrictions/SharedAccountWithRestrictionsFactory.sol rename to contracts/SharedAccountWithRestrictions/SharedRestrictedAccountFactory.sol index 1b44c9f..51fd483 100644 --- a/contracts/SharedAccountWithRestrictions/SharedAccountWithRestrictionsFactory.sol +++ b/contracts/SharedAccountWithRestrictions/SharedRestrictedAccountFactory.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; import "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; -contract SharedAccountWithRestrictionsFactory { +contract SharedRestrictedAccountFactory { bytes32 public aaBytecodeHash; constructor(bytes32 _aaBytecodeHash) { diff --git a/demo/shared-restricted-fund.ts b/demo/shared-restricted-fund.ts new file mode 100644 index 0000000..a62e673 --- /dev/null +++ b/demo/shared-restricted-fund.ts @@ -0,0 +1,36 @@ +// This script will: +// 1. Send ETH to the shared restricted account + +import { ethers } from "ethers"; +import { Provider, Wallet } from "zksync-web3"; +import { getDeployedContractDetailsFromVars, config } from "../deploy/utils"; + +const NETWORK = "zkSyncLocalnet"; +const RPC_URL = config.L2RpcUrl; +const PRIVATE_KEY = config.firstWalletPrivateKey; + +async function main() { + const provider = new Provider(RPC_URL); + const mainWallet = new Wallet(PRIVATE_KEY, provider); + + // Load the contract address from vars.json + const accountAddress = getDeployedContractDetailsFromVars(NETWORK, "SharedRestrictedAccount").address; + // send 100 ETH to the paAddress + const balance = await provider.getBalance(accountAddress); + const tx = await mainWallet.sendTransaction({ + to: accountAddress, + value: ethers.utils.parseEther("100"), + }); + await tx.wait(); + const newBalance = await provider.getBalance(accountAddress); + console.log(`Sent 100 ETH to Pension Account at: ${accountAddress}`); + console.log(`Balance before: ${ethers.utils.formatEther(balance)} ETH`); + console.log(`Balance after: ${ethers.utils.formatEther(newBalance)} ETH`); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/deploy/deploy-shared-account-with-restrictions.ts b/deploy/deploy-shared-account-with-restrictions.ts deleted file mode 100644 index 9cf4f59..0000000 --- a/deploy/deploy-shared-account-with-restrictions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Wallet, Provider } from "zksync-web3"; -import { HardhatRuntimeEnvironment, HttpNetworkConfig } from "hardhat/types"; -import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; -import { deployAccountAbstraction } from "../utils"; - -import dotenv from "dotenv"; -dotenv.config(); - -const KEY = process.env.PRIVATE_KEY as string; - -export default async function (hre: HardhatRuntimeEnvironment) { - const provider = new Provider({ url: (hre.network.config as HttpNetworkConfig).url }); - const wallet = new Wallet(KEY).connect(provider); - const deployer = new Deployer(hre, wallet); - await deployAccountAbstraction(deployer, "SharedAccountWithRestrictionsFactory", "SharedAccountWithRestrictions"); -} diff --git a/deploy/deploy-shared-restricted.ts b/deploy/deploy-shared-restricted.ts new file mode 100644 index 0000000..d436324 --- /dev/null +++ b/deploy/deploy-shared-restricted.ts @@ -0,0 +1,34 @@ +import { Wallet, Provider, utils } from "zksync-web3"; +import { HardhatRuntimeEnvironment, HttpNetworkConfig } from "hardhat/types"; +import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; +import { config, saveContractToVars } from "../deploy/utils"; +import { ethers } from "ethers"; + +const KEY = config.firstWalletPrivateKey; + +export default async function (hre: HardhatRuntimeEnvironment) { + const provider = new Provider({ url: (hre.network.config as HttpNetworkConfig).url }); + const wallet = new Wallet(KEY).connect(provider); + const deployer = new Deployer(hre, wallet); + const accountFactoryArtifact = await deployer.loadArtifact("SharedRestrictedAccountFactory"); + const accountArtifact = await deployer.loadArtifact("SharedRestrictedAccount"); + + // Deploy the factory + const bytecodeHash = utils.hashBytecode(accountArtifact.bytecode); + const factory = await deployer.deploy(accountFactoryArtifact, [bytecodeHash], undefined, [accountArtifact.bytecode]); + console.log(`SharedRestrictedAccountFactory address: ${factory.address}`); + saveContractToVars(hre.network.name, "SharedRestrictedAccountFactory", factory.address); + + // Deploy the account + const salt = ethers.constants.HashZero; + + // deploy account owned by owner1 & owner2 + const tx = await factory.deployAccount(salt, wallet.address); + await tx.wait(); + + // Getting the address of the deployed contract account + const abiCoder = new ethers.utils.AbiCoder(); + let accountAddress = utils.create2Address(factory.address, await factory.aaBytecodeHash(), salt, abiCoder.encode(["address"], [wallet.address])); + console.log(`SharedRestrictedAccount address: ${accountAddress}`); + saveContractToVars(hre.network.name, "SharedRestrictedAccount", accountAddress); +} diff --git a/deploy/vars.json b/deploy/vars.json index 7a4198b..7c95b7a 100644 --- a/deploy/vars.json +++ b/deploy/vars.json @@ -3,11 +3,19 @@ "deployed": [ { "name": "PensionAccountFactory", - "address": "0x111C3E89Ce80e62EE88318C2804920D4c96f92bb" + "address": "0x094499Df5ee555fFc33aF07862e43c90E6FEe501" }, { "name": "PensionAccount", - "address": "0x58d487F21a6baCDAC552dfce6EE09365bD52dbE1" + "address": "0xafA540E68a4e544d604277311190a0AC2CA6B8E6" + }, + { + "name": "SharedRestrictedAccountFactory", + "address": "0xb76eD02Dea1ba444609602BE5D587c4bFfd67153" + }, + { + "name": "SharedRestrictedAccount", + "address": "0x542baA36793feE02404c081B26284120af899428" } ] } diff --git a/package.json b/package.json index ec64b54..f919a95 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "MIT", "scripts": { "test": "hardhat test --network hardhat", - "deploy:shared-account-with-restrictions": "hardhat deploy-zksync --script deploy-shared-account-with-restrictions.ts --network zkSyncLocalnet", + "deploy:shared-restricted": "hardhat deploy-zksync --script deploy-shared-restricted.ts --network zkSyncLocalnet", "deploy:aafactory": "hardhat deploy-zksync --script deploy-aafactory.ts --network zkSyncLocalnet", "deploy:multisig": "hardhat deploy-zksync --script deploy-multisig.ts --network zkSyncLocalnet", "deploy:pafactory": "hardhat deploy-zksync --script deploy-pafactory.ts --network zkSyncLocalnet", @@ -13,6 +13,8 @@ "demo:pension-setup": "yarn deploy:pafactory && yarn deploy:pension && yarn demo:pension-fund", "demo:pension-fund": "ts-node demo/pension-fund-eth.ts", "demo:pension-withdraw": "ts-node demo/pension-send-eth.ts", + "demo:shared-restricted-setup": "yarn deploy:shared-restricted && yarn demo:shared-restricted-fund", + "demo:shared-restricted-fund": "ts-node demo/shared-restricted-fund.ts", "demo:fast-forward": "ts-node demo/fast-forward.ts" }, "devDependencies": { diff --git a/test/main.test.ts b/test/main.test.ts index a651b12..e7057b9 100644 --- a/test/main.test.ts +++ b/test/main.test.ts @@ -3,11 +3,15 @@ import { expect } from "chai"; import * as hre from "hardhat"; import { ethers } from "ethers"; import * as zks from "zksync-web3"; -import { deployFactory, deployMultisig, fundAccount, MultiSigWallet, deployPension, PensionWallet, advanceBlocks } from "./utils"; +import { deployFactory, deployMultisig, fundAccount, MultiSigWallet, deployPension, SingleSignerAAWallet, advanceBlocks, deploySharedRestricted, deployContract } from "./utils"; const config = { firstWalletPrivateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", firstWalletAddress: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + secondWalletPrivateKey: "0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3", + secondWalletAddress: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + thirdWalletPrivateKey: "0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e", + thirdWalletAddress: "0x0D43eB5B8a47bA8900d84AA36656c92024e9772e", }; describe("Account Abstraction Tests", function () { @@ -145,7 +149,7 @@ describe("Account Abstraction Tests", function () { }); it("Should not be able to withdraw before the lockup period", async function () { - const pensionWallet = new PensionWallet( + const pensionWallet = new SingleSignerAAWallet( pensionAccountContract.address, ownerWallet.privateKey, provider @@ -173,7 +177,7 @@ describe("Account Abstraction Tests", function () { // Advance the blockchain by 10 blocks await advanceBlocks(10); - const pensionWallet = new PensionWallet( + const pensionWallet = new SingleSignerAAWallet( pensionAccountContract.address, ownerWallet.privateKey, provider @@ -196,4 +200,143 @@ describe("Account Abstraction Tests", function () { }); }); }); + + describe("Shared Restricted Account Abstraction Tests", function () { + const accountContractName = "SharedRestrictedAccount"; + const factoryContractName = "SharedRestrictedAccountFactory"; + let factoryContract: ethers.Contract; + describe("Shared Restricted Account Factory", function () { + before(async function () { + factoryContract = await deployFactory(firstRichWallet, accountContractName, factoryContractName); + }); + + it("Should have a tx hash that starts from 0x", async function () { + result = factoryContract.deployTransaction.hash; + expect(result).to.contains("0x"); + }); + }); + + describe("Shared Restricted Account", function () { + const firstOwnerWallet = new zks.Wallet(config.secondWalletPrivateKey, provider); + const secondOwnerWallet = new zks.Wallet(config.thirdWalletPrivateKey, provider); + let sharedRestrictedAccountContract: ethers.Contract; + let testContract1: ethers.Contract; + let testContract2: ethers.Contract; + let testContract3: ethers.Contract; + before(async function () { + sharedRestrictedAccountContract = await deploySharedRestricted(firstRichWallet, factoryContract.address, firstRichWallet); + await fundAccount(firstRichWallet, sharedRestrictedAccountContract.address); + testContract1 = await deployContract(firstRichWallet, "TestContract"); + testContract2 = await deployContract(firstRichWallet, "TestContract"); + testContract3 = await deployContract(firstRichWallet, "TestContract"); + }); + + it("Should have a tx hash that starts from 0x", async function () { + result = factoryContract.deployTransaction.hash; + expect(result).to.contains("0x"); + }); + + it("Should have to owners initially", async function () { + const owners = await sharedRestrictedAccountContract.getOwners(); + expect(owners.length).to.equal(0); + }); + + it("Should be able to add owners", async function () { + const tx = await sharedRestrictedAccountContract.addOwner(firstOwnerWallet.address); + await tx.wait(); + const owners = await sharedRestrictedAccountContract.getOwners(); + expect(owners.length).to.equal(1); + expect(owners[0]).to.equal(firstOwnerWallet.address); + const tx2 = await sharedRestrictedAccountContract.addOwner(secondOwnerWallet.address); + await tx2.wait(); + const owners2 = await sharedRestrictedAccountContract.getOwners(); + expect(owners2.length).to.equal(2); + expect(owners2[0]).to.equal(firstOwnerWallet.address); + expect(owners2[1]).to.equal(secondOwnerWallet.address); + }); + + it("Should have no allowed call addresses initially", async function () { + const allowedCallAddresses = await sharedRestrictedAccountContract.getAllowedCallAddresses(); + expect(allowedCallAddresses.length).to.equal(0); + }); + + it("Should be able to add allowed call addresses", async function () { + const tx = await sharedRestrictedAccountContract.addAllowedCallAddress(testContract1.address, [ + testContract1.interface.getSighash('testFunction1') + ]); + await tx.wait(); + const allowedCallAddresses = await sharedRestrictedAccountContract.getAllowedCallAddresses(); + expect(allowedCallAddresses.length).to.equal(1); + expect(allowedCallAddresses[0]).to.equal(testContract1.address); + const tx2 = await sharedRestrictedAccountContract.addAllowedCallAddress(testContract2.address, [ + testContract2.interface.getSighash('testFunction2') + ]); + await tx2.wait(); + const allowedCallAddresses2 = await sharedRestrictedAccountContract.getAllowedCallAddresses(); + expect(allowedCallAddresses2.length).to.equal(2); + expect(allowedCallAddresses2[0]).to.equal(testContract1.address); + expect(allowedCallAddresses2[1]).to.equal(testContract2.address); + }); + + it("Should not be able to use account with some random wallet", async function () { + const randomWallet = zks.Wallet.createRandom(); + const accountWallet = new SingleSignerAAWallet( + sharedRestrictedAccountContract.address, + randomWallet.privateKey, + provider + ); + try { + accountWallet.transfer({ + to: firstRichWallet.address, + amount: ethers.utils.parseUnits("1", 18), + overrides: { type: 113 }, + }); + } catch (e) { + expect(e.message).to.contains("execution reverted: Account validation error"); + } + }); + + it("Should be able to use account with owner wallet and allowed contract method", async function () { + const accountWallet = new SingleSignerAAWallet( + sharedRestrictedAccountContract.address, + firstOwnerWallet.privateKey, + provider + ); + const testContract1accountWallet = testContract1.connect(accountWallet); + const result = await testContract1accountWallet.testFunction1(firstOwnerWallet.address); + expect(result).to.equal(firstOwnerWallet.address); + const testContract2accountWallet = testContract2.connect(accountWallet); + const result2 = await testContract2accountWallet.testFunction2(); + expect(result2).to.equal(true); + }); + + it("Should not be able to use account with owner wallet and not allowed call address", async function () { + const accountWallet = new SingleSignerAAWallet( + sharedRestrictedAccountContract.address, + firstOwnerWallet.privateKey, + provider + ); + const testContract3accountWallet = testContract3.connect(accountWallet); + try { + testContract3accountWallet.testFunction1(firstOwnerWallet.address); + } catch (e) { + expect(e.message).to.contains("execution reverted: Account validation error"); + } + }); + + it("Should not be able to use account with owner wallet and allowed call address but not allowed method", async function () { + const accountWallet = new SingleSignerAAWallet( + sharedRestrictedAccountContract.address, + firstOwnerWallet.privateKey, + provider + ); + const testContract1accountWallet = testContract1.connect(accountWallet); + try { + testContract1accountWallet.testFunction3(); + } catch (e) { + expect(e.message).to.contains("execution reverted: Account validation error"); + } + }); + }); + }); }); diff --git a/test/shared-account-with-restrictions.test.ts b/test/shared-account-with-restrictions.test.ts deleted file mode 100644 index 69a9f03..0000000 --- a/test/shared-account-with-restrictions.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import "@matterlabs/hardhat-zksync-node/dist/type-extensions"; -import * as hre from "hardhat"; -import { ethers, BigNumber } from "ethers"; -import * as zks from "zksync-web3"; -import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; -import { expectThrowsAsync, fundAccount } from "./utils"; -import { deployAccountAbstraction, deployContract } from "../utils"; - -const config = { - firstWalletPrivateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", - firstWalletAddress: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", -}; - -async function sendAATransaction( - tx: ethers.PopulatedTransaction, - aaContractAddress: string, - provider: zks.Provider, - signer: zks.Wallet, - customGasLimit?: BigNumber -) { - tx.from = aaContractAddress; - const gasLimit = customGasLimit || await provider.estimateGas(tx); - const gasPrice = await provider.getGasPrice(); - tx = { - ...tx, - gasLimit, - gasPrice, - chainId: (await provider.getNetwork()).chainId, - nonce: await provider.getTransactionCount(aaContractAddress), - type: 113, - customData: { - gasPerPubdata: zks.utils.DEFAULT_GAS_PER_PUBDATA_LIMIT - } as zks.types.Eip712Meta, - value: BigNumber.from(0), - }; - const signedTxHash = zks.EIP712Signer.getSignedDigest(tx); - const signature = ethers.utils.joinSignature(signer._signingKey().signDigest(signedTxHash)); - tx.customData = { - ...tx.customData, - customSignature: signature, - }; - const aaTx = await provider.sendTransaction(zks.utils.serialize(tx)); - await aaTx.wait(); -} - -describe("Shared account with restrictions", function () { - let provider: zks.Provider; - let admin: zks.Wallet; - - before(async function () { - provider = new zks.Provider(hre.network.config.url); - admin = new zks.Wallet(config.firstWalletPrivateKey, provider); - }); - - // single test just to showcase how it works - it("verifies account works as expected", async () => { - const deployer = new Deployer(hre, admin); - - // Deploy AA contract - const aaContract = await deployAccountAbstraction(deployer, "SharedAccountWithRestrictionsFactory", "SharedAccountWithRestrictions"); - - // Fund the AA contract - await fundAccount(admin, aaContract.address, "2"); - - // Deploy some test contracts - const testContract1 = await deployContract(deployer, "TestContract", []); - const testContract2 = await deployContract(deployer, "TestContract", []); - const testContract3 = await deployContract(deployer, "TestContract", []); - - // add owners - const owner1 = zks.Wallet.createRandom(); - const owner2 = zks.Wallet.createRandom(); - await (await aaContract.addOwner(owner1.address)).wait(); - await (await aaContract.addOwner(owner2.address)).wait(); - - // add allowed call addresses and methods - await (await aaContract.addAllowedCallAddress(testContract1.address, [ - testContract1.interface.getSighash('testFunction1') - ])).wait(); - await (await aaContract.addAllowedCallAddress(testContract2.address, [ - testContract2.interface.getSighash('testFunction2') - ])).wait(); - - // try to use account with some random wallet - const randomWallet = zks.Wallet.createRandom(); - let txWithRandomSigner = await testContract1.populateTransaction.testFunction1(randomWallet.address) - const sendTxSignedByRandomWallet = () => sendAATransaction( - txWithRandomSigner, - aaContract.address, - provider, - randomWallet - ) - await expectThrowsAsync(sendTxSignedByRandomWallet, "Account validation error"); - - // use account with owner wallet and allowed contract method - // should not throw - let txWithOwner1Signer = await testContract1.populateTransaction.testFunction1(owner1.address) - await sendAATransaction( - txWithOwner1Signer, - aaContract.address, - provider, - owner1 - ); - // should not throw - let txWithOwner2Signer = await testContract2.populateTransaction.testFunction2() - await sendAATransaction( - txWithOwner2Signer, - aaContract.address, - provider, - owner2 - ); - - // try to use account with owner wallet and not allowed call address - let txWithNotAllowedCallAddress = await testContract3.populateTransaction.testFunction1(owner1.address) - const sendTxWithNotAllowedCallAddress = () => sendAATransaction( - txWithNotAllowedCallAddress, - aaContract.address, - provider, - owner1 - ) - await expectThrowsAsync(sendTxWithNotAllowedCallAddress, "Account validation error"); - - // try to use account with owner wallet and allowed call address but not allowed method - let txWithNotAllowedMethod = await testContract1.populateTransaction.testFunction3() - const sendTxWithNotAllowedMethod = () => sendAATransaction( - txWithNotAllowedMethod, - aaContract.address, - provider, - owner1 - ) - await expectThrowsAsync(sendTxWithNotAllowedMethod, "Account validation error"); - }); -}); diff --git a/test/utils.ts b/test/utils.ts index 65837de..4ac7b1e 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -2,7 +2,6 @@ import { utils, Wallet, Provider, types } from "zksync-web3"; import * as hre from "hardhat"; import { ethers } from "ethers"; import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; -import { expect } from "chai"; export async function deployFactory(wallet: Wallet, accountContractName: string, accountFactoryContractName: string) { const deployer = new Deployer(hre, wallet); @@ -21,6 +20,13 @@ export async function deployFactory(wallet: Wallet, accountContractName: string, return accountFactory; } +export async function deployContract(wallet: Wallet, contractName: string, args: any[] = [], additionalFactoryDeps: string[] = []) { + const deployer = new Deployer(hre, wallet); + const contractArtifact = await deployer.loadArtifact(contractName); + const contract = await deployer.deploy(contractArtifact, args, undefined, additionalFactoryDeps); + return contract; +} + export async function deployMultisig(wallet: Wallet, factoryAddress: string, ownerWallet1: Wallet, ownerWallet2: Wallet) { const factoryArtifact = await hre.artifacts.readArtifact("AAFactory"); const accountArtifact = await hre.artifacts.readArtifact("TwoUserMultisig"); @@ -75,6 +81,27 @@ export async function deployPension(wallet: Wallet, factoryAddress: string, wall return pensionAccountContract; } +export async function deploySharedRestricted(wallet: Wallet, factoryAddress: string, ownerWallet: Wallet) { + const factoryArtifact = await hre.artifacts.readArtifact("SharedRestrictedAccountFactory"); + const accountArtifact = await hre.artifacts.readArtifact("SharedRestrictedAccount"); + + const sraFactory = new ethers.Contract(factoryAddress, factoryArtifact.abi, wallet); + + // For the simplicity of the tutorial, we will use zero hash as salt + const salt = ethers.constants.HashZero; + + // deploy account owned by owner1 & owner2 + const tx = await sraFactory.deployAccount(salt, ownerWallet.address); + await tx.wait(); + + // Getting the address of the deployed contract account + const abiCoder = new ethers.utils.AbiCoder(); + let accountAddress = utils.create2Address(factoryAddress, await sraFactory.aaBytecodeHash(), salt, abiCoder.encode(["address"], [ownerWallet.address])); + + const accountContract = new ethers.Contract(accountAddress, accountArtifact.abi, ownerWallet); + return accountContract; +} + export async function fundAccount(wallet: Wallet, destinationAddress: string, amount: string = "100") { // Send funds to the account await ( @@ -120,7 +147,7 @@ export class MultiSigWallet extends Wallet { } // Temporary wallet for testing - that is accepting one private key - and signs the transaction with it. -export class PensionWallet extends Wallet { +export class SingleSignerAAWallet extends Wallet { readonly accountAddress: string; // accountAddress - is the account abstraction address for which, we'll use the private key to sign transactions. @@ -147,27 +174,6 @@ export class PensionWallet extends Wallet { } } -export async function expectThrowsAsync( - // eslint-disable-next-line @typescript-eslint/ban-types - method: Function, - errorMessage: string -): Promise { - let error = null; - try { - await method(); - } catch (err) { - error = err; - } - - expect(error).to.be.an("Error"); - if (errorMessage) { - expect((error as unknown as Error).message).to.include(errorMessage); - return (error as unknown as Error).message; - } - - return ""; -} - function createMockAddress(base: string) { const baseHex = base.replace(/[^0-9A-Fa-f]/g, ''); // Remove non-hex characters const paddingLength = 40 - baseHex.length; // Calculate padding length diff --git a/utils.ts b/utils.ts deleted file mode 100644 index d8e6799..0000000 --- a/utils.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Contract, utils } from "zksync-web3"; -import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; -import { ethers } from "ethers"; - -export async function deployContract(deployer: Deployer, contractName: string, args: any[] = [], additionalFactoryDeps: string[] = []): Promise { - const artifact = await deployer.loadArtifact(contractName); - const deploymentFee = await deployer.estimateDeployFee(artifact, args); - const parsedFee = ethers.utils.formatEther(deploymentFee.toString()); - console.log(`The ${artifact.contractName} deployment is estimated to cost ${parsedFee} ETH`); - const contract = await deployer.deploy(artifact, args, undefined, additionalFactoryDeps); - logBlue(`${artifact.contractName} was deployed to ${contract.address}`); - return contract; -} - -export async function deployAccountAbstraction(deployer: Deployer, factoryContractName: string, accountContractName: string): Promise { - const aaArtifact = await deployer.loadArtifact(accountContractName); - const bytecodeHash = utils.hashBytecode(aaArtifact.bytecode); - const aaFactoryContract = await deployContract(deployer, factoryContractName, [bytecodeHash], [aaArtifact.bytecode]); - logBlue(`${aaFactoryContract.contractName} was deployed to ${aaFactoryContract.address}`); - const salt = ethers.utils.hexlify(ethers.utils.randomBytes(32)); - // using deployer address as account admin - let tx = await aaFactoryContract.deployAccount(salt, deployer.zkWallet.address); - await tx.wait(); - const abiCoder = new ethers.utils.AbiCoder(); - const accountAbstractionAddress = utils.create2Address( - aaFactoryContract.address, - await aaFactoryContract.aaBytecodeHash(), - salt, - abiCoder.encode(["address"], [deployer.zkWallet.address]) - ); - logBlue(`${aaArtifact.contractName} was deployed to ${accountAbstractionAddress}`); - return new ethers.Contract(accountAbstractionAddress, aaArtifact.abi, deployer.zkWallet); -} - -function logBlue(value: string) { - console.log('\x1b[36m%s\x1b[0m', value); -} \ No newline at end of file