Skip to content
This repository has been archived by the owner on Oct 3, 2024. It is now read-only.

Commit

Permalink
Fix tutorials to support zksync-ethers v6 (#914)
Browse files Browse the repository at this point in the history
Co-authored-by: Roman Petriv <[email protected]>
Co-authored-by: Jack Hamer <[email protected]>
  • Loading branch information
3 people authored Mar 6, 2024
1 parent e680d8f commit 928d995
Show file tree
Hide file tree
Showing 7 changed files with 376 additions and 240 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Skip the hassle for test ETH by using `zksync-cli` for local testing. Simply exe
Download the complete project [here](https://github.com/matter-labs/tutorials/tree/main/custom-aa).

::: info Project available in Atlas IDE
This entire tutorial can be run in under a minute using Atlas. Atlas is a smart contract IDE that lets you write, deploy, and interact with contracts from your browser. [Open this project in Atlas](https://app.atlaszk.com/projects?template=https://github.com/atlas-labs-inc/zksync-aa-multisig&open=/scripts/main.ts&chainId=280).
This entire tutorial can be run in under a minute using Atlas. Atlas is a smart contract IDE that lets you write, deploy, and interact with contracts from your browser. [Open this project in Atlas](https://app.atlaszk.com/projects?template=https://github.com/atlas-labs-inc/zksync-aa-multisig&open=/scripts/main.ts&chainId=300).
:::

## Setup
Expand All @@ -41,33 +41,34 @@ This entire tutorial can be run in under a minute using Atlas. Atlas is a smart
npx zksync-cli create custom-aa-tutorial --template hardhat_solidity
```

This creates a new zkSync Era project called `custom-aa-tutorial` with a basic `Greeter` contract.
This creates a new zkSync Era project called `custom-aa-tutorial` with a few example contracts.

1. Navigate into the project directory:
2. Navigate into the project directory:

```sh
cd custom-aa-tutorial
```

3. For the purposes of this tutorial, we don't need the Greeter related files. So, proceed with removing `Greeter.sol` from our `/contracts` directory:
3. For the purposes of this tutorial, we don't need the example contracts related files. So, proceed by removing all the files inside the `/contracts` and `/deploy` folders manually or by running the following commands::

```sh
rm -rf ./contracts/Greeter.sol
rm -rf ./contracts/*
rm -rf ./deploy/*
```

4. Similarly, remove the deploy scripts associated with the Greeter contract:
4. Add the zkSync and OpenZeppelin contract libraries:

```sh
rm -rf ./deploy/deploy-greeter.ts && rm -rf ./deploy/use-greeter.ts
yarn add -D @matterlabs/zksync-contracts @openzeppelin/[email protected]
```

5. Add the zkSync and OpenZeppelin contract libraries:
5. Include the `isSystem: true` setting in the `zksolc` section of the `hardhat.config.ts` configuration file to allow interaction with system contracts:

```sh
yarn add -D @matterlabs/zksync-contracts @openzeppelin/contracts
```
::: warning

6. Include the `isSystem: true` setting in the `hardhat.config.ts` configuration file to allow interaction with system contracts:
This project does not use the latest version available of `@openzeppelin/contracts`. Mae sure you install the specific version mentioned above.

:::

```ts
import { HardhatUserConfig } from "hardhat/config";
Expand All @@ -80,7 +81,7 @@ const config: HardhatUserConfig = {
zksolc: {
version: "latest", // Uses latest available in https://github.com/matter-labs/zksolc-bin/
settings: {
isSystem: true, // make sure to include this line
isSystem: true, // ⚠️ Make sure to include this line
},
},
defaultNetwork: "zkSyncTestnet",
Expand All @@ -93,7 +94,7 @@ const config: HardhatUserConfig = {
},
},
solidity: {
version: "0.8.17",
version: "0.8.20",
},
};

Expand Down Expand Up @@ -833,7 +834,7 @@ This functionality is built into the SDK.
## Deploy the Factory

::: tip
Make sure you deposit funds on zkSync Era using [bridges](https://zksync.io/explore#bridges) before running the deployment script.
Make sure you deposit funds on zkSync Era using [one of the available bridges](https://zksync.io/explore#bridges) before running the deployment script.
:::

1. In the `deploy` folder, create the file `deploy-factory.ts` and copy/paste the following code, replacing `<WALLET_PRIVATE_KET>` with your private key.
Expand All @@ -860,7 +861,9 @@ export default async function (hre: HardhatRuntimeEnvironment) {
aaArtifact.bytecode,
]);

console.log(`AA factory address: ${factory.address}`);
const factoryAddress = await factory.getAddress();

console.log(`AA factory address: ${factoryAddress}`);
}
```

Expand All @@ -874,7 +877,8 @@ yarn hardhat deploy-zksync --script deploy-factory.ts
The output should look like this:

```txt
AA factory address: 0x9db333Cb68Fb6D317E3E415269a5b9bE7c72627Ds
AA factory address: 0x70696950F71BB1cCF36Dbd1B77Ae54f96a79b005
✨ Done in 15.10s.
```

Note that the address will be different for each run.
Expand All @@ -886,7 +890,7 @@ Note that the address will be different for each run.
Now, let's deploy an account and use it to initiate a new transaction.

:::warning
This section assumes you have an EOA account with sufficient funds on zkSync Era.
This section assumes you have an EOA account with sufficient funds on zkSync Era to deploy a smart contract account.
:::

1. In the `deploy` folder, create a file called `deploy-multisig.ts`.
Expand All @@ -905,6 +909,7 @@ export default async function (hre: HardhatRuntimeEnvironment) {
const provider = new Provider("https://sepolia.era.zksync.dev");
// Private key of the account used to deploy
const wallet = new Wallet("<WALLET-PRIVATE-KEY>").connect(provider);

const factoryArtifact = await hre.artifacts.readArtifact("AAFactory");

const aaFactory = new ethers.Contract(AA_FACTORY_ADDRESS, factoryArtifact.abi, wallet);
Expand All @@ -914,14 +919,15 @@ export default async function (hre: HardhatRuntimeEnvironment) {
const owner2 = Wallet.createRandom();

// For the simplicity of the tutorial, we will use zero hash as salt
const salt = ethers.constants.HashZero;
const salt = ethers.ZeroHash;

// deploy account owned by owner1 & owner2
const tx = await aaFactory.deployAccount(salt, owner1.address, owner2.address);
await tx.wait();

// Getting the address of the deployed contract account
const abiCoder = new ethers.utils.AbiCoder();
// Always use the JS utility methods
const abiCoder = new ethers.AbiCoder();
const multisigAddress = utils.create2Address(
AA_FACTORY_ADDRESS,
await aaFactory.aaBytecodeHash(),
Expand All @@ -944,16 +950,18 @@ export default async function (hre: HardhatRuntimeEnvironment) {
Before the deployed account can submit transactions, we need to deposit some ETH to it for the transaction fees.

```ts
console.log("Sending funds to multisig account");

// Send funds to the multisig account we just deployed
await(
await wallet.sendTransaction({
to: multisigAddress,
// You can increase the amount of ETH sent to the multisig
value: ethers.utils.parseEther("0.008"),
value: ethers.parseEther("0.008"),
nonce: await wallet.getNonce(),
})
).wait();

//Get the balance in the Multisig account
let multisigBalance = await provider.getBalance(multisigAddress);

console.log(`Multisig account balance is ${multisigBalance.toString()}`);
Expand All @@ -962,8 +970,8 @@ console.log(`Multisig account balance is ${multisigBalance.toString()}`);
Now we can try to deploy a new multisig; the initiator of the transaction will be our deployed account from the previous part.

```ts
// The transaction to deploy a new account using the multisig we just deployed
let aaTx = await aaFactory.populateTransaction.deployAccount(
// Transaction to deploy a new account using the multisig we just deployed
let aaTx = await aaFactory.deployAccount.populateTransaction(
salt,
// These are accounts that will own the newly deployed account
Wallet.createRandom().address,
Expand All @@ -974,7 +982,7 @@ let aaTx = await aaFactory.populateTransaction.deployAccount(
Then, we need to fill all the transaction fields:

```ts
const gasLimit = await provider.estimateGas(aaTx);
const gasLimit = await provider.estimateGas({ ...aaTx, from: wallet.address });
const gasPrice = await provider.getGasPrice();

aaTx = {
Expand All @@ -989,7 +997,7 @@ aaTx = {
customData: {
gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
} as types.Eip712Meta,
value: ethers.BigNumber.from(0),
value: 0n,
};
```

Expand All @@ -1005,12 +1013,8 @@ Then, we need to sign the transaction and provide the `aaParamas` in the customD
```ts
const signedTxHash = EIP712Signer.getSignedDigest(aaTx);

const signature = ethers.utils.concat([
// Note, that `signMessage` wouldn't work here, since we don't want
// the signed hash to be prefixed with `\x19Ethereum Signed Message:\n`
ethers.utils.joinSignature(owner1._signingKey().signDigest(signedTxHash)),
ethers.utils.joinSignature(owner2._signingKey().signDigest(signedTxHash)),
]);
// Sign the transaction with both owners
const signature = ethers.concat([ethers.Signature.from(owner1.signingKey.sign(signedTxHash)).serialized, ethers.Signature.from(owner2.signingKey.sign(signedTxHash)).serialized]);

aaTx.customData = {
...aaTx.customData,
Expand All @@ -1022,7 +1026,9 @@ Finally, we are ready to send the transaction:

```ts
console.log(`The multisig's nonce before the first tx is ${await provider.getTransactionCount(multisigAddress)}`);
const sentTx = await provider.sendTransaction(utils.serialize(aaTx));

const sentTx = await provider.broadcastTransaction(types.Transaction.from(aaTx).serialized);
console.log(`Transaction sent from multisig with hash ${sentTx.hash}`);
await sentTx.wait();

// Checking that the nonce for the account has increased
Expand All @@ -1043,12 +1049,13 @@ import * as ethers from "ethers";
import { HardhatRuntimeEnvironment } from "hardhat/types";

// Put the address of your AA factory
const AA_FACTORY_ADDRESS = "<FACTORY-ADDRESS>";
const AA_FACTORY_ADDRESS = "<FACTORY-ADDRESS>"; //sepolia

export default async function (hre: HardhatRuntimeEnvironment) {
const provider = new Provider("https://sepolia.era.zksync.dev");
// Private key of the account used to deploy
const wallet = new Wallet("<WALLET-PRIVATE-KEY>").connect(provider);

const factoryArtifact = await hre.artifacts.readArtifact("AAFactory");

const aaFactory = new ethers.Contract(AA_FACTORY_ADDRESS, factoryArtifact.abi, wallet);
Expand All @@ -1058,15 +1065,16 @@ export default async function (hre: HardhatRuntimeEnvironment) {
const owner2 = Wallet.createRandom();

// For the simplicity of the tutorial, we will use zero hash as salt
const salt = ethers.constants.HashZero;
const salt = ethers.ZeroHash;

// deploy account owned by owner1 & owner2
const tx = await aaFactory.deployAccount(salt, owner1.address, owner2.address);
await tx.wait();

// Getting the address of the deployed contract account
// Always use the JS utility methods
const abiCoder = new ethers.utils.AbiCoder();
const abiCoder = new ethers.AbiCoder();

const multisigAddress = utils.create2Address(
AA_FACTORY_ADDRESS,
await aaFactory.aaBytecodeHash(),
Expand All @@ -1081,7 +1089,8 @@ export default async function (hre: HardhatRuntimeEnvironment) {
await wallet.sendTransaction({
to: multisigAddress,
// You can increase the amount of ETH sent to the multisig
value: ethers.utils.parseEther("0.008"),
value: ethers.parseEther("0.008"),
nonce: await wallet.getNonce(),
})
).wait();

Expand All @@ -1090,14 +1099,14 @@ export default async function (hre: HardhatRuntimeEnvironment) {
console.log(`Multisig account balance is ${multisigBalance.toString()}`);

// Transaction to deploy a new account using the multisig we just deployed
let aaTx = await aaFactory.populateTransaction.deployAccount(
let aaTx = await aaFactory.deployAccount.populateTransaction(
salt,
// These are accounts that will own the newly deployed account
Wallet.createRandom().address,
Wallet.createRandom().address
);

const gasLimit = await provider.estimateGas(aaTx);
const gasLimit = await provider.estimateGas({ ...aaTx, from: wallet.address });
const gasPrice = await provider.getGasPrice();

aaTx = {
Expand All @@ -1112,24 +1121,24 @@ export default async function (hre: HardhatRuntimeEnvironment) {
customData: {
gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
} as types.Eip712Meta,
value: ethers.BigNumber.from(0),
value: 0n,
};

const signedTxHash = EIP712Signer.getSignedDigest(aaTx);

const signature = ethers.utils.concat([
// Note, that `signMessage` wouldn't work here, since we don't want
// the signed hash to be prefixed with `\x19Ethereum Signed Message:\n`
ethers.utils.joinSignature(owner1._signingKey().signDigest(signedTxHash)),
ethers.utils.joinSignature(owner2._signingKey().signDigest(signedTxHash)),
]);
// Sign the transaction with both owners
const signature = ethers.concat([ethers.Signature.from(owner1.signingKey.sign(signedTxHash)).serialized, ethers.Signature.from(owner2.signingKey.sign(signedTxHash)).serialized]);

aaTx.customData = {
...aaTx.customData,
customSignature: signature,
};

console.log(`The multisig's nonce before the first tx is ${await provider.getTransactionCount(multisigAddress)}`);
const sentTx = await provider.sendTransaction(utils.serialize(aaTx));

const sentTx = await provider.broadcastTransaction(types.Transaction.from(aaTx).serialized);
console.log(`Transaction sent from multisig with hash ${sentTx.hash}`);

await sentTx.wait();

// Checking that the nonce for the account has increased
Expand All @@ -1150,9 +1159,13 @@ yarn hardhat deploy-zksync --script deploy-multisig.ts
The output should look something like this:

```txt
Multisig deployed on address 0xCEBc59558938bccb43A6C94769F87bBdb770E956
Multisig account deployed on address 0x4A1e5F7AeA6830372dCa584cbFaaBa1F21298a01
Sending funds to multisig account
Multisig account balance is 8000000000000000
The multisig's nonce before the first tx is 0
Transaction sent from multisig with hash 0xe91a8665f6777aa3c003844d9d0971a3b3ebbe1f8f3e0d941a3bc797963c7cca
The multisig's nonce after the first tx is 1
Multisig account balance is now 7962859900000000
```

::: tip
Expand Down
Loading

0 comments on commit 928d995

Please sign in to comment.