diff --git a/content/00.build/40.tooling/20.hardhat/20.guides/10.getting-started.md b/content/00.build/40.tooling/20.hardhat/20.guides/10.getting-started.md index 20e1d1f1..6e8e180d 100644 --- a/content/00.build/40.tooling/20.hardhat/20.guides/10.getting-started.md +++ b/content/00.build/40.tooling/20.hardhat/20.guides/10.getting-started.md @@ -56,58 +56,37 @@ The ZKsync Era deployment and compiler plugin imports: #### Solidity project ```ts -import "@matterlabs/hardhat-zksync"; +:code-import{filePath="hardhat-sol-template/hardhat.config.ts:zksync-import"} ``` The `zksolc` block contains the minimal configuration for the compiler. ```ts -zksolc: { - version: "latest", // Uses latest available in %%zk_git_repo_zksolc-bin%% - settings: {}, -}, +:code-import{filePath="hardhat-sol-template/hardhat.config.ts:zksolc"} ``` #### Vyper project ```ts -import "@nomiclabs/hardhat-vyper"; -import "@matterlabs/hardhat-zksync-deploy"; -import "@matterlabs/hardhat-zksync-vyper"; +:code-import{filePath="hardhat-vyper-template/hardhat.config.ts:zksync-vyper-import"} ``` The `zkvyper` block contains the minimal configuration for the compiler. ```ts -zkvyper: { - version: "latest", // Uses latest available in %%zk_git_repo_zkvyper-bin%% - settings: {}, -}, +:code-import{filePath="hardhat-vyper-template/hardhat.config.ts:zkvyper"} ``` #### Network -The network endpoints of the `zkSyncTestnet` network change dynamically for local tests. +The default network is set to `zkSyncSepoliaTestnet`, but mainnet and local test node networks are also configured. ```ts -// dynamically changes endpoints for local tests -const zkSyncTestnet = - process.env.NODE_ENV == "test" - ? { - url: "http://localhost:3050", - ethNetwork: "http://localhost:8545", - zksync: true, - } - : { - url: "%%zk_testnet_rpc_url%%", - ethNetwork: "%%zk_testnet_identifier%%", - zksync: true, - }; +:code-import{filePath="hardhat-sol-template/hardhat.config.ts:networks"} ``` ::callout{icon="i-heroicons-information-circle" color="blue"} -For local ZKsync testing, modify `url` and `ethNetwork` in `hardhat.config.ts` -to align with your local ZKsync and Ethereum node's L2 and L1 RPC URLs, respectively. +For local ZKsync testing, modify the default network name. :: ::callout{icon="i-heroicons-information-circle" color="blue"} @@ -130,10 +109,26 @@ Smart contracts belong in the `contracts` folder. #### 1. To compile the contract, run -```sh -yarn hardhat compile +::code-group + +```bash [npm] +npm run compile ``` +```bash [yarn] +yarn compile +``` + +```bash [pnpm] +pnpm compile +``` + +```bash [bun] +bun compile +``` + +:: + You'll see the following output: ```text @@ -150,70 +145,34 @@ These folders contain the compilation artifacts (including contract's ABIs) and The `artifacts-zk` and `cache-zk` folders are included in the `.gitignore` file. :: -The `deploy-greeter.ts` script is in the `deploy` folder. -This script uses the `Deployer` class from the `hardhat-zksync-deploy` package to deploy the `Greeter.sol`/`Greeter.vy` contract. +In the `deploy` folder, the `deploy.ts` script shows an example of how to deploy the `Greeter.sol`/`Greeter.vy` contract. ```ts -import { Wallet, utils } from "zksync-ethers"; -import * as ethers from "ethers"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; - -// load env file -import dotenv from "dotenv"; -dotenv.config(); - -// load wallet private key from env file -const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; - -if (!PRIVATE_KEY) throw "⛔️ Private key not detected! Add it to the .env file!"; - -// An example of a deploy script that will deploy and call a simple contract. -export default async function (hre: HardhatRuntimeEnvironment) { - console.log(`Running deploy script for the Greeter contract`); - - // Initialize the wallet. - const wallet = new Wallet(PRIVATE_KEY); - - // Create deployer object and load the artifact of the contract you want to deploy. - const deployer = new Deployer(hre, wallet); - const artifact = await deployer.loadArtifact("Greeter"); - - // Estimate contract deployment fee - const greeting = "Hi there!"; - const deploymentFee = await deployer.estimateDeployFee(artifact, [greeting]); - - // ⚠️ OPTIONAL: You can skip this block if your account already has funds in L2 - // const depositHandle = await deployer.zkWallet.deposit({ - // to: deployer.zkWallet.address, - // token: utils.ETH_ADDRESS, - // amount: deploymentFee.mul(2), - // }); - // // Wait until the deposit is processed on ZKsync - // await depositHandle.wait(); - - // Deploy this contract. The returned object will be of a `Contract` type, similar to ones in `ethers`. - // `greeting` is an argument for contract constructor. - const parsedFee = ethers.formatEther(deploymentFee); - console.log(`The deployment is estimated to cost ${parsedFee} ETH`); - - const greeterContract = await deployer.deploy(artifact, [greeting]); - - // obtain the Constructor Arguments - console.log("constructor args:" + greeterContract.interface.encodeDeploy([greeting])); - - // Show the contract info. - const contractAddress = await greeterContract.getAddress(); - console.log(`${artifact.contractName} was deployed to ${contractAddress}`); -} +:code-import{filePath="hardhat-sol-template/deploy/deploy.ts"} ``` #### 2. To execute the deployment script run -```sh -yarn hardhat deploy-zksync --script deploy-greeter.ts +::code-group + +```bash [npm] +npm run deploy +``` + +```bash [yarn] +yarn deploy +``` + +```bash [pnpm] +pnpm deploy +``` + +```bash [bun] +bun run deploy ``` +:: + This script deploys the `Greeting` contract with the message "Hi there!" to %%zk_testnet_name%%. You should see something like this: @@ -241,64 +200,34 @@ Congratulations! You have deployed a smart contract project to %%zk_testnet_name The template project contains another script to interact with the contract. -1. Enter the address of the deployed Greeter contract in the `CONTRACT_ADDRESS` variable of the `use-greeter.ts` script: - - ```ts [use-greeter.ts] - import { Provider } from "zksync-ethers"; - import * as ethers from "ethers"; - import { HardhatRuntimeEnvironment } from "hardhat/types"; - - // load env file - import dotenv from "dotenv"; - dotenv.config(); - - // load contract artifact. Make sure to compile first! - Solidity Project - import * as ContractArtifact from "../artifacts-zk/contracts/Greeter.sol/Greeter.json"; - // load contract artifact. Make sure to compile first! - Vyper Project - //import * as ContractArtifact from "../artifacts-zk/contracts/Greeter.vy/Greeter.json"; - - const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; - - if (!PRIVATE_KEY) throw "⛔️ Private key not detected! Add it to the .env file!"; - - // Address of the contract on ZKsync testnet - const CONTRACT_ADDRESS = ""; - - if (!CONTRACT_ADDRESS) throw "⛔️ Contract address not provided"; +1. Enter the address of the deployed Greeter contract in the `CONTRACT_ADDRESS` variable of the `interact.ts` script: - // An example of a deploy script that will deploy and call a simple contract. - export default async function (hre: HardhatRuntimeEnvironment) { - console.log(`Running script to interact with contract ${CONTRACT_ADDRESS}`); - - // Initialize the provider. - // @ts-ignore - const provider = new Provider(hre.userConfig.networks?.zkSyncTestnet?.url); - const signer = new ethers.Wallet(PRIVATE_KEY, provider); - - // Initialise contract instance - const contract = new ethers.Contract(CONTRACT_ADDRESS, ContractArtifact.abi, signer); + ```ts [interact.ts] + :code-import{filePath="hardhat-sol-template/deploy/interact.ts"} + ``` - // Read message from contract - console.log(`The message is ${await contract.greet()}`); +1. To execute the script, run: - // send transaction to update the message - const newMessage = "Hello people!"; - const tx = await contract.setGreeting(newMessage); + ::code-group - console.log(`Transaction to change the message is ${tx.hash}`); - await tx.wait(); + ```bash [npm] + npm run interact + ``` - // Read message after transaction - console.log(`The message now is ${await contract.greet()}`); - } + ```bash [yarn] + yarn interact ``` -1. To execute the script, run: + ```bash [pnpm] + pnpm interact + ``` - ```sh - yarn hardhat deploy-zksync --script use-greeter.ts + ```bash [bun] + bun interact ``` + :: + The script will: - Retrieve the message from the contract by calling the `greet()` method. @@ -320,7 +249,7 @@ The template project contains another script to interact with the contract. - To learn more about the ZKsync Hardhat plugins check out the [plugins documentation](/build/tooling/hardhat/guides/getting-started). - If you want to know more about how to interact with ZKsync using Javascript, -check out the [zksync-ethers Javascript SDK documentation](https://sdk.zksync.io/js/ethers). +check out the [`zksync-ethers` Javascript SDK documentation](https://sdk.zksync.io/js/ethers). ::callout{icon="i-heroicons-light-bulb"} Check the [installation guide](/build/tooling/hardhat/installation) for instructions! diff --git a/content/00.build/40.tooling/20.hardhat/20.guides/20.migrating-to-zksync.md b/content/00.build/40.tooling/20.hardhat/20.guides/20.migrating-to-zksync.md index fbed8175..f0a45aed 100644 --- a/content/00.build/40.tooling/20.hardhat/20.guides/20.migrating-to-zksync.md +++ b/content/00.build/40.tooling/20.hardhat/20.guides/20.migrating-to-zksync.md @@ -44,7 +44,7 @@ You can learn more about each plugin in the [Getting started section](/build/too ```ts // Other Hardhat plugin imports above - import "@matterlabs/hardhat-zksync"; + :code-import{filePath="hardhat-sol-template/hardhat.config.ts:zksync-import"} ``` 3. Remove conflicting plugin imports @@ -58,38 +58,13 @@ You can learn more about each plugin in the [Getting started section](/build/too // import "@nomicfoundation/hardhat-toolbox" // import "@nomicfoundation/hardhat-ethers" // import "@openzeppelin/hardhat-upgrades" - import "@matterlabs/hardhat-zksync" + :code-import{filePath="hardhat-sol-template/hardhat.config.ts:zksync-import"} ``` 4. Add the preferred ZKsync networks to the `hardhat.config.ts` file: - ```js - networks: { - zkSyncSepoliaTestnet: { - url: "https://sepolia.era.zksync.dev", - ethNetwork: "sepolia", - zksync: true, - verifyURL: "https://explorer.sepolia.era.zksync.dev/contract_verification", - }, - zkSyncMainnet: { - url: "https://mainnet.era.zksync.io", - ethNetwork: "mainnet", - zksync: true, - verifyURL: "https://zksync2-mainnet-explorer.zksync.io/contract_verification", - }, - dockerizedNode: { - url: "http://localhost:3050", - ethNetwork: "http://localhost:8545", - zksync: true, - }, - inMemoryNode: { - url: "http://127.0.0.1:8011", - ethNetwork: "localhost", - zksync: true, - }, - // Other networks - - } + ```ts + :code-import{filePath="hardhat-sol-template/hardhat.config.ts:networks"} ``` ::callout{icon="i-heroicons-light-bulb"} @@ -201,9 +176,8 @@ The command will add the libraries and their addresses to the `hardhat.config.ts 1. Now you can compile the main contract that imports the libraries and deploy the contract without the need to reference the libraries: -```typescript [ZKsync non-inlineable libraries] -const mainContract = await hre.ethers.deployContract("MainContract") -await mainContract.waitForDeployment(); +```ts [deploy.ts] +:code-import{filePath="hardhat-sol/deploy/deploy-main.ts"} ``` ### Troubleshoting @@ -244,17 +218,13 @@ To run tests on the Dockerized local setup, follow these steps: 1. Run `npx zksync-cli dev start` to start the L1 and L2 nodes. 1. Add the Dockerized nodes to the list of networks in the `hardhat.config.ts` file: - ```bash - networks: { - dockerizedNode: { - url: "http://localhost:3050", - ethNetwork: "http://localhost:8545", - zksync: true, - }, - // Other networks - - } - ``` +```ts [hardhat.config.ts] +networks: { +:code-import{filePath="hardhat-sol/hardhat.config.ts:dockerizedNode"} + // Other networks + +} +``` 1. Make sure the providers in your test files target the correct url. 1. Run the test task with `npx hardhat test --network dockerizedNode`. @@ -282,14 +252,12 @@ See below examples for `hardhat-ethers` and `hardhat-zksync-ethers`: ::code-group -```typescript [hardhat-ethers] -const greeter = await hre.ethers.deployContract('Greeter', ['Hi there!']); -await greeter.waitForDeployment(); +```ts [hardhat-ethers] +:code-import{filePath="hardhat-sol-template/deploy/deploy-greeter.ts:deploy"} ``` -```typescript [hardhat-zksync-ethers] -const greeter = await hre.ethers.deployContract('Greeter', ['Hi there!']); -await greeter.waitForDeployment(); +```ts [hardhat-zksync-ethers] +:code-import{filePath="hardhat-sol-template/deploy/deploy-greeter.ts:deploy"} ``` :: @@ -298,16 +266,12 @@ When a custom deployment is needed, use `ContractFactory`. ::code-group -```typescript [hardhat-ethers] -const GreeterFactory = await hre.ethers.getContractFactory('Greeter'); -const greeterContract = GreeterFactory.deploy(); // if any, pass constructor arguments in deploy arguments -await greeter.waitForDeployment(); +```ts [hardhat-ethers] +:code-import{filePath="hardhat-sol-template/deploy/deploy-greeter.ts:deploy-factory"} ``` -```typescript [hardhat-zksync-ethers] -const GreeterFactory = await hre.ethers.getContractFactory('Greeter'); -const greeterContract = GreeterFactory.deploy(); // if any, pass constructor arguments in deploy arguments -await greeter.waitForDeployment(); +```ts [hardhat-zksync-ethers] +:code-import{filePath="hardhat-sol-template/deploy/deploy-greeter.ts:deploy-factory"} ``` :: diff --git a/content/00.build/40.tooling/20.hardhat/20.guides/40.compiling-libraries.md b/content/00.build/40.tooling/20.hardhat/20.guides/40.compiling-libraries.md index f3245f88..f7e0b01c 100644 --- a/content/00.build/40.tooling/20.hardhat/20.guides/40.compiling-libraries.md +++ b/content/00.build/40.tooling/20.hardhat/20.guides/40.compiling-libraries.md @@ -28,31 +28,14 @@ This section describes the compilation of non-inlinable libraries only. Let's say that we have a small library that calculates the square of a number: -```solidity -pragma solidity ^0.8.0; - -library MiniMath { - function square(uint256 x) public pure returns (uint256) { - return x*x; - } -} +```ts [MiniMath.sol] +:code-import{filePath="hardhat-sol/contracts/MiniMath/MiniMath.sol"} ``` -And there is a smart contract that uses this library +And there is a smart contract that uses this library: -```solidity -pragma solidity ^0.8.0; - -import "./MiniMath.sol"; - -contract Main { - uint256 public lastNumber; - - function storeSquare(uint256 x) public { - uint256 square = MiniMath.square(x); - lastNumber = square; - } -} +```ts [Main.sol] +:code-import{filePath="hardhat-sol/contracts/MiniMath/Main.sol"} ``` ::callout{icon="i-heroicons-exclamation-triangle" color="amber"} @@ -109,49 +92,14 @@ After deploying _only_ the library to ZKsync Era, you should get the address of The process of deploying the library is the same as deploying a smart contract. You can learn how to deploy smart contracts on ZKsync Era in the [getting started](getting-started#compile-and-deploy-a-contract) guide. -Let's say that the address of the deployed library is `0xF9702469Dfb84A9aC171E284F71615bd3D3f1EdC`. +Let's say that the address of the deployed library is `0x111C3E89Ce80e62EE88318C2804920D4c96f92bb`. To pass this address to the compiler parameters, open the `hardhat.config.ts` file of the project where the `Main` contract is located and add the `libraries` section in the `zksolc` plugin properties: -```typescript -import "@matterlabs/hardhat-zksync-deploy"; -import "@matterlabs/hardhat-zksync-solc"; - -module.exports = { - zksolc: { - version: "latest", // Uses latest available in %%zk_git_repo_zksolc-bin%% - settings: { - libraries: { - "contracts/MiniMath.sol": { - MiniMath: "0xF9702469Dfb84A9aC171E284F71615bd3D3f1EdC", - }, - }, - }, - }, - defaultNetwork: "zkTestnet", - networks: { - zkTestnet: { - url: "%%zk_testnet_rpc_url%%", // URL of the ZKsync network RPC - ethNetwork: "%%zk_testnet_identifier%%", // Can also be the RPC URL of the Ethereum network (e.g. `https://sepolia.infura.io/v3/`) - zksync: true, - }, - }, - solidity: { - version: "0.8.13", - }, -}; -``` - -The address of the library is passed in the following lines: - -```typescript -libraries: { - 'contracts/MiniMath.sol': { - 'MiniMath': '0xF9702469Dfb84A9aC171E284F71615bd3D3f1EdC' - } -}, +```ts [hardhat.config.ts] +:code-import{filePath="hardhat-sol/hardhat.config.ts:zksolc"} ``` -where `'contracts/MiniMath.sol'` is the location of the library's Solidity file and `MiniMath` is the name of the library. +where `'contracts/MiniMath/MiniMath.sol'` is the location of the library's Solidity file and `MiniMath` is the name of the library. Now, running `yarn hardhat compile` should successfully compile the `Main` contract. diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 00000000..a5e8bdb6 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,114 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.vscode + +# hardhat artifacts +artifacts +cache + +# zksync artifacts +artifacts-zk +cache-zk +deployments-zk + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port diff --git a/examples/LICENSE b/examples/LICENSE new file mode 100644 index 00000000..7fff3d4e --- /dev/null +++ b/examples/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Matter Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/hardhat-sol-template/.env.example b/examples/hardhat-sol-template/.env.example new file mode 100644 index 00000000..c65faa8c --- /dev/null +++ b/examples/hardhat-sol-template/.env.example @@ -0,0 +1 @@ +WALLET_PRIVATE_KEY= diff --git a/examples/hardhat-sol-template/.npmrc b/examples/hardhat-sol-template/.npmrc new file mode 100644 index 00000000..521a9f7c --- /dev/null +++ b/examples/hardhat-sol-template/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/examples/hardhat-sol-template/bun.lockb b/examples/hardhat-sol-template/bun.lockb new file mode 100755 index 00000000..c304d048 Binary files /dev/null and b/examples/hardhat-sol-template/bun.lockb differ diff --git a/examples/hardhat-sol-template/contracts/Greeter.sol b/examples/hardhat-sol-template/contracts/Greeter.sol new file mode 100644 index 00000000..5496798f --- /dev/null +++ b/examples/hardhat-sol-template/contracts/Greeter.sol @@ -0,0 +1,18 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +contract Greeter { + string private greeting; + + constructor(string memory _greeting) { + greeting = _greeting; + } + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + } +} diff --git a/examples/hardhat-sol-template/deploy/deploy-greeter.ts b/examples/hardhat-sol-template/deploy/deploy-greeter.ts new file mode 100644 index 00000000..7302fdc8 --- /dev/null +++ b/examples/hardhat-sol-template/deploy/deploy-greeter.ts @@ -0,0 +1,14 @@ +import * as hre from 'hardhat'; + +export default async function () { + // ANCHOR: deploy + const greeter = await hre.ethers.deployContract('Greeter', ['Hi there!']); + await greeter.waitForDeployment(); + // ANCHOR_END: deploy + + // ANCHOR: deploy-factory + const GreeterFactory = await hre.ethers.getContractFactory('Greeter'); + const greeterContract = await GreeterFactory.deploy('Hi there!'); // if any, pass constructor arguments in deploy arguments + await greeterContract.waitForDeployment(); + // ANCHOR_END: deploy-factory +} diff --git a/examples/hardhat-sol-template/deploy/deploy.ts b/examples/hardhat-sol-template/deploy/deploy.ts new file mode 100644 index 00000000..ae90fbc0 --- /dev/null +++ b/examples/hardhat-sol-template/deploy/deploy.ts @@ -0,0 +1,10 @@ +import { deployContract } from './utils'; + +// An example of a basic deploy script +// It will deploy a Greeter contract to selected network +// as well as verify it on Block Explorer if possible for the network +export default async function () { + const contractArtifactName = 'Greeter'; + const constructorArguments = ['Hi there!']; + await deployContract(contractArtifactName, constructorArguments); +} diff --git a/examples/hardhat-sol-template/deploy/interact.ts b/examples/hardhat-sol-template/deploy/interact.ts new file mode 100644 index 00000000..53a58466 --- /dev/null +++ b/examples/hardhat-sol-template/deploy/interact.ts @@ -0,0 +1,36 @@ +import * as hre from 'hardhat'; +import { getWallet } from './utils'; +import { ethers } from 'ethers'; + +// Address of the contract to interact with +const CONTRACT_ADDRESS = ''; +if (!CONTRACT_ADDRESS) throw '⛔️ Provide address of the contract to interact with!'; + +// An example of a script to interact with the contract +export default async function () { + console.log(`Running script to interact with contract ${CONTRACT_ADDRESS}`); + + // Load compiled contract info + const contractArtifact = await hre.artifacts.readArtifact('Greeter'); + + // Initialize contract instance for interaction + const contract = new ethers.Contract( + CONTRACT_ADDRESS, + contractArtifact.abi, + getWallet() // Interact with the contract on behalf of this wallet + ); + + // Run contract read function + const response = await contract.greet(); + console.log(`Current message is: ${response}`); + + // Run contract write function + const transaction = await contract.setGreeting('Hello people!'); + console.log(`Transaction hash of setting new message: ${transaction.hash}`); + + // Wait until transaction is processed + await transaction.wait(); + + // Read message after transaction + console.log(`The message now is: ${await contract.greet()}`); +} diff --git a/examples/hardhat-sol-template/deploy/utils.ts b/examples/hardhat-sol-template/deploy/utils.ts new file mode 100644 index 00000000..353d5163 --- /dev/null +++ b/examples/hardhat-sol-template/deploy/utils.ts @@ -0,0 +1,175 @@ +import { Provider, Wallet } from 'zksync-ethers'; +import * as hre from 'hardhat'; +import { Deployer } from '@matterlabs/hardhat-zksync'; +import dotenv from 'dotenv'; +import { ethers } from 'ethers'; + +import '@matterlabs/hardhat-zksync-node/dist/type-extensions'; +import '@matterlabs/hardhat-zksync-verify/dist/src/type-extensions'; + +// Load env file +dotenv.config(); + +export const getProvider = () => { + const rpcUrl = hre.network.config.url; + if (!rpcUrl) + throw `⛔️ RPC URL wasn't found in "${hre.network.name}"! Please add a "url" field to the network config in hardhat.config.ts`; + + // Initialize ZKsync Provider + const provider = new Provider(rpcUrl); + + return provider; +}; + +export const getWallet = (privateKey?: string) => { + if (!privateKey) { + // Get wallet private key from .env file + if (!process.env.WALLET_PRIVATE_KEY) throw "⛔️ Wallet private key wasn't found in .env file!"; + } + + const provider = getProvider(); + + // Initialize ZKsync Wallet + const wallet = new Wallet(privateKey ?? process.env.WALLET_PRIVATE_KEY!, provider); + + return wallet; +}; + +export const verifyEnoughBalance = async (wallet: Wallet, amount: bigint) => { + // Check if the wallet has enough balance + const balance = await wallet.getBalance(); + if (balance < amount) + throw `⛔️ Wallet balance is too low! Required ${ethers.formatEther(amount)} ETH, but current ${wallet.address} balance is ${ethers.formatEther(balance)} ETH`; +}; + +/** + * @param {string} data.contract The contract's path and name. E.g., "contracts/Greeter.sol:Greeter" + */ +export const verifyContract = async (data: { + address: string; + contract: string; + constructorArguments: string; + bytecode: string; +}) => { + const verificationRequestId: number = await hre.run('verify:verify', { + ...data, + noCompile: true, + }); + return verificationRequestId; +}; + +type DeployContractOptions = { + /** + * If true, the deployment process will not print any logs + */ + silent?: boolean; + /** + * If true, the contract will not be verified on Block Explorer + */ + noVerify?: boolean; + /** + * If specified, the contract will be deployed using this wallet + */ + wallet?: Wallet; +}; +export const deployContract = async ( + contractArtifactName: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructorArguments?: any[], + options?: DeployContractOptions +) => { + const log = (message: string) => { + if (!options?.silent) console.log(message); + }; + + log(`\nStarting deployment process of "${contractArtifactName}"...`); + + const wallet = options?.wallet ?? getWallet(); + const deployer = new Deployer(hre, wallet); + const artifact = await deployer.loadArtifact(contractArtifactName).catch((error) => { + if (error?.message?.includes(`Artifact for contract "${contractArtifactName}" not found.`)) { + console.error(error.message); + throw `⛔️ Please make sure you have compiled your contracts or specified the correct contract name!`; + } else { + throw error; + } + }); + + // Estimate contract deployment fee + const deploymentFee = await deployer.estimateDeployFee(artifact, constructorArguments || []); + log(`Estimated deployment cost: ${ethers.formatEther(deploymentFee)} ETH`); + + // Check if the wallet has enough balance + await verifyEnoughBalance(wallet, deploymentFee); + + // Deploy the contract to ZKsync + const contract = await deployer.deploy(artifact, constructorArguments); + const address = await contract.getAddress(); + const constructorArgs = contract.interface.encodeDeploy(constructorArguments); + const fullContractSource = `${artifact.sourceName}:${artifact.contractName}`; + + // Display contract deployment info + log(`\n"${artifact.contractName}" was successfully deployed:`); + log(` - Contract address: ${address}`); + log(` - Contract source: ${fullContractSource}`); + log(` - Encoded constructor arguments: ${constructorArgs}\n`); + + if (!options?.noVerify && hre.network.config.verifyURL) { + log(`Requesting contract verification...`); + await verifyContract({ + address, + contract: fullContractSource, + constructorArguments: constructorArgs, + bytecode: artifact.bytecode, + }); + } + + return contract; +}; + +/** + * Rich wallets can be used for testing purposes. + * Available on ZKsync In-memory node and Dockerized node. + */ +export const LOCAL_RICH_WALLETS = [ + { + address: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', + privateKey: '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110', + }, + { + address: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + privateKey: '0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3', + }, + { + address: '0x0D43eB5B8a47bA8900d84AA36656c92024e9772e', + privateKey: '0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e', + }, + { + address: '0xA13c10C0D5bd6f79041B9835c63f91de35A15883', + privateKey: '0x850683b40d4a740aa6e745f889a6fdc8327be76e122f5aba645a5b02d0248db8', + }, + { + address: '0x8002cD98Cfb563492A6fB3E7C8243b7B9Ad4cc92', + privateKey: '0xf12e28c0eb1ef4ff90478f6805b68d63737b7f33abfa091601140805da450d93', + }, + { + address: '0x4F9133D1d3F50011A6859807C837bdCB31Aaab13', + privateKey: '0xe667e57a9b8aaa6709e51ff7d093f1c5b73b63f9987e4ab4aa9a5c699e024ee8', + }, + { + address: '0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA', + privateKey: '0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959', + }, + { + address: '0xedB6F5B4aab3dD95C7806Af42881FF12BE7e9daa', + privateKey: '0x74d8b3a188f7260f67698eb44da07397a298df5427df681ef68c45b34b61f998', + }, + { + address: '0xe706e60ab5Dc512C36A4646D719b889F398cbBcB', + privateKey: '0xbe79721778b48bcc679b78edac0ce48306a8578186ffcb9f2ee455ae6efeace1', + }, + { + address: '0xE90E12261CCb0F3F7976Ae611A29e84a6A85f424', + privateKey: '0x3eb15da85647edd9a1159a4a13b9e7c56877c4eb33f614546d4db06a51868b1c', + }, +]; diff --git a/examples/hardhat-sol-template/hardhat.config.ts b/examples/hardhat-sol-template/hardhat.config.ts new file mode 100644 index 00000000..71dbad0f --- /dev/null +++ b/examples/hardhat-sol-template/hardhat.config.ts @@ -0,0 +1,47 @@ +import type { HardhatUserConfig } from 'hardhat/config'; +// ANCHOR: zksync-import +import '@matterlabs/hardhat-zksync'; +// ANCHOR_END: zksync-import +const config: HardhatUserConfig = { + // ANCHOR: networks + defaultNetwork: 'zkSyncSepoliaTestnet', + networks: { + zkSyncSepoliaTestnet: { + url: 'https://sepolia.era.zksync.dev', + ethNetwork: 'sepolia', + zksync: true, + verifyURL: 'https://explorer.sepolia.era.zksync.dev/contract_verification', + }, + zkSyncMainnet: { + url: 'https://mainnet.era.zksync.io', + ethNetwork: 'mainnet', + zksync: true, + verifyURL: 'https://zksync2-mainnet-explorer.zksync.io/contract_verification', + }, + dockerizedNode: { + url: 'http://localhost:3050', + ethNetwork: 'http://localhost:8545', + zksync: true, + }, + inMemoryNode: { + url: 'http://127.0.0.1:8011', + ethNetwork: 'localhost', // in-memory node doesn't support eth node; removing this line will cause an error + zksync: true, + }, + hardhat: { + zksync: true, + }, + }, + // ANCHOR_END: networks + // ANCHOR: zksolc + zksolc: { + version: 'latest', // Uses latest available in %%zk_git_repo_zksolc-bin%% + settings: {}, + }, + // ANCHOR_END: zksolc + solidity: { + version: '0.8.17', + }, +}; + +export default config; diff --git a/examples/hardhat-sol-template/package.json b/examples/hardhat-sol-template/package.json new file mode 100644 index 00000000..f17da7f2 --- /dev/null +++ b/examples/hardhat-sol-template/package.json @@ -0,0 +1,32 @@ +{ + "name": "zksync-hardhat-template", + "description": "A template for ZKsync smart contracts development with Hardhat", + "private": true, + "author": "Matter Labs", + "license": "MIT", + "repository": "https://github.com/matter-labs/zksync-hardhat-template.git", + "scripts": { + "deploy": "hardhat deploy-zksync --script deploy.ts", + "deploy:greeter": "hardhat deploy-zksync --script deploy-greeter.ts", + "interact": "hardhat deploy-zksync --script interact.ts", + "compile": "hardhat compile", + "clean": "hardhat clean", + "test": "hardhat test --network hardhat" + }, + "devDependencies": { + "@matterlabs/hardhat-zksync": "^1.1.0", + "@matterlabs/zksync-contracts": "^0.6.1", + "@openzeppelin/contracts": "^4.9.2", + "@nomicfoundation/hardhat-verify": "^2.0.9", + "@types/chai": "^4.3.16", + "@types/mocha": "^10.0.7", + "chai": "^4.5.0", + "dotenv": "^16.4.5", + "ethers": "^6.13.2", + "hardhat": "^2.22.7", + "mocha": "^10.7.0", + "ts-node": "^10.9.2", + "typescript": "^5.5.4", + "zksync-ethers": "^6.11.0" + } +} diff --git a/examples/hardhat-sol-template/test/greeter.test.ts b/examples/hardhat-sol-template/test/greeter.test.ts new file mode 100644 index 00000000..78ea99ad --- /dev/null +++ b/examples/hardhat-sol-template/test/greeter.test.ts @@ -0,0 +1,21 @@ +import { expect } from 'chai'; +import { getWallet, deployContract, LOCAL_RICH_WALLETS } from '../deploy/utils'; + +describe('Greeter', function () { + it("Should return the new greeting once it's changed", async function () { + const wallet = getWallet(LOCAL_RICH_WALLETS[0].privateKey); + + const greeting = 'Hello world!'; + const greeter = await deployContract('Greeter', [greeting], { wallet, silent: true }); + + expect(await greeter.greet()).to.eq(greeting); + + const newGreeting = 'Hola, mundo!'; + const setGreetingTx = await greeter.setGreeting(newGreeting); + + // wait until the transaction is processed + await setGreetingTx.wait(); + + expect(await greeter.greet()).to.equal(newGreeting); + }); +}); diff --git a/examples/hardhat-sol-template/tsconfig.json b/examples/hardhat-sol-template/tsconfig.json new file mode 100644 index 00000000..d366442a --- /dev/null +++ b/examples/hardhat-sol-template/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "resolveJsonModule": true + }, + "include": ["./hardhat.config.ts", "./scripts", "./deploy", "./test", "typechain/**/*"] +} diff --git a/examples/hardhat-sol/.env.example b/examples/hardhat-sol/.env.example new file mode 100644 index 00000000..c65faa8c --- /dev/null +++ b/examples/hardhat-sol/.env.example @@ -0,0 +1 @@ +WALLET_PRIVATE_KEY= diff --git a/examples/hardhat-sol/.npmrc b/examples/hardhat-sol/.npmrc new file mode 100644 index 00000000..521a9f7c --- /dev/null +++ b/examples/hardhat-sol/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/examples/hardhat-sol/bun.lockb b/examples/hardhat-sol/bun.lockb new file mode 100755 index 00000000..c304d048 Binary files /dev/null and b/examples/hardhat-sol/bun.lockb differ diff --git a/examples/hardhat-sol/contracts/MiniMath/Main.sol b/examples/hardhat-sol/contracts/MiniMath/Main.sol new file mode 100644 index 00000000..11f3fdf1 --- /dev/null +++ b/examples/hardhat-sol/contracts/MiniMath/Main.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./MiniMath.sol"; + +contract Main { + uint256 public lastNumber; + + function storeSquare(uint256 x) public { + uint256 square = MiniMath.square(x); + lastNumber = square; + } +} diff --git a/examples/hardhat-sol/contracts/MiniMath/MiniMath.sol b/examples/hardhat-sol/contracts/MiniMath/MiniMath.sol new file mode 100644 index 00000000..c564b567 --- /dev/null +++ b/examples/hardhat-sol/contracts/MiniMath/MiniMath.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library MiniMath { + function square(uint256 x) public pure returns (uint256) { + return x*x; + } +} diff --git a/examples/hardhat-sol/deploy/deploy-main.ts b/examples/hardhat-sol/deploy/deploy-main.ts new file mode 100644 index 00000000..1dff5081 --- /dev/null +++ b/examples/hardhat-sol/deploy/deploy-main.ts @@ -0,0 +1,9 @@ +import * as hre from 'hardhat'; + +export default async function () { + // ANCHOR: deploy + const mainContract = await hre.ethers.deployContract('Main'); + await mainContract.waitForDeployment(); + console.log('Main contract deployed to:', mainContract.target); + // ANCHOR_END: deploy +} diff --git a/examples/hardhat-sol/hardhat.config.ts b/examples/hardhat-sol/hardhat.config.ts new file mode 100644 index 00000000..8cb3f331 --- /dev/null +++ b/examples/hardhat-sol/hardhat.config.ts @@ -0,0 +1,52 @@ +import type { HardhatUserConfig } from 'hardhat/config'; +import '@matterlabs/hardhat-zksync'; + +const config: HardhatUserConfig = { + defaultNetwork: 'inMemoryNode', + networks: { + zkSyncSepoliaTestnet: { + url: 'https://sepolia.era.zksync.dev', + ethNetwork: 'sepolia', + zksync: true, + verifyURL: 'https://explorer.sepolia.era.zksync.dev/contract_verification', + }, + zkSyncMainnet: { + url: 'https://mainnet.era.zksync.io', + ethNetwork: 'mainnet', + zksync: true, + verifyURL: 'https://zksync2-mainnet-explorer.zksync.io/contract_verification', + }, + // ANCHOR: dockerizedNode + dockerizedNode: { + url: 'http://localhost:3050', + ethNetwork: 'http://localhost:8545', + zksync: true, + }, + // ANCHOR_END: dockerizedNode + inMemoryNode: { + url: 'http://127.0.0.1:8011', + ethNetwork: 'localhost', // in-memory node doesn't support eth node; removing this line will cause an error + zksync: true, + }, + hardhat: { + zksync: true, + }, + }, + // ANCHOR: zksolc + zksolc: { + version: 'latest', + settings: { + libraries: { + 'contracts/MiniMath/MiniMath.sol': { + MiniMath: '0x111C3E89Ce80e62EE88318C2804920D4c96f92bb', + }, + }, + }, + }, + // ANCHOR_END: zksolc + solidity: { + version: '0.8.17', + }, +}; + +export default config; diff --git a/examples/hardhat-sol/package.json b/examples/hardhat-sol/package.json new file mode 100644 index 00000000..23842432 --- /dev/null +++ b/examples/hardhat-sol/package.json @@ -0,0 +1,29 @@ +{ + "name": "zksync-hardhat-template", + "description": "A template for ZKsync smart contracts development with Hardhat", + "private": true, + "author": "Matter Labs", + "license": "MIT", + "repository": "https://github.com/matter-labs/zksync-hardhat-template.git", + "scripts": { + "deploy:main": "hardhat deploy-zksync --script deploy-main.ts", + "compile": "hardhat compile", + "clean": "hardhat clean" + }, + "devDependencies": { + "@matterlabs/hardhat-zksync": "^1.1.0", + "@matterlabs/zksync-contracts": "^0.6.1", + "@openzeppelin/contracts": "^4.9.2", + "@nomicfoundation/hardhat-verify": "^2.0.9", + "@types/chai": "^4.3.16", + "@types/mocha": "^10.0.7", + "chai": "^4.5.0", + "dotenv": "^16.4.5", + "ethers": "^6.13.2", + "hardhat": "^2.22.7", + "mocha": "^10.7.0", + "ts-node": "^10.9.2", + "typescript": "^5.5.4", + "zksync-ethers": "^6.11.0" + } +} diff --git a/examples/hardhat-sol/tsconfig.json b/examples/hardhat-sol/tsconfig.json new file mode 100644 index 00000000..d366442a --- /dev/null +++ b/examples/hardhat-sol/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "resolveJsonModule": true + }, + "include": ["./hardhat.config.ts", "./scripts", "./deploy", "./test", "typechain/**/*"] +} diff --git a/examples/hardhat-vyper-template/.env.example b/examples/hardhat-vyper-template/.env.example new file mode 100644 index 00000000..c65faa8c --- /dev/null +++ b/examples/hardhat-vyper-template/.env.example @@ -0,0 +1 @@ +WALLET_PRIVATE_KEY= diff --git a/examples/hardhat-vyper-template/bun.lockb b/examples/hardhat-vyper-template/bun.lockb new file mode 100755 index 00000000..8c16076d Binary files /dev/null and b/examples/hardhat-vyper-template/bun.lockb differ diff --git a/examples/hardhat-vyper-template/contracts/Greeter.vy b/examples/hardhat-vyper-template/contracts/Greeter.vy new file mode 100644 index 00000000..904c7138 --- /dev/null +++ b/examples/hardhat-vyper-template/contracts/Greeter.vy @@ -0,0 +1,17 @@ +# @version ^0.3.3 +# vim: ft=python + +greeting: public(String[100]) + +@external +def __init__(_greeting: String[100]): + self.greeting = _greeting + +@external +@view +def greet() -> String[100]: + return self.greeting + +@external +def set_greeting( _greet: String[100]): + self.greeting = _greet diff --git a/examples/hardhat-vyper-template/deploy/deploy.ts b/examples/hardhat-vyper-template/deploy/deploy.ts new file mode 100644 index 00000000..ae90fbc0 --- /dev/null +++ b/examples/hardhat-vyper-template/deploy/deploy.ts @@ -0,0 +1,10 @@ +import { deployContract } from './utils'; + +// An example of a basic deploy script +// It will deploy a Greeter contract to selected network +// as well as verify it on Block Explorer if possible for the network +export default async function () { + const contractArtifactName = 'Greeter'; + const constructorArguments = ['Hi there!']; + await deployContract(contractArtifactName, constructorArguments); +} diff --git a/examples/hardhat-vyper-template/deploy/interact.ts b/examples/hardhat-vyper-template/deploy/interact.ts new file mode 100644 index 00000000..d45ea5a4 --- /dev/null +++ b/examples/hardhat-vyper-template/deploy/interact.ts @@ -0,0 +1,36 @@ +import * as hre from 'hardhat'; +import { getWallet } from './utils'; +import { ethers } from 'ethers'; + +// Address of the contract to interact with +const CONTRACT_ADDRESS = ''; +if (!CONTRACT_ADDRESS) throw '⛔️ Provide address of the contract to interact with!'; + +// An example of a script to interact with the contract +export default async function () { + console.log(`Running script to interact with contract ${CONTRACT_ADDRESS}`); + + // Load compiled contract info + const contractArtifact = await hre.artifacts.readArtifact('Greeter'); + + // Initialize contract instance for interaction + const contract = new ethers.Contract( + CONTRACT_ADDRESS, + contractArtifact.abi, + getWallet() // Interact with the contract on behalf of this wallet + ); + + // Run contract read function + const response = await contract.greet(); + console.log(`Current message is: ${response}`); + + // Run contract write function + const transaction = await contract.set_greeting('Hello people!'); + console.log(`Transaction hash of setting new message: ${transaction.hash}`); + + // Wait until transaction is processed + await transaction.wait(); + + // Read message after transaction + console.log(`The message now is: ${await contract.greet()}`); +} diff --git a/examples/hardhat-vyper-template/deploy/utils.ts b/examples/hardhat-vyper-template/deploy/utils.ts new file mode 100644 index 00000000..aa936342 --- /dev/null +++ b/examples/hardhat-vyper-template/deploy/utils.ts @@ -0,0 +1,143 @@ +import { Provider, Wallet } from 'zksync-ethers'; +import * as hre from 'hardhat'; +import { Deployer } from '@matterlabs/hardhat-zksync-deploy'; +import dotenv from 'dotenv'; +import { ethers } from 'ethers'; + +import '@matterlabs/hardhat-zksync-node/dist/type-extensions'; + +// Load env file +dotenv.config(); + +export const getProvider = () => { + const rpcUrl = hre.network.config.url; + if (!rpcUrl) + throw `⛔️ RPC URL wasn't found in "${hre.network.name}"! Please add a "url" field to the network config in hardhat.config.ts`; + + // Initialize ZKsync Provider + const provider = new Provider(rpcUrl); + + return provider; +}; + +export const getWallet = (privateKey?: string) => { + if (!privateKey) { + // Get wallet private key from .env file + if (!process.env.WALLET_PRIVATE_KEY) throw "⛔️ Wallet private key wasn't found in .env file!"; + } + + const provider = getProvider(); + + // Initialize ZKsync Wallet + const wallet = new Wallet(privateKey ?? process.env.WALLET_PRIVATE_KEY!, provider); + + return wallet; +}; + +export const verifyEnoughBalance = async (wallet: Wallet, amount: bigint) => { + // Check if the wallet has enough balance + const balance = await wallet.getBalance(); + if (balance < amount) + throw `⛔️ Wallet balance is too low! Required ${ethers.formatEther(amount)} ETH, but current ${wallet.address} balance is ${ethers.formatEther(balance)} ETH`; +}; + +type DeployContractOptions = { + /** + * If true, the deployment process will not print any logs + */ + silent?: boolean; + /** + * If specified, the contract will be deployed using this wallet + */ + wallet?: Wallet; +}; +export const deployContract = async ( + contractArtifactName: string, + constructorArguments?: never[], + options?: DeployContractOptions +) => { + const log = (message: string) => { + if (!options?.silent) console.log(message); + }; + + log(`\nStarting deployment process of "${contractArtifactName}"...`); + + const wallet = options?.wallet ?? getWallet(); + const deployer = new Deployer(hre, wallet); + const artifact = await deployer.loadArtifact(contractArtifactName).catch((error) => { + if (error?.message?.includes(`Artifact for contract "${contractArtifactName}" not found.`)) { + console.error(error.message); + throw `⛔️ Please make sure you have compiled your contracts or specified the correct contract name!`; + } else { + throw error; + } + }); + + // Estimate contract deployment fee + const deploymentFee = await deployer.estimateDeployFee(artifact, constructorArguments || []); + log(`Estimated deployment cost: ${ethers.formatEther(deploymentFee)} ETH`); + + // Check if the wallet has enough balance + await verifyEnoughBalance(wallet, deploymentFee); + + // Deploy the contract to ZKsync + const contract = await deployer.deploy(artifact, constructorArguments); + const address = await contract.getAddress(); + const constructorArgs = contract.interface.encodeDeploy(constructorArguments); + const fullContractSource = `${artifact.sourceName}:${artifact.contractName}`; + + // Display contract deployment info + log(`\n"${artifact.contractName}" was successfully deployed:`); + log(` - Contract address: ${address}`); + log(` - Contract source: ${fullContractSource}`); + log(` - Encoded constructor arguments: ${constructorArgs}\n`); + + return contract; +}; + +/** + * Rich wallets can be used for testing purposes. + * Available on ZKsync In-memory node and Dockerized node. + */ +export const LOCAL_RICH_WALLETS = [ + { + address: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', + privateKey: '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110', + }, + { + address: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + privateKey: '0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3', + }, + { + address: '0x0D43eB5B8a47bA8900d84AA36656c92024e9772e', + privateKey: '0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e', + }, + { + address: '0xA13c10C0D5bd6f79041B9835c63f91de35A15883', + privateKey: '0x850683b40d4a740aa6e745f889a6fdc8327be76e122f5aba645a5b02d0248db8', + }, + { + address: '0x8002cD98Cfb563492A6fB3E7C8243b7B9Ad4cc92', + privateKey: '0xf12e28c0eb1ef4ff90478f6805b68d63737b7f33abfa091601140805da450d93', + }, + { + address: '0x4F9133D1d3F50011A6859807C837bdCB31Aaab13', + privateKey: '0xe667e57a9b8aaa6709e51ff7d093f1c5b73b63f9987e4ab4aa9a5c699e024ee8', + }, + { + address: '0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA', + privateKey: '0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959', + }, + { + address: '0xedB6F5B4aab3dD95C7806Af42881FF12BE7e9daa', + privateKey: '0x74d8b3a188f7260f67698eb44da07397a298df5427df681ef68c45b34b61f998', + }, + { + address: '0xe706e60ab5Dc512C36A4646D719b889F398cbBcB', + privateKey: '0xbe79721778b48bcc679b78edac0ce48306a8578186ffcb9f2ee455ae6efeace1', + }, + { + address: '0xE90E12261CCb0F3F7976Ae611A29e84a6A85f424', + privateKey: '0x3eb15da85647edd9a1159a4a13b9e7c56877c4eb33f614546d4db06a51868b1c', + }, +]; diff --git a/examples/hardhat-vyper-template/hardhat.config.ts b/examples/hardhat-vyper-template/hardhat.config.ts new file mode 100644 index 00000000..531a331e --- /dev/null +++ b/examples/hardhat-vyper-template/hardhat.config.ts @@ -0,0 +1,49 @@ +import type { HardhatUserConfig } from 'hardhat/config'; + +// ANCHOR: zksync-vyper-import +import '@nomiclabs/hardhat-vyper'; +import '@matterlabs/hardhat-zksync-vyper'; +import '@matterlabs/hardhat-zksync-node'; +import '@matterlabs/hardhat-zksync-deploy'; +// ANCHOR_END: zksync-vyper-import + +const config: HardhatUserConfig = { + defaultNetwork: 'zkSyncSepoliaTestnet', + networks: { + zkSyncSepoliaTestnet: { + url: 'https://sepolia.era.zksync.dev', + ethNetwork: 'sepolia', + zksync: true, + }, + zkSyncMainnet: { + url: 'https://mainnet.era.zksync.io', + ethNetwork: 'mainnet', + zksync: true, + }, + dockerizedNode: { + url: 'http://localhost:3050', + ethNetwork: 'http://localhost:8545', + zksync: true, + }, + inMemoryNode: { + url: 'http://127.0.0.1:8011', + ethNetwork: 'localhost', // in-memory node doesn't support eth node; removing this line will cause an error + zksync: true, + }, + hardhat: { + zksync: true, + }, + }, + // ANCHOR: zkvyper + zkvyper: { + version: 'latest', // Uses latest available in %%zk_git_repo_zkvyper-bin%% + settings: {}, + }, + // ANCHOR_END: zkvyper + // Currently, only Vyper 0.3.3 or 0.3.9 are supported. + vyper: { + version: '0.3.3', + }, +}; + +export default config; diff --git a/examples/hardhat-vyper-template/package.json b/examples/hardhat-vyper-template/package.json new file mode 100644 index 00000000..07ab71f1 --- /dev/null +++ b/examples/hardhat-vyper-template/package.json @@ -0,0 +1,32 @@ +{ + "name": "zksync-hardhat-vyper-template", + "description": "A template for ZKsync Vyper smart contracts development with Hardhat", + "private": true, + "author": "Matter Labs", + "license": "MIT", + "repository": "https://github.com/matter-labs/zksync-hardhat-template.git", + "scripts": { + "deploy": "hardhat deploy-zksync --script deploy.ts", + "interact": "hardhat deploy-zksync --script interact.ts", + "compile": "hardhat compile", + "clean": "hardhat clean", + "test": "hardhat test --network hardhat" + }, + "devDependencies": { + "@matterlabs/hardhat-zksync-deploy": "^1.5.0", + "@matterlabs/hardhat-zksync-node": "^1.1.1", + "@matterlabs/hardhat-zksync-vyper": "^1.1.1", + "@nomicfoundation/hardhat-verify": "^2.0.9", + "@nomiclabs/hardhat-vyper": "^3.0.7", + "@types/chai": "^4.3.16", + "@types/mocha": "^10.0.7", + "chai": "^4.5.0", + "dotenv": "^16.4.5", + "ethers": "^6.13.2", + "hardhat": "^2.22.7", + "mocha": "^10.7.0", + "ts-node": "^10.9.2", + "typescript": "^5.5.4", + "zksync-ethers": "^6.11.0" + } +} diff --git a/examples/hardhat-vyper-template/test/greeter.test.ts b/examples/hardhat-vyper-template/test/greeter.test.ts new file mode 100644 index 00000000..3ea82bd2 --- /dev/null +++ b/examples/hardhat-vyper-template/test/greeter.test.ts @@ -0,0 +1,23 @@ +import { expect } from 'chai'; +import { getWallet, deployContract } from '../deploy/utils'; + +const RICH_WALLET_PK = '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; + +describe('Greeter', function () { + it("Should return the new greeting once it's changed", async function () { + const wallet = getWallet(RICH_WALLET_PK); + + const greeting = 'Hello world!'; + const greeter = await deployContract('Greeter', [greeting], { wallet, silent: true }); + + expect(await greeter.greet()).to.eq(greeting); + + const newGreeting = 'Hola, mundo!'; + const setGreetingTx = await greeter.set_greeting(newGreeting); + + // wait until the transaction is processed + await setGreetingTx.wait(); + + expect(await greeter.greet()).to.equal(newGreeting); + }); +}); diff --git a/examples/hardhat-vyper-template/tsconfig.json b/examples/hardhat-vyper-template/tsconfig.json new file mode 100644 index 00000000..d366442a --- /dev/null +++ b/examples/hardhat-vyper-template/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "resolveJsonModule": true + }, + "include": ["./hardhat.config.ts", "./scripts", "./deploy", "./test", "typechain/**/*"] +} diff --git a/nuxt.config.ts b/nuxt.config.ts index f7553155..a6f6a3d5 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -6,6 +6,9 @@ export default defineNuxtConfig({ name: 'ZKsync Docs', url: process.env.NUXT_SITE_ENV === 'production' ? 'https://docs.zksync.io' : 'https://staging-docs.zksync.io', }, + nitro: { + plugins: ['./plugins/code-snippet-import.ts'], + }, runtimeConfig: { public: { app: 'docs', diff --git a/server/plugins/code-snippet-import.ts b/server/plugins/code-snippet-import.ts new file mode 100644 index 00000000..faa7e9fe --- /dev/null +++ b/server/plugins/code-snippet-import.ts @@ -0,0 +1,108 @@ +import { EOL } from 'os'; +import { join } from 'path'; +import { readFileSync } from 'fs'; + +// files cache +const files = new Map(); + +// Scans the file to look for `:code-import{filePath:}` templates, and replaces +// them with the corresponding code snippet from the `examples` directory. `anchor_name` is optional. +// +// The code snippets are also scanned for the `ANCHOR` and `ANCHOR_END` markers, +// which are used to mark that only the part of the snippet should be used. +export default defineNitroPlugin((nitroApp) => { + nitroApp.hooks.hook('content:file:beforeParse', (file) => { + if (file.body.includes(':code-import{filePath')) { + const newBody = handleCodeImport(file.body); + file.body = newBody; + } + }); +}); + +function handleCodeImport(body: string) { + const lines = body.split(EOL); + let inCodeBlock = false; + let codeBlockIndent = ''; + let removeESLintLines = false; + + for (let i = 0; i < lines.length; i++) { + const trimmedLine = lines[i].trim(); + + if (trimmedLine.startsWith('```')) { + inCodeBlock = !inCodeBlock; + if (inCodeBlock) { + const matches = lines[i].match(/^\s*/); + codeBlockIndent = matches ? matches[0] : ''; + } + } + + if (inCodeBlock && trimmedLine.includes(':code-import{filePath')) { + removeESLintLines = trimmedLine.includes('remove-linter'); + const filepath = trimmedLine.split('"')[1]; + let newCode = getCodeFromFilepath(filepath, removeESLintLines); + let split = newCode.split(EOL); + split = split.map((line) => codeBlockIndent + line); + newCode = split.join(EOL); + lines[i] = newCode; + removeESLintLines = false; + } + } + + return lines.join(EOL); +} + +function getCodeFromFilepath(filepath: string, removeESLintLines: boolean) { + const splitPath = filepath.split(':'); + const cleanPath = splitPath[0]; + const cache = files.get(cleanPath); + let code; + if (cache) { + code = cache; + } else { + const fullPath = join(process.cwd(), 'examples', cleanPath); + code = readFileSync(fullPath, 'utf8'); + files.set(filepath, code); + } + const exampleComment = splitPath[1] || null; + if (exampleComment) { + code = extractCommentBlock(code, exampleComment); + } + // remove any other ANCHOR tags & eslint comments + const lines = code.split(EOL); + const trimmedLines = lines.filter((line) => { + let include = !line.trimStart().startsWith('// ANCHOR'); + if (removeESLintLines && include) { + include = !line.trimStart().startsWith('// eslint-'); + } + return include; + }); + return trimmedLines.join(EOL); +} + +function extractCommentBlock(content: string, comment: string | null) { + const commentTypes = ['