diff --git a/content/00.build/05.start-coding/10.zksync-101/00.index.md b/content/00.build/05.start-coding/10.zksync-101/00.index.md index 8363ee0e..8b5fecd0 100644 --- a/content/00.build/05.start-coding/10.zksync-101/00.index.md +++ b/content/00.build/05.start-coding/10.zksync-101/00.index.md @@ -11,9 +11,9 @@ help you become a great developer on ZKsync! You will learn how to: :check-icon Initialize a new project with ZKsync CLI. -:check-icon Deploy contracts on ZKsync Era using Hardhat or Foundry. +:check-icon Deploy contracts on ZKsync Era using Hardhat. -:check-icon Test your contracts with Hardhat or Foundry. +:check-icon Test your contracts with Hardhat. :check-icon Implement upgradable patterns for your contracts. @@ -21,96 +21,91 @@ help you become a great developer on ZKsync! You will learn how to: --- -## Install zksync-cli +## Install ZKsync CLI -Our Quickstart series utilizes ZKsync CLI to help you develop and interact with ZKsync from your local machine. -We will use the full features of ZKsync CLI to build and deploy contracts in a local ZKsync Era test node. -Follow the instructions to install all dependencies including the optional dependencies -and install ZKsync CLI globally on the [Getting started](/build/zksync-cli) page. +Our 101 series utilizes ZKsync CLI to help you develop and interact with ZKsync from your local machine. +We will use the full features of ZKsync CLI to build and deploy contracts in a local ZKsync Era node. -### Setup local node +### Prerequisites -This series of guides will use ZKsync Era test node which allows for quicker testing and debugging processes. -A great benefit of using a local test node is that you will avoid incurring any actual transaction costs. -ZKsync Era test node provides a set of rich wallets that come with more than enough ETH to use for development. +- [Node.js v18 or newer](https://nodejs.org/en) +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) +- [Git](https://docs.github.com/en/get-started/getting-started-with-git/set-up-git) -#### Run a local in memory node - -Once you've followed all of the instructions on [Getting started](/build/zksync-cli) to setup ZKsync CLI on your machine, -check and ensure that Docker is running. The easiest way to start Docker is to open the Docker Desktop app. - -We are going to use the "In memory node" module for our local node setup. - -Run the following command in your terminal: +Open a terminal and install ZKsync CLI with the following command: ```bash -zksync-cli dev config +npm install -g zksync-cli ``` -It will provide a list of available node types you can run locally. +## Setup local node -Use the arrow keys to navigate to "In memory node" and press **Enter** to select. -The next question will ask what additional modules you want to use. -Make sure additional modules are unselected for this setup and press **Enter** to finish the configuration. +This series of guides will use an in memory node which allows for quicker testing and debugging processes. +A great benefit of using a local node is that you will avoid incurring any actual transaction costs. +ZKsync Era node provides a set of rich wallets that come with more than enough ETH to use for development. -The In memory node module will run a lighter version of the ZKsync Era node -which is ideal for swift testing, prototyping, and bootloader and system contract testing. +### Run a local in memory node -Run the following command in your terminal to start up the node: +To run a local in memory node on your machine, you will need Docker running. +The easiest way to start Docker is to run the Docker Desktop app. -```bash -zksync-cli dev start -``` +We are going to use the "In memory node" module for our local node setup. -The local era node will also include pre-configured rich wallets for use, visit [era-test-node rich wallets](/build/test-and-debug/in-memory-node#pre-configured-rich-wallets). +1. Run the following command in your terminal: -Your local ZKsync Era node is accessible at **[http://127.0.0.1:8011](http://127.0.0.1:8011/)**, ready for deployment or testing purposes. -You can use the Docker Desktop app to view logs from the running ZKsync Era node or use the `zksync-cli dev logs` command. -When you are done running your local ZKsync Era node, you can stop it with `zksync-cli dev stop`. -You can learn more about managing a local node with ZKsync CLI on [Running a node](/build/zksync-cli/running-a-node). + ```bash + zksync-cli dev config + ``` ---- + It will provide a list of available node types you can run locally. -## Choose Hardhat or Foundry +2. Use the arrow keys to navigate to "In memory node" and press **Enter** to select. + The next question will ask what additional modules you want to use. + Make sure additional modules are unselected for this setup and press **Enter** to finish the configuration. -Our Quickstart series provides two options for your learning process using -either Hardhat or Foundry. Pick one to use and stick with as you go through -each of the guides. + ```bash + ? Node to use (Use arrow keys) + ❯ In memory node - Quick startup, no persisted state, only L2 node - zkcli-in-memory-node + Dockerized node - Persistent state, includes L1 and L2 nodes - zkcli-dockerized-node + (Move up and down to reveal more choices) + ``` - + The in memory node module will run a lighter version of the ZKsync Era node + which is ideal for swift testing, prototyping, bootloader and system contract testing. -### Install foundry-zksync +3. Run the following command in your terminal to start up the node: -If you choose to use Foundry for the Quick Start series, you will need to -install the `foundry-zksync` tool. This tool is a specialized fork of Foundry, tailored for ZKsync. -It extends Foundry's capabilities for Ethereum app development to support ZKsync, -allowing for the compilation, deployment, testing, and interaction with smart contracts on ZKsync. + ```bash + zksync-cli dev start + ``` -::callout{icon="i-heroicons-information-circle-16-solid" color="amber"} -`foundry-zksync` is still in an alpha stage, so some features might not be fully supported -yet and may not work as fully intended. It is open-sourced and contributions are welcome. -:: + The in memory node includes pre-configured rich wallets for use, see [in-memory-node rich wallets](/build/test-and-debug/in-memory-node#pre-configured-rich-wallets). + +Your in memory node is accessible at **[http://127.0.0.1:8011](http://127.0.0.1:8011/)**, ready for deployment or testing purposes. +You can use the Docker Desktop app to view logs from the running ZKsync Era node or use the `zksync-cli dev logs` command. +When you are done running your in memory node, you can stop it with `zksync-cli dev stop`. +You can learn more about managing a local node with ZKsync CLI on [Running a node](/build/zksync-cli/running-a-node). -Quickly set up `foundry-zksync` by following these steps: +## Create the ZKsync 101 project -1. **Clone the Repository**: - Download the latest version from GitHub: +We have a template available for you to get started with quickly in the ZKsync 101 series. +In a directory where you want to create your project, run the following command in your terminal: - ```bash - git clone git@github.com:matter-labs/foundry-zksync.git - cd foundry-zksync - ``` +```sh +zksync-cli create zksync-101 --template zksync-101 +``` -2. **Run the Installer**: - Execute the script to install the `foundry-zksync` binaries `forge` and `cast`: +After you run the `create` command, the CLI will download and install packages for the project. +You should see a success message and instructions to get started with your project. - ```bash - ./install-foundry-zksync - ``` +Since we are using a local in memory node for development, we can use one of the +rich wallets for transactions and deployments. -### Private key setup with Foundry keystore +Rename the `.env.example` to `.env` which includes the private key for the first rich wallet. -:display-partial{ path="_partials/_foundry-create-keystore" } +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +Never save a real private key to the `.env` file! +:: --- @@ -118,6 +113,7 @@ Quickly set up `foundry-zksync` by following these steps: You should now have a fully working local environment to build new projects on ZKsync! -- Continue to [Hello ZKsync!](/build/start-coding/zksync-101/hello-zksync) to begin the series on building a crowdfunding campaign for Zeek. -- This setup provides you everything you need to build in ZKsync. -- You can skip on to [creating your own projects using ZKsync CLI](/build/zksync-cli/creating-projects). +- Continue to the next step, [Hello ZKsync!](/build/start-coding/zksync-101/hello-zksync) to begin the 101 series + on building a crowdfunding campaign for Zeek. +- This setup provides you everything you need to build on ZKsync Era. +- You can skip this 101 series to go on to [creating your own projects using ZKsync CLI](/build/zksync-cli/creating-projects). diff --git a/content/00.build/05.start-coding/10.zksync-101/10.hello-zksync.md b/content/00.build/05.start-coding/10.zksync-101/10.hello-zksync.md index e2db70a7..9bd2cb07 100644 --- a/content/00.build/05.start-coding/10.zksync-101/10.hello-zksync.md +++ b/content/00.build/05.start-coding/10.zksync-101/10.hello-zksync.md @@ -3,48 +3,197 @@ title: Hello ZKsync! description: Learn to deploy smart contracts efficiently in the ZKsync environment. --- -Welcome to the ZKsync 101 guide for deploying smart contracts on ZKsync! In this series, we'll walk you through the process -of creating and deploying a simple smart contract that creates a crowdfunding campaign for Zeek. In this section you will learn the following: - -:check-icon Initialize a new project with zksync-cli. +Welcome to ZKsync 101 for deploying smart contracts on ZKsync! In this series, we'll walk you through the process +of creating and deploying a simple smart contract that creates a crowdfunding campaign for Zeek. +In this section you will learn how to: :check-icon Craft a smart contract to fund Zeek's latest adventure. -:check-icon Deploy the contract on the ZKsync Era using your choice of Hardhat or Foundry. +:check-icon Deploy the contract onto your local in memory node. + +:check-icon Interact with the contract with ZKsync CLI Let's dive in and start your developer journey on ZKsync! --- -## Prerequisites - -This series requires some initial setup of tools to elevate your +::callout{icon="i-heroicons-information-circle" color="blue"} +This series requires some initial setup of tools to enhance your development experience building for ZKsync. Make sure to go through the setup provided in the initial [Getting started](/build/start-coding/zksync-101) section. +:: -If you have not started up a local node yet from the Getting started page, see -[Set up your local node](/build/start-coding/zksync-101#setup-local-node) in Getting started. +## Compile the CrowdfundingCampaign.sol contract -Select the following framework you want to get started using ZKsync Era with. +This guide introduces a crowdfunding campaign contract aimed at supporting Zeek's creative ventures. +Let's start by reviewing the starter contract `CrowdfundingCampaign.sol` in the +[`contracts/1-hello-zksync/CrowdfundingCampaign.sol` directory][crowdfunding-campaign-sol]. -::content-switcher ---- -items: [{ - label: 'Hardhat', - partial: '_hello-zksync/_hardhat_deploy_contract' -}, { - label: 'Foundry', - partial: '_hello-zksync/_foundry_deploy_contract' -}] ---- +The `CrowdfundingCampaign` contract is designed for a simple crowdfunding campaign. +This contract features: + +- A constructor to initialize the campaign's funding target. +- The `contribute` method to log funds, triggering `ContributionReceived` and `GoalReached` events. +- The `withdrawFunds` method, allowing the owner to collect accumulated funds post-goal achievement. +- The `getTotalFundsRaised` method to return the total amount of funds that's been raised. +- The `getFundingGoal` method to return the goal for the campaign to reach. + +Run the compile script from your project in the terminal: + +```bash [npm] +npm run compile +``` + +Upon successful compilation, you'll receive output detailing the +`zksolc` and `solc` versions used during compiling and the number +of Solidity files compiled. + +```bash +Compiling contracts for ZKsync Era with zksolc v1.4.1 and solc v0.8.17 +Compiling 15 Solidity files +Successfully compiled 15 Solidity files +``` + +The compiled artifacts will be located in the `/artifacts-zk` folder. + +::callout{icon="i-heroicons-information-circle" color="blue"} +Smart contracts deployed to ZKsync must be compiled using our custom compiler. +`zksolc` is the compiler used for Solidity. +This is why they're placed in the `artifacts-zk` folder! :: +## Deploy the contract + +The deployment script is located at +[`/deploy/1-hello-zksync/deploy.ts`][deploy-script]. + +**Key Components:** + +- **contractArtifactName:** Identifies the `CrowdfundingCampaign` contract for deployment. +- **constructorArguments:** Sets initialization parameters for the contract. In this case, +the fundraising goal, converted from ether to wei to match Solidity's `uint256` type. + +1. Execute the deployment command from `package.json`. + +```bash [npm] +npm run deploy:hello-zksync +``` + +Upon successful deployment, you'll receive output detailing the deployment process, +including the contract address, source, and encoded constructor arguments: + +```bash +Starting deployment process of "CrowdfundingCampaign"... +Estimated deployment cost: 0.0065057128 ETH + +"CrowdfundingCampaign" was successfully deployed: + - Contract address: 0x26b368C3Ed16313eBd6660b72d8e4439a697Cb0B + - Contract source: contracts/1-hello-zksync/CrowdfundingCampaign.sol:CrowdfundingCampaign + - Encoded constructor arguments: 0x00000000000000000000000000000000000000000000000000470de4df820000 +``` + +## Interact with the contract + +Now that our contract is deployed to our local in memory node, let's interact with it! +We initially set up the crowdfunding campaign with an amount of `.02 ETH` for the goal. + +### Read from the contract using ZKsync CLI + +We can confirm the amount by calling the `getFundingGoal` method using ZKsync CLI. + +In the terminal, run the following in your project directory, +replacing the contract address with your contract's address: + +```bash +zksync-cli contract read \ +--chain in-memory-node \ +--contract <0xYOUR_CONTRACT_ADDRESS> \ +--abi artifacts-zk/contracts/1-hello-zksync/CrowdfundingCampaign.sol/CrowdfundingCampaign.json +``` + +The CLI will prompt you with a list of available methods to select from. Navigate with the arrow keys and press **Enter** +on the method `getFundingGoal()`. + +```bash +Using provided ABI file +? Contract method to call (Use arrow keys) + ──────────────── Provided contract ──────────────── +❯ getFundingGoal() view returns (uint256) + getTotalFundsRaised() view returns (uint256) + owner() view returns (address) + ─────────────────────────────────────────────────── + Type method manually +``` + +- The `--chain` option defines the network we want to use, which is our local in-memory node. +- The `--contract` is the address of the contract you just deployed. +- The `--abi` provides the ABI to decode the contract and provide the list of methods to select from. + Without the ABI provided, you will have to manually type out the method name. + +You will get a response with the amount that we passed in to the constructor on deploy: + +```bash +? Contract method to call getFundingGoal() view returns (uint256) + +✔ Method response (raw): 0x00000000000000000000000000000000000000000000000000470de4df820000 +✔ Decoded method response: 20000000000000000 +``` + +### Write to the contract using ZKsync CLI + +Let's fund this crowdfunding campaign and make Zeek happy! + +We will write to the contract using ZKsync CLI, which requires a private key. +In this demonstration we will use the second wallet provided in the list of rich wallets. + +In the terminal, run the following command with your deployed contract's address: + +```bash +zksync-cli contract write \ +--chain in-memory-node \ +--contract <0xCONTRACT_ADDRESS> \ +--abi artifacts-zk/contracts/1-hello-zksync/CrowdfundingCampaign.sol/CrowdfundingCampaign.json \ +--pk 0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3 \ +--value 0.5 +``` + +In the prompt, press **Enter** on the `contribute() payable` method. +The CLI will then output the transaction information upon success. + +We can read the transaction data of the contribution with the following: + +```bash +zksync-cli transaction info \ +--tx <0xTRANSACTION_HASH> \ +--chain in-memory-node +``` + +Our crowdfund has reached its funding goal! Let's withdraw the funds for the owner: + +```bash +zksync-cli contract write \ +--chain in-memory-node \ +--contract <0xCONTRACT_ADDRESS> \ +--pk 0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 +``` + +The CLI will prompt for the method to call, we will call `withdrawFunds()`: + +```bash +? Enter method to call withdrawFunds() +``` + +Congratulations! You've deployed a crowdfunding contract and learned how +to interact with the deployed contract using ZKsync CLI! + ## Takeaways - **EVM Compatibility:** ZKsync is EVM compatible and you can write smart contracts in Solidity or Vyper. - **Custom Compilation:** Contracts deployed to ZKsync are compiled using `zksolc` or `zkvyper` as they generate a special bytecode for ZKsync's ZKEVM. -- **Development Tools:** ZKsync supports your favorite development toolkit Hardhat and Foundry. +- **Development Tools:** In memory node is a quick and easy local node environment to deploy to, + saving costs on deployment while developing. +- **ZKsync CLI:** A powerful command line tool to interact with contracts easily. ## Next steps @@ -53,9 +202,13 @@ a proficient ZKsync developer. To expand your expertise: - **Explore Contract Factories:** Enhance your project by building a contract factory for the `CrowdfundingCampaign` contract in the [next guide](/build/start-coding/zksync-101/contract-factory). -This will allow you to efficiently manage multiple crowdfunding campaigns, each with its own unique parameters. +This will allow you to efficiently manage multiple crowdfunding campaigns, each with their own unique parameters. - **Dive Deeper into ZKsync Features:** Investigate advanced ZKsync features such as account abstraction, and paymasters. -- **Join the Community:** Engage with the ZKsync developer community through forums, +- **Join the Community:** Engage with the [ZKsync developer community][zksync-community] through forums, Discord channels, Dev Discussions, or GitHub repositories. Share your experiences, ask questions, and collaborate on projects. + +[crowdfunding-campaign-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/1-hello-zksync/CrowdfundingCampaign.sol +[deploy-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/1-hello-zksync/deploy.ts +[zksync-community]: /build/resources/community-channels diff --git a/content/00.build/05.start-coding/10.zksync-101/20.contract-factory.md b/content/00.build/05.start-coding/10.zksync-101/20.contract-factory.md index 76380d9d..3d416a74 100644 --- a/content/00.build/05.start-coding/10.zksync-101/20.contract-factory.md +++ b/content/00.build/05.start-coding/10.zksync-101/20.contract-factory.md @@ -11,7 +11,7 @@ foundational `CrowdfundingCampaign` contract in the first guide. :check-icon Construct a contract factory to create multiple crowdfunding campaigns. -:check-icon Seamlessly deploy your contract factory on ZKsync Era, using either Hardhat or Foundry. +:check-icon Seamlessly deploy your contract factory on ZKsync Era using Hardhat. Let's explore the efficiency and scalability that contract factories bring. @@ -26,24 +26,133 @@ numerous similar contracts efficiently. ## Setup the project +Make sure to go through the setup provided in the initial [Getting started](/build/start-coding/zksync-101) section. +You will have downloaded the 101 project through ZKsync CLI `create` and started up a local in memory node for development. + +If you haven't started up your local in memory node or you're not sure, run the following: + +```bash +zksync-cli dev restart +``` + +## Compile the contracts + +This section will focus on compiling and deploying the `CrowdfundingFactory.sol` +contract that is provided under the [`/contracts/2-contract-factory` directory][crowdfunding-factory-sol]. + +The `CrowdfundingFactory.sol`contract will be used to deploy multiple instances of +the `CrowdfundingCampaign.sol` contract from the previous guide. +This contract factory approach streamlines the deployment of crowdfunding campaigns, +making it efficient to launch and manage multiple campaigns. + +The `CrowdfundingFactory` contract automates the creation and oversight of +`CrowdfundingCampaign` contracts, each with their own distinct funding goals. +The factory contract features: + +- **Campaign Creation**: Utilizes the `createCampaign` method to initiate a new +`CrowdfundingCampaign` contract. This function takes a `fundingGoal` as an argument, +deploys a new campaign contract with this goal, and tracks the created campaign in the +`campaigns` array. +- **Campaign Tracking**: The `getCampaigns` method offers a view into all the campaigns +created by the factory, allowing for easy access and management of multiple crowdfunding +initiatives. + +Run the compile script from your project in the terminal: + +```bash [npm] +npm run compile +``` + +### Deploy CrowdfundingCampaigns via the CrowdfundingFactory + +This section outlines the steps to deploy the `CrowdfundingCampaign` contract +using our new `CrowdfundingFactory`. + +The deployment script is located at [`/deploy/2-contract-factory/deploy.ts`][deploy-script]. + +The deploy script: + +- deploys a single `CrowdfundingFactory`. +- saves an instance of the deployed factory to `factoryContract`. + This gives us access to the factory's functionalities. +- The `createCampaign` method is called on this instance to create + and deploy a new crowdfunding campaign contract. + +Run the deployment command. + +```bash [npm] +npm run deploy:crowdfunding-factory +``` + +Upon successful deployment, you'll receive output detailing the deployment process, +including the contract address, source, and encoded constructor arguments: + +```bash +Starting deployment process of "CrowdfundingFactory"... +Estimated deployment cost: 0.0002500236 ETH + +"CrowdfundingFactory" was successfully deployed: + - Contract address: <0xFACTORY_CONTRACT_ADDRESS> + - Contract source: contracts/CrowdfundFactory.sol:CrowdfundingFactory + - Encoded constructor arguments: 0x + +✅ Deployment and campaign creation complete! +``` + +## Create another campaign with ZKsync CLI + +We've got one crowdfunding campaign created from the deployment script, +let's create another through ZKsync CLI! + +Run the following command: + +```bash +zksync-cli contract write \ +--chain in-memory-node \ +--contract <0xFACTORY_CONTRACT_ADDRESS> \ +--pk 0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 +``` + +The CLI will prompt you with a few questions. +We want to call the method `createCampaign(uint256 fundingGoal)` +and provide a funding goal in wei: + +```bash +? Enter method to call createCampaign(uint256 fundingGoal) +? Provide method arguments: +? [1/1] fundingGoal (uint256) 200000000000000000 +``` + +Now that we've created a second crowdfunding campaign, +let's check the list of campaigns! + +Run the following command from within your project directory. + +```bash +zksync-cli contract read \ +--chain in-memory-node \ +--contract <0xFACTORY_CONTRACT_ADDRESS> \ +--abi artifacts-zk/contracts/2-contract-factory/CrowdfundingFactory.sol/CrowdfundingFactory.json +``` + ::callout{icon="i-heroicons-information-circle" color="blue"} -If you have not started up a local node yet from the Getting started page, run `zksync-cli dev start`. -Make sure you are running the "In memory node" option. +We pass the ABI in to help decode the output. +Without the ABI, the CLI will return the raw response. :: -Select the framework you want to get started using ZKsync Era with. +The CLI will prompt with a list of the available methods from the contract. +Navigate with the arrow keys and press **Enter** on `getCampaigns() view returns (address[])`. -::content-switcher ---- -items: [{ - label: 'Hardhat', - partial: '_deploy_factory/_hardhat_deploy_contract_factory' -}, { - label: 'Foundry', - partial: '_deploy_factory/_foundry_deploy_contract_factory' -}] ---- -:: +The ClI returns the method's response in raw format along with the decoded format +because we passed in the ABI. + +You should see two addresses in the decoded method response, the first created from +the deploy script and the second for the campaign you created with ZKsync CLI! + +```bash +✔ Method response (raw): 0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009bf2281f53847387e1d8b5645cfcfbea576c4397000000000000000000000000ca55c6bbb6b122058ed742c33859f3621bc8030c +✔ Decoded method response: 0x9bf2281F53847387e1d8b5645cFCfbea576c4397,0xca55c6BBB6B122058Ed742C33859F3621bc8030c +``` ## Takeaways @@ -67,6 +176,10 @@ comprehensive tests is critical. - **Advanced ZKsync Integrations:** Explore deeper into ZKsync's ecosystem by implementing features like account abstraction and paymasters to enhance user experience and contract flexibility. -- **Community Engagement and Contribution:** Join the vibrant ZKsync community. +- **Community Engagement and Contribution:** Join the vibrant [ZKsync developer community][zksync-community]. Participate in forums, Discord, or GitHub discussions. Sharing insights, asking queries, and contributing can enrich the ecosystem and your understanding of ZKsync. + +[crowdfunding-factory-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/2-contract-factory/CrowdfundingFactory.sol +[deploy-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/2-contract-factory/deploy.ts +[zksync-community]: /build/resources/community-channels diff --git a/content/00.build/05.start-coding/10.zksync-101/30.testing.md b/content/00.build/05.start-coding/10.zksync-101/30.testing.md index 17a5b4e4..f953232d 100644 --- a/content/00.build/05.start-coding/10.zksync-101/30.testing.md +++ b/content/00.build/05.start-coding/10.zksync-101/30.testing.md @@ -6,35 +6,130 @@ description: Discover how to effectively test smart contracts on ZKsync Era ecos Welcome back to our ZKsync 101 series, your fast-track to ZKsync development! In this third guide, we transition from deploying and managing contracts to the critical phase of testing. This guide will walk you through the steps to ensure your `CrowdfundingCampaign` -contracts, introduced in our first guide and efficiently deployed through contract factories +contract, introduced in our first guide and efficiently deployed through contract factories in the second, work flawlessly. :check-icon Elevate your ZKsync toolkit with robust contract testing techniques. :check-icon Craft comprehensive tests for your `CrowdfundingCampaign` to ensure reliability and security. -:check-icon Use Hardhat or Foundry to write and run tests, ensuring your campaigns are ready. - -Dive into the world of smart contract testing and solidify the foundation of your ZKsync projects. +:check-icon Use Hardhat to write and run tests, ensuring your campaigns are ready. ## Setup the project -::callout{icon="i-heroicons-information-circle" color="blue"} -If you have not started up a local node yet from the Getting started page, run `zksync-cli dev start`. -Make sure you are running the "In memory node" option. -:: +Make sure to go through the setup provided in the initial [Getting started](/build/start-coding/zksync-101) section. +You will have downloaded the 101 project through ZKsync CLI `create`. +Since we are using `hardhat-zksync` for testing, you do not need to have the +in memory node running for this section. + +If your local in-memory node is running, stop it with the following command: + +```bash +zksync-cli dev stop +``` + +## `hardhat-zksync-node` + +Testing contracts requires a more structured setup. +We'll use `hardhat-zksync-node` which is a part of the `hardhat-zksync` package to run tests against its own implementation +of the in memory node. + +Within the `hardhat.config.ts`, you'll observe the `zksync` flag set to `true` under the +`hardhat` network, indicating the integration with ZKsync's testing environment. + +```typescript [hardhat.config.ts] +const config: HardhatUserConfig = { + defaultNetwork: "inMemoryNode" + networks: { + ... + hardhat: { + zksync: true, + }, + ... + } + ... +} +``` + +Your project has npm scripts for testing that set the `--network` to `"hardhat"`. + +Secondly within the `hardhat.config.ts`, you'll observe the importing of +`@nomicfoundation/hardhat-chai-matchers`. This plugin provides Hardhat with an extended +suite of assertion methods tailored for contract testing, significantly improving the testing +toolkit available for your project. + +```typescript +import "@nomicfoundation/hardhat-chai-matchers"; +``` -::content-switcher --- -items: [{ - label: 'Hardhat', - partial: '_testing/_hardhat_contract_testing' -}, { - label: 'Foundry', - partial: '_testing/_foundry_contract_testing' -}] + +## Compile the `CrowdfundingCampaign` contract + +Now that our setup is complete, it's time to focus on the core of this +guide - testing our `CrowdfundingCampaign.sol` contract. + +Thorough testing involves scrutinizing every function and aspect of our contract, +including potential failure scenarios. In this guide, we'll focus in on the `contribute` +method to ensure it's tested. + +To compile the contracts in your project, run the following command: + +```bash [npm] +npm run compile +``` + --- -:: + +## Test `CrowdfundingCampaign` + +This section describes testing the `CrowdfundingCampaign.sol` contract. +Let's review the contract at [`test/1-hello-zksync/CrowdfundingCampaign.test.ts`][crowdfunding-campaign-test] + +- **Initialization**: Each test case initializes with fresh contract instances in the `beforeEach` + and predefined rich wallet accounts to simulate various contributors and the contract owner. +- **Deployment**: The `CrowdfundingCampaign` contract is deployed using + the `deployContract` utility, setting a specific funding goal for each test scenario. + +**`contribute` Method Tests:** + +- **Zero Contributions**: Verifies that the contract correctly rejects contribution attempts + with zero value, ensuring the integrity of the contribution process. +- **Funds Aggregation**: Tests the contract's ability to accurately aggregate contributions + from multiple addresses and update the `totalFundsRaised` accordingly. +- **Goal Achievement**: Checks for the `GoalReached` event emission upon meeting the funding goal, + confirming the contract's responsiveness to achieving its set target. + +Run the tests with the following command: + +```bash [npm] +npm run test:crowdfunding-campaign +``` + +Upon completion, the test suite will provide a summary of all executed tests, +indicating their success or failure: + +```bash + CrowdfundingCampaign + contribute + ✔ should reject contributions of 0 + ✔ should aggregate contributions in totalFundsRaised (150ms) + ✔ should emit GoalReached event when funding goal is met (81ms) + withdrawFunds + ✔ should revert if called by a non-owner + ✔ should revert if funding goal hasn't been reached + ✔ should transfer the funds to the owner when funds have been raised (229ms) + getFundingGoal + ✔ should return the correct funding goal + getTotalFundsRaised + ✔ should return 0 when no contributions have been made + + + 8 passing (2s) +``` + +🎉 Congratulations! Your `CrowdfundingCampaign` contract +has been thoroughly tested and is ready for action. ## Takeaways @@ -43,7 +138,7 @@ on ZKsync. Proper testing safeguards against unforeseen errors and vulnerabiliti - **Comprehensive Coverage**: Achieving comprehensive test coverage, including both positive and negative testing scenarios, is essential for a robust smart contract. This guide emphasized the `contribute` method, but testing should encompass all aspects of your contract. -- **Tooling Efficiency**: Leveraging Hardhat or Foundry for testing provides a streamlined and efficient workflow. +- **Tooling Efficiency**: Leveraging Hardhat for testing provides a streamlined and efficient workflow. These tools offer powerful features and plugins, like `@nomicfoundation/hardhat-chai-matchers`, that enhance the testing process. @@ -52,12 +147,15 @@ that enhance the testing process. With a solid foundation in contract testing, you're well-equipped to advance your ZKsync development journey. Consider the following steps to further your expertise: -- **Upgradeability**: Dive into the [Upgradeability article](/build/start-coding/zksync-101/upgrading) focusing on contract upgradeability. +- **Upgradeability**: Dive into the [Upgradeability guide](/build/start-coding/zksync-101/upgrading) focusing on contract upgradeability. Learning to make your contracts upgradeable will enable you to update and improve your smart contracts over time without losing state or funds. - **Advanced ZKsync Integrations:** Explore deeper into ZKsync's ecosystem by implementing features like account abstraction and paymasters to enhance user experience and contract flexibility. -- **Community Engagement and Contribution:** Join the vibrant ZKsync community. +- **Community Engagement and Contribution:** Join the vibrant [ZKsync developer community][zksync-community]. Participate in forums, Discord, or GitHub discussions. Sharing insights, asking queries, and contributing can enrich the ecosystem and your understanding of ZKsync. + +[crowdfunding-campaign-test]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/test/1-hello-zksync/CrowdfundingCampaign.test.ts +[zksync-community]: /build/resources/community-channels diff --git a/content/00.build/05.start-coding/10.zksync-101/40.upgrading.md b/content/00.build/05.start-coding/10.zksync-101/40.upgrading.md index 07d91e7f..2d3706a9 100644 --- a/content/00.build/05.start-coding/10.zksync-101/40.upgrading.md +++ b/content/00.build/05.start-coding/10.zksync-101/40.upgrading.md @@ -3,23 +3,28 @@ title: Upgradability description: Learn to make smart contracts upgradeable within the ZKsync ecosystem. --- -In this fourth installment for ZKsync 101, we embark on a journey through contract upgradability, +In this fourth installment for ZKsync 101, we embark on a journey through contract upgradeability, an important aspect for maintaining and enhancing smart contracts over time. This guide will lead you through the strategies and practices for making the `CrowdfundingCampaign` contract **upgradeable**. -:check-icon Harness advanced techniques for contract upgradability in ZKsync. +:check-icon Harness advanced techniques for contract upgradeability in ZKsync. :check-icon Implement upgradeable patterns for the `CrowdfundingCampaign` to ensure long-term adaptability and improvement. :check-icon Leverage tools and best practices in ZKsync to facilitate seamless contract upgrades. -Begin to understand smart contract evolution and empower your ZKsync applications with the -flexibility of upgradability. +## Setup the project -::callout{icon="i-heroicons-information-circle" color="blue"} -If you have not started up a local node yet from the Getting started page, run `zksync-cli dev start`. -Make sure you are running the "In memory node" option. -:: +Make sure to go through the setup provided in the initial [Getting started](/build/start-coding/zksync-101) section. +You will have downloaded the 101 project through ZKsync CLI `create` and started up a local in-memory node for development. + +If you haven't started up your local in-memory node or you're not sure, run the following: + +```bash +zksync-cli dev restart +``` + +--- Select your preferred upgrade mechanism: @@ -31,17 +36,16 @@ items: [{ }, { label: 'Beacon', partial: '_upgrading/_beacon_proxy_contract_upgradability' -}, - { +}, { label: 'UUPS', partial: '_upgrading/_uups_contract_upgradability' -},] +}] --- :: ## Takeaways -- **Upgradability:** The guide highlights the critical aspect of smart contract upgradability, introducing techniques +- **Upgradeability:** The guide highlights the critical aspect of smart contract upgradeability, introducing techniques for using transparent, beacon, and UUPS proxies. This ensures your contracts remain adaptable, allowing for seamless updates to business logic or enhancements in efficiency. @@ -57,6 +61,8 @@ offering new models for transaction fee management and enhancing user experience - **Advanced ZKsync Integrations:** Explore deeper into ZKsync's ecosystem by implementing features like account abstraction and paymasters to enhance user experience and contract flexibility. -- **Community Engagement and Contribution:** Join the vibrant ZKsync community. +- **Community Engagement and Contribution:** Join the vibrant [ZKsync developer community][zksync-community]. Participate in forums, Discord, or GitHub discussions. Sharing insights, asking queries, and contributing can enrich the ecosystem and your understanding of ZKsync. + +[zksync-community]: /build/resources/community-channels diff --git a/content/00.build/05.start-coding/10.zksync-101/50.paymaster.md b/content/00.build/05.start-coding/10.zksync-101/50.paymaster.md index 8f72535a..0836c408 100644 --- a/content/00.build/05.start-coding/10.zksync-101/50.paymaster.md +++ b/content/00.build/05.start-coding/10.zksync-101/50.paymaster.md @@ -14,10 +14,7 @@ fee management and enhance user experiences within your dApps. :check-icon Learn how paymasters can cover transaction fees for your dApp users, enhancing accessibility and user experience. :check-icon Discover the flexibility of fee payment with paymasters, including the ability to pay -fees in ERC20 tokens on ZKsync Era, using Hardhat or Foundry. - -Embark on this journey to understand how paymasters can add a new layer of functionality and user-friendliness -to your decentralized applications. +fees in ERC20 tokens on ZKsync Era. ## What is a Paymaster? @@ -41,8 +38,7 @@ this flow provides a structured approach. It ensures that user tokens can be sea fees, subject to user-approved limits. As we explore paymasters, remember that while they offer enhanced flexibility for fee management, their -implementation should always prioritize security and user trust. This guide aims to equip you with the knowledge -to effectively incorporate paymasters into your ZKsync projects, paving the way for more user-friendly and accessible dApps. +implementation should always prioritize security and user trust. ::callout{icon="i-heroicons-exclamation-triangle" color="amber"} The paymaster smart contract code is provided "as-is" without any express or implied warranties. @@ -59,7 +55,7 @@ The paymaster smart contract code is provided "as-is" without any express or imp ## Paymaster flow -Select the paymaster type you want to get started using ZKsync Era with. +Select the paymaster type you want to use. ::content-switcher --- @@ -85,6 +81,9 @@ interactions. This feature enhances user engagement and adoption of dApps. ## Next Steps +- **Learn more about Paymasters:** Read further on our section on + [Paymasters](/build/developer-reference/account-abstraction/paymasters) + to deepen your understanding. - **Experiment with Different Paymaster Contracts:** Now that you are familiar with both approval-based and general paymaster flows, you can experiment with these contracts by deploying them under various conditions and with different types validations, restrictions and enhancements. @@ -92,6 +91,8 @@ interactions. This feature enhances user engagement and adoption of dApps. [building a user interface with ZKsync CLI](/build/zksync-cli/creating-projects) that interacts with the paymaster contracts you have deployed. This will not only improve the usability of your contracts but also provide practical insights into how end-users interact with your dApps in real-world scenarios. -- **Community Engagement and Contribution:** Join the vibrant ZKsync community. +- **Community Engagement and Contribution:** Join the vibrant [ZKsync developer community][zksync-community]. Participate in forums, Discord, or GitHub discussions. Sharing insights, asking queries, and contributing can enrich the ecosystem and your understanding of ZKsync. + +[zksync-community]: /build/resources/community-channels diff --git a/content/00.build/05.start-coding/10.zksync-101/_deploy_factory/_foundry_deploy_contract_factory.md b/content/00.build/05.start-coding/10.zksync-101/_deploy_factory/_foundry_deploy_contract_factory.md deleted file mode 100644 index 84d31e1b..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_deploy_factory/_foundry_deploy_contract_factory.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -title: Foundry | Deploy Contract Factory ---- - -:display-partial{path = "/_partials/_foundry_alpha_warning"} - -Run the following command in your terminal to initialize the Foundry project. - -```sh -zksync-cli create --template qs-fs-factories foundry-contract-factory-quickstart -cd foundry-contract-factory-quickstart -``` - -## Review the CrowdfundingFactory contract - -The `CrowdfundingFactory.sol` we will compile and deploy is provided under the [`/src` directory](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/foundry/factory/src/CrowdfundFactory.sol). - -The `CrowdfundingFactory.sol`contract will be used to deploy multiple instances of -the `CrowdfundingCampaign.sol` contract from the previous guide. - -::drop-panel - ::panel{label="CrowdfundingFactory.sol"} - ```solidity [CrowdfundingFactory.sol] - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - - // Crowdfunding campaign contract - import "./CrowdfundingCampaign.sol"; - - // Factory contract to create and manage crowdfunding campaigns - contract CrowdfundingFactory { - CrowdfundingCampaign[] public campaigns; - - event CampaignCreated(address campaignAddress, uint256 fundingGoal); - - function createCampaign(uint256 fundingGoal) public { - CrowdfundingCampaign newCampaign = new CrowdfundingCampaign(fundingGoal); - campaigns.push(newCampaign); - - emit CampaignCreated(address(newCampaign), fundingGoal); - } - - function getCampaigns() public view returns (CrowdfundingCampaign[] memory) { - return campaigns; - } - } - ``` - :: -:: - -The `CrowdfundingFactory` contract automates the creation and oversight of -`CrowdfundingCampaign` contracts, each with its distinct funding goals, it features: - -- **Campaign Creation**: Utilizes the `createCampaign` method to initiate a new -`CrowdfundingCampaign` contract. This function takes a `fundingGoal` as an argument, -deploys a new campaign contract with this goal, and tracks the created campaign in the -`campaigns` array. -- **Campaign Tracking**: The `getCampaigns` method offers a view into all the campaigns -created by the factory, allowing for easy access and management of multiple crowdfunding -initiatives. - -This contract factory approach streamlines the deployment of crowdfunding campaigns, -making it efficient to launch and manage multiple campaigns. - -### Compile contract - -Smart contracts deployed to ZKsync must be compiled using our custom compiler. -For this particular guide we are making use of `zksolc`. - -To compile the contracts in the project, run the following command: - -```bash -forge build --zksync -``` - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -[⠒] Compiling... -[⠃] Compiling 3 files with 0.8.20 -[⠊] Solc 0.8.20 finished in 336.48ms -Compiler run successful! -Compiling contracts for ZKsync Era with zksolc v1.4.0 -``` - -The compiled zkEVM artifacts will be located in the `/zkout` folder, and the solc -artifacts will be located in the `/out` folder. - ---- - -## Deploy a CrowdfundingCampaign with CrowdfundingFactory - -This section outlines the steps to deploy the `CrowdfundingCampaign` contract using -our new `CrowdfundingFactory`. - -1. Let's start by deploying the `CrowdfundingFactory` contract. Execute the following -command: - - ```bash - forge create src/CrowdfundFactory.sol:CrowdfundingFactory --account myKeystore --rpc-url inMemoryNode --zksync - ``` - - Upon a successfull deployment you'll receive details of the deploying address, the contract address, - and the transaction hash, like so: - - ```bash - Deployer: 0x89E0Ff69Cc520b55C9F7Bcd3EAC17e81d9bB8dc2 - Deployed to: 0x607545Fd35ef49d7445555ddFa22938fD4Efb219 - Transaction hash: 0x94e7a97bb64c2bacffbd2a47f3c10021a80156d11082c079046a426c99518d28 - ``` - -1. Using the `CrowdfundingFactory` contract address let's deploy our `CrowdfundingCampaign`: - - ```bash - cast send 0x607545Fd35ef49d7445555ddFa22938fD4Efb219 "createCampaign(uint256)" "1" --rpc-url inMemoryNode --account myKeystore - ``` - - Upon a successfull deployment you'll receive details of the transaction, including the - contract address of our crowdfunding campaign: - - ```bash - blockHash 0x7f8dfcd365b4ba5ac690e94aedb5fdb2bdb5ef12b2ff68672ab58c7a89738161 - blockNumber 1576375 - contractAddress 0x95f83473b88B5599cdB273F976fB3DC66DEA1c1D - ... - ... - ``` - -🌟 Brilliant! Your contract factory and its first crowdfunding campaign are now operational. diff --git a/content/00.build/05.start-coding/10.zksync-101/_deploy_factory/_hardhat_deploy_contract_factory.md b/content/00.build/05.start-coding/10.zksync-101/_deploy_factory/_hardhat_deploy_contract_factory.md deleted file mode 100644 index bdbe0b48..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_deploy_factory/_hardhat_deploy_contract_factory.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -title: Hardhat | Deploy Contract Factory ---- -Run the following command in your terminal to initialize the project. - -```sh -zksync-cli create --template qs-factories contract-factory-quickstart -cd contract-factory-quickstart -``` - -## Update the hardhat.config.ts - -Since we are using the "In memory node" with ZKsync CLI, we need to set the default network Hardhat uses -for deploying. - -Open up the `hardhat.config.ts` file and set the `defaultNetwork` to `inMemoryNode`. - -```ts -// ... -const config: HardhatUserConfig = { - defaultNetwork: "inMemoryNode", -// ... -``` - -## Compile the contracts - -This section will focus on compiling and deploying the `CrowdfundingFactory.sol` -contract that is provided under the [`/contracts` directory](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/factory/contracts/CrowdfundFactory.sol). - -The `CrowdfundingFactory.sol`contract will be used to deploy multiple instances of -the `CrowdfundingCampaign.sol` contract from the previous guide. -This contract factory approach streamlines the deployment of crowdfunding campaigns, -making it efficient to launch and manage multiple campaigns. - -::drop-panel - ::panel{label="CrowdfundingFactory.sol"} - ```solidity [CrowdfundingFactory.sol] - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - - // Crowdfunding campaign contract - import "./CrowdfundingCampaign.sol"; - - // Factory contract to create and manage crowdfunding campaigns - contract CrowdfundingFactory { - CrowdfundingCampaign[] public campaigns; - - event CampaignCreated(address campaignAddress, uint256 fundingGoal); - - function createCampaign(uint256 fundingGoal) public { - CrowdfundingCampaign newCampaign = new CrowdfundingCampaign(fundingGoal); - campaigns.push(newCampaign); - - emit CampaignCreated(address(newCampaign), fundingGoal); - } - - function getCampaigns() public view returns (CrowdfundingCampaign[] memory) { - return campaigns; - } - } - ``` - :: -:: - -The `CrowdfundingFactory` contract automates the creation and oversight of -`CrowdfundingCampaign` contracts, each with their own distinct funding goals. -The factory contract features: - -- **Campaign Creation**: Utilizes the `createCampaign` method to initiate a new -`CrowdfundingCampaign` contract. This function takes a `fundingGoal` as an argument, -deploys a new campaign contract with this goal, and tracks the created campaign in the -`campaigns` array. -- **Campaign Tracking**: The `getCampaigns` method offers a view into all the campaigns -created by the factory, allowing for easy access and management of multiple crowdfunding -initiatives. - -:display-partial{path = "/_partials/_compile-solidity-contracts"} - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.1 and solc v0.8.17 -Compiling 15 Solidity files -Successfully compiled 15 Solidity files -``` - -The compiled artifacts will be located in the `/artifacts-zk` folder. - -### Deploy CrowdfundingCampaigns via the CrowdfundingFactory - -This section outlines the steps to deploy the `CrowdfundingCampaign` contract -using our new `CrowdfundingFactory`. - -The deployment script is located at `/deploy/deployUsingFactory.ts`. - -::drop-panel - ::panel{label="deployUsingFactory.ts"} - - ```typescript [deployUsingFactory.ts] - import { deployContract, getWallet } from "./utils"; - import { ethers } from "ethers"; - import { HardhatRuntimeEnvironment } from "hardhat/types"; - - export default async function (hre: HardhatRuntimeEnvironment) { - const contractArtifactName = "CrowdfundingFactory"; - const constructorArguments = []; - const crowdfundingFactory = await deployContract(contractArtifactName, constructorArguments); - - console.log(`🏭 CrowdfundingFactory address: ${crowdfundingFactory.target}`); - - const contractArtifact = await hre.artifacts.readArtifact("CrowdfundingFactory"); - const factoryContract = new ethers.Contract( - crowdfundingFactory.target, - contractArtifact.abi, - getWallet() - ); - - // Define funding goal for the campaign, e.g., 0.1 ether - const fundingGoalInWei = ethers.parseEther('0.1').toString(); - - // Use the factory to create a new CrowdfundingCampaign - const createTx = await factoryContract.createCampaign(fundingGoalInWei); - await createTx.wait(); - - // Retrieve the address of the newly created CrowdfundingCampaign - const campaigns = await factoryContract.getCampaigns(); - const newCampaignAddress = campaigns[campaigns.length - 1]; - - console.log(`🚀 New CrowdfundingCampaign deployed at: ${newCampaignAddress}`); - console.log('✅ Deployment and campaign creation complete!'); - } - ``` - - :: -:: - -- The `deployUsingFactory.ts` script deploys the `CrowdfundingFactory` through the `deployContract` method. -- An instance of the factory is assigned to `factoryContract`. - This gives us access to the factory's functionalities. -- The `createCampaign` method is called on this instance to create - and deploy a new crowdfunding campaign contract. - -Run the deployment command. - -```bash [npm] -npm run deploy -``` - -Upon successful deployment, you'll receive output detailing the deployment process, -including the contract address, source, and encoded constructor arguments: - -```bash -Starting deployment process of "CrowdfundingFactory"... -Estimated deployment cost: 0.0002500236 ETH - -"CrowdfundingFactory" was successfully deployed: - - Contract address: 0xD084EF36f8F5353f70498cD84cb8D2B844C120a8 - - Contract source: contracts/CrowdfundFactory.sol:CrowdfundingFactory - - Encoded constructor arguments: 0x - -Requesting contract verification... -Your verification ID is: 10097 -Contract successfully verified on ZKsync block explorer! -🏭 CrowdfundingFactory address: 0xD084EF36f8F5353f70498cD84cb8D2B844C120a8 -🚀 New CrowdfundingCampaign deployed at: 0x060B748eC3512795E94045c406CFd5877DD84e4D -✅ Deployment and campaign creation complete! -``` - -🌟 Brilliant! Your contract factory and its first crowdfunding campaign are now -operational. diff --git a/content/00.build/05.start-coding/10.zksync-101/_hello-zksync/_foundry_deploy_contract.md b/content/00.build/05.start-coding/10.zksync-101/_hello-zksync/_foundry_deploy_contract.md deleted file mode 100644 index 963d4bee..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_hello-zksync/_foundry_deploy_contract.md +++ /dev/null @@ -1,159 +0,0 @@ ---- -title: Foundry | Deploy Contract ---- - -:display-partial{path = "/_partials/_foundry_alpha_warning"} - -Run the following command in your terminal to initialize the Foundry project. - -```sh -zksync-cli create --template qs-fs-hello-zksync hello-zksync-foundry-quickstart -cd hello-zksync-foundry-quickstart -``` - -## Set up your wallet - -:display-partial{path = "/build/start-coding/zksync-101/_partials/_setup-wallet"} - -## Compile your first contract - -This guide -introduces a crowdfunding campaign contract aimed at supporting Zeek's inventive ventures. - -Let's start by reviewing the starter contract in the [`src/` directory](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/foundry/hello-zksync/src/Crowdfund.sol). - -::drop-panel - ::panel{label="CrowdfundingCampaign.sol"} - ```solidity [CrowdfundingCampaign.sol] - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - - contract CrowdfundingCampaign { - address public owner; - uint256 public fundingGoal; - uint256 public totalFundsRaised; - mapping(address => uint256) public contributions; - - event ContributionReceived(address contributor, uint256 amount); - event GoalReached(uint256 totalFundsRaised); - - constructor(uint256 _fundingGoal) { - owner = msg.sender; - fundingGoal = _fundingGoal; - } - - function contribute() public payable { - require(msg.value > 0, "Contribution must be greater than 0"); - contributions[msg.sender] += msg.value; - totalFundsRaised += msg.value; - - emit ContributionReceived(msg.sender, msg.value); - - if (totalFundsRaised >= fundingGoal) { - emit GoalReached(totalFundsRaised); - } - } - - function withdrawFunds() public { - require(msg.sender == owner, "Only the owner can withdraw funds"); - require(totalFundsRaised >= fundingGoal, "Funding goal not reached"); - - uint256 amount = address(this).balance; - totalFundsRaised = 0; - - (bool success, ) = payable(owner).call{value: amount}(""); - require(success, "Transfer failed."); - } - - function getTotalFundsRaised() public view returns (uint256) { - return totalFundsRaised; - } - - function getFundingGoal() public view returns (uint256) { - return fundingGoal; - } - } - ``` - :: -:: - -The `CrowdfundingCampaign` contract is designed for project crowdfunding. -Owned and deployed with a set funding goal, it features: - -- A constructor to initialize the campaign's funding target. -- The `contribute` method to log funds, triggering `ContributionReceived` and `GoalReached` events. -- The `withdrawFunds` method, allowing the owner to collect accumulated funds post-goal achievement. - -Smart contracts deployed to ZKsync must be compiled using our custom compiler. -For this particular guide we are making use of `zksolc`. - -To compile the contracts in the project, run the following command: - -```bash -forge build --zksync -``` - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -[⠒] Compiling... -[⠃] Compiling 2 files with 0.8.20 -[⠊] Solc 0.8.20 finished in 736.48ms -Compiler run successful! -Compiling contracts for ZKsync Era with zksolc v1.4.0 -``` - -The compiled zkEVM artifacts will be located in the `/zkout` folder, and the solc artifacts will be -located in the `/out` folder. - -### Deploy the CrowdfundingCampaign contract - -The deployment script is located at [`/script/Deploy.s.sol`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/foundry/hello-zksync/script/Deploy.s.sol). - -```solidity [Deploy.s.sol] -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Script.sol"; -import "../src/Crowdfund.sol"; - -contract DeployCrowdfundContract is Script { - function run() external { - uint256 deployerPrivateKey = vm.envUint("WALLET_PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - - uint256 fundingGoalInWei = 0.02 ether; - new CrowdfundingCampaign(fundingGoalInWei); - - vm.stopBroadcast(); - } -} -``` - -**Key Components:** - -- **Constructor Argument:** The `CrowdfundingCampaign` contract is initialized with -a single constructor argument, `fundingGoalInWei`. -- **Broadcast Method:** The deployment uses `vm.startBroadcast(deployerPrivateKey)` to begin -the transaction broadcast and `vm.stopBroadcast()` to end it, facilitating the actual deployment of the contract on the blockchain. - -Execute the deployment command. - -```bash -forge script script/Deploy.s.sol:DeployCrowdfundContract --account myKeystore --sender --rpc-url inMemoryNode --broadcast --zksync -``` - -Upon successful deployment, you'll receive output detailing the deployment process, -including the contract address, transaction hash, and block number deployed to: - -```bash -... -✅ [Success]Hash: 0x69f5f1f0f5b3fa12ed2fbab4d6bb6edc02bbfff2f8c414d8171cc8295250296c -Contract Address: 0xB0C0d3d02c270b6ABe4862EA90bBa1Af192314a8 -Block: 1491370 -Paid: 0.0001168854 ETH (1168854 gas * 0.1 gwei) -``` - -🥳 Congratulations! Your smart contract is now deployed. 🚀 diff --git a/content/00.build/05.start-coding/10.zksync-101/_hello-zksync/_hardhat_deploy_contract.md b/content/00.build/05.start-coding/10.zksync-101/_hello-zksync/_hardhat_deploy_contract.md deleted file mode 100644 index ab392540..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_hello-zksync/_hardhat_deploy_contract.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -title: Hardhat | Deploy Contract ---- - -Run the following command in your terminal to initialize the project. - -```sh -zksync-cli create --template qs-hello-zksync hello-zksync-quickstart -cd hello-zksync-quickstart -``` - -## Update the hardhat.config.ts - -Since we are using the "In memory node" with ZKsync CLI, we need to set the default network Hardhat uses -for deploying. - -Open up the `hardhat.config.ts` file and set the `defaultNetwork` to `inMemoryNode`. - -```ts -// ... -const config: HardhatUserConfig = { - defaultNetwork: "inMemoryNode", -// ... -``` - -## Compile the CrowdfundingCampaign.sol contract - -This guide introduces a crowdfunding campaign contract aimed at supporting Zeek's creative ventures. -Let's start by reviewing the starter contract in the [`contracts/` directory](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/hello-zksync/contracts/Crowdfund.sol). - -::drop-panel - ::panel{label="CrowdfundingCampaign.sol"} - ```solidity [CrowdfundingCampaign.sol] - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - - contract CrowdfundingCampaign { - address public owner; - uint256 public fundingGoal; - uint256 public totalFundsRaised; - mapping(address => uint256) public contributions; - - event ContributionReceived(address contributor, uint256 amount); - event GoalReached(uint256 totalFundsRaised); - - constructor(uint256 _fundingGoal) { - owner = msg.sender; - fundingGoal = _fundingGoal; - } - - function contribute() public payable { - require(msg.value > 0, "Contribution must be greater than 0"); - contributions[msg.sender] += msg.value; - totalFundsRaised += msg.value; - - emit ContributionReceived(msg.sender, msg.value); - - if (totalFundsRaised >= fundingGoal) { - emit GoalReached(totalFundsRaised); - } - } - - function withdrawFunds() public { - require(msg.sender == owner, "Only the owner can withdraw funds"); - require(totalFundsRaised >= fundingGoal, "Funding goal not reached"); - - uint256 amount = address(this).balance; - totalFundsRaised = 0; - - (bool success, ) = payable(owner).call{value: amount}(""); - require(success, "Transfer failed."); - } - - function getTotalFundsRaised() public view returns (uint256) { - return totalFundsRaised; - } - - function getFundingGoal() public view returns (uint256) { - return fundingGoal; - } - } - ``` - :: -:: - -The `CrowdfundingCampaign` contract is designed for project crowdfunding. -This contract features: - -- A constructor to initialize the campaign's funding target. -- The `contribute` method to log funds, triggering `ContributionReceived` and `GoalReached` events. -- The `withdrawFunds` method, allowing the owner to collect accumulated funds post-goal achievement. - -:display-partial{path = "/_partials/_compile-solidity-contracts"} - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.1 and solc v0.8.17 -Compiling 15 Solidity files -Successfully compiled 15 Solidity files -``` - -The compiled artifacts will be located in the `/artifacts-zk` folder. - -## Deploy the contract - -The deployment script is located at [`/deploy/deploy.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/hello-zksync/deploy/deploy.ts). - -```typescript [deploy.ts] -import { deployContract } from "./utils"; - -// An example of a basic deploy script -// It will deploy a CrowdfundingCampaign contract to selected network -// `parseEther` converts ether to wei, and `.toString()` ensures serialization compatibility. -export default async function () { - const contractArtifactName = "CrowdfundingCampaign"; - const constructorArguments = [ethers.parseEther('.02').toString()]; - await deployContract(contractArtifactName, constructorArguments); -} -``` - -**Key Components:** - -- **contractArtifactName:** Identifies the `CrowdfundingCampaign` contract for deployment. -- **constructorArguments:** Sets initialization parameters for the contract. In this case, -the fundraising goal, converted from ether to `wei` to match Solidity's `uint256` type. - -1. Execute the deployment command from `package.json`. - -```bash [npm] -npm run deploy -``` - -Upon successful deployment, you'll receive output detailing the deployment process, -including the contract address, source, and encoded constructor arguments: - -```bash -Starting deployment process of "CrowdfundingCampaign"... -Estimated deployment cost: 0.000501 ETH - -"CrowdfundingCampaign" was successfully deployed: - - Contract address: 0x4E3404F21b29d069539e15f8f9E712CeAE39d90C - - Contract source: contracts/Crowdfund.sol:CrowdfundingCampaign - - Encoded constructor arguments: 0x00000000000000000000000000000000000000000000000000470de4df820000 - -Requesting contract verification... -Your verification ID is: 10067 -Contract successfully verified on ZKsync block explorer! -``` - -🥳 Congratulations! Your smart contract is now deployed. 🚀 diff --git a/content/00.build/05.start-coding/10.zksync-101/_partials/_setup-wallet.md b/content/00.build/05.start-coding/10.zksync-101/_partials/_setup-wallet.md deleted file mode 100644 index 47e95dcb..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_partials/_setup-wallet.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Set up your wallet ---- - -Deploying contracts on the %%zk_testnet_name%% requires having testnet ETH. -If you're working within the local development environment, -you can utilize pre-configured rich wallets and skip this step. -For testnet deployments, you should have your wallet funded from the [previous step](/build/start-coding/zksync-101#fund-your-wallet). diff --git a/content/00.build/05.start-coding/10.zksync-101/_paymasters/_approval_paymaster_flow.md b/content/00.build/05.start-coding/10.zksync-101/_paymasters/_approval_paymaster_flow.md index 9aeece40..cdbbd2c1 100644 --- a/content/00.build/05.start-coding/10.zksync-101/_paymasters/_approval_paymaster_flow.md +++ b/content/00.build/05.start-coding/10.zksync-101/_paymasters/_approval_paymaster_flow.md @@ -3,260 +3,59 @@ title: Approval Paymaster description: Learn to deploy contract factories in the ZKsync environment. --- -Run the following command in your terminal to initialize the project. - -```sh -zksync-cli create --template qs-paymaster contract-paymaster-quickstart -cd contract-paymaster-quickstart -``` - -## Set up your wallet - -:display-partial{path = "/build/start-coding/zksync-101/_partials/_setup-wallet"} - ---- - ## Understanding the `ApprovalPaymaster` contract -Let's start by reviewing the `ApprovalFlowPaymaster.sol` contract in the `contracts/` directory: - -::drop-panel - ::panel{label="contracts/ApprovalFlowPaymaster.sol"} - ```solidity [contracts/ApprovalFlowPaymaster.sol] - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - - import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol"; - import {IPaymasterFlow} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol"; - import {TransactionHelper, Transaction} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; - - import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; - - import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - import "@openzeppelin/contracts/access/Ownable.sol"; - - /// @notice This smart contract pays the gas fees for accounts with balance of a specific ERC20 token. It makes use of the approval-based flow paymaster. - contract ApprovalFlowPaymaster is IPaymaster, Ownable { - uint256 constant PRICE_FOR_PAYING_FEES = 1; - - address public allowedToken; - - modifier onlyBootloader() { - require( - msg.sender == BOOTLOADER_FORMAL_ADDRESS, - "Only bootloader can call this method" - ); - _; - } - - constructor() { - // Sepolia CROWN token address - allowedToken = 0x927488F48ffbc32112F1fF721759649A89721F8F; - } - - function validateAndPayForPaymasterTransaction( - bytes32, - bytes32, - Transaction calldata _transaction - ) - external - payable - onlyBootloader - returns (bytes4 magic, bytes memory context) - { - // Default to transaction acceptance - magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; - require( - _transaction.paymasterInput.length >= 4, - "The standard paymaster input must be at least 4 bytes long" - ); - - bytes4 paymasterInputSelector = bytes4( - _transaction.paymasterInput[0:4] - ); - // Check if it's approval-based flow - if (paymasterInputSelector == IPaymasterFlow.approvalBased.selector) { - (address token, uint256 amount, bytes memory data) = abi.decode( - _transaction.paymasterInput[4:], - (address, uint256, bytes) - ); - - // Ensure the token is the allowed one - require(token == allowedToken, "Invalid token"); - - // Check user's allowance - address userAddress = address(uint160(_transaction.from)); - address thisAddress = address(this); - uint256 providedAllowance = IERC20(token).allowance(userAddress, thisAddress); - require( - providedAllowance >= PRICE_FOR_PAYING_FEES, - "Min allowance too low" - ); - - uint256 requiredETH = _transaction.gasLimit * _transaction.maxFeePerGas; - try IERC20(token).transferFrom(userAddress, thisAddress, amount) {} - catch (bytes memory revertReason) { - if (revertReason.length <= 4) { - revert("Failed to transferFrom from user's account"); - } else { - assembly { - revert(add(0x20, revertReason), mload(revertReason)) - } - } - } - - (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{value: requiredETH}(""); - require(success, "Failed to transfer tx fee to bootloader."); - } else { - revert("Unsupported paymaster flow"); - } - } - - function postTransaction( - bytes calldata _context, - Transaction calldata _transaction, - bytes32, - bytes32, - ExecutionResult _txResult, - uint256 _maxRefundedGas - ) external payable override onlyBootloader {} - - function withdraw(address _to) external onlyOwner { - (bool success, ) = payable(_to).call{value: address(this).balance}(""); - require(success, "Failed to withdraw funds from paymaster."); - } - - receive() external payable {} - } - ``` - :: -:: +Let's start by reviewing the contract at [`contracts/4-paymaster/approval/ApprovalFlowPaymaster.sol`][approval-flow-paymaster-sol]: **Key components:** - The `ApprovalFlowPaymaster` contract allows for transactions costs to be covered using an allowed ERC20 token at the exchange of 1. -- **Allowed Token:** Transactions are facilitated using the `CROWN` token at address [0x927488F48ffbc32112F1fF721759649A89721F8F](https://sepolia.explorer.zksync.io/address/0x927488F48ffbc32112F1fF721759649A89721F8F#contract), -with a fee set at a constant value of 1. -- **`validateAndPayForPaymasterTransaction` Method:** This critical method evaluates transactions -to decide if the contract will cover the gas fees. It confirms the token used matches the allowed token -and checks if the token allowance is adequate. If conditions are met, it proceeds to transfer funds calculated -as `tx.gasprice * tx.gasLimit` to the `bootloader`. +- **Allowed Token:** Transactions are facilitated using the + [`CROWN`][crown-token-sol] + token that we'll deploy along with the `ApprovalPaymaster` contract, + with a fee set at a constant value of 1. +- **`validateAndPayForPaymasterTransaction` Method:** + This critical method evaluates transactions + to decide if the contract will cover the gas fees. It confirms the token used matches the allowed token + and checks if the token allowance is adequate. If conditions are met, it proceeds to transfer funds calculated + as `tx.gasprice * tx.gasLimit` to the `bootloader`. - **`postTransaction`** Method: An optional method invoked -post-transaction execution, provided the transaction doesn't fail -due to out-of-gas errors. It receives several parameters, including the transaction's context and result, aiding in finalizing paymaster duties. -- **`onlyBootloader`** Modifier: Ensures that certain methods are exclusively callable by the system's bootloader, -adding an extra layer of security and control. + post-transaction execution, provided the transaction doesn't fail + due to out-of-gas errors. It receives several parameters, including the transaction's context and result, aiding in finalizing paymaster duties. + - **`onlyBootloader`** Modifier: Ensures that certain methods are exclusively callable by the system's bootloader, + adding an extra layer of security and control. --- -## Compile and deploy the `ApprovalFlowPaymaster` contract - -:display-partial{path = "/_partials/_compile-solidity-contracts"} - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. +## Deploy the `ApprovalFlowPaymaster` and `CrownToken` contracts -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.0 and solc v0.8.17 -Compiling 1 Solidity file -Successfully compiled 1 Solidity file -``` +Run the npm script `compile` to compile the contracts: -The compiled artifacts will be located in the `/artifacts-zk` folder. - -The script to deploy the `ApprovalFlowPaymaster` contract is located at [`/deploy/deployApprovalFlowPaymaster.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/paymaster/deploy/deployApprovalFlowPaymaster.ts). - -```typescript [deployApprovalFlowPaymaster.ts] -import { deployContract, getWallet, getProvider } from "./utils"; -import { ethers } from "ethers"; - -// An example of a basic deploy script -// It will deploy a CrowdfundingCampaign contract to selected network -// `parseEther` converts ether to wei, and `.toString()` ensures serialization compatibility. -export default async function() { - const contractArtifactName = "ApprovalFlowPaymaster"; - const constructorArguments = []; - const contract = await deployContract( - contractArtifactName, - constructorArguments - ); - const wallet = getWallet(); - const provider = getProvider(); - - // Supplying paymaster with ETH - // Paymaster will receive CROWN tokens from users and - // cover the gas fees for the transactions using ETH - await ( - await wallet.sendTransaction({ - to: contract.target, - value: ethers.parseEther("0.005"), - }) - ).wait(); - - let paymasterBalance = await provider.getBalance(contract.target.toString()); - console.log(`Paymaster ETH balance is now ${paymasterBalance.toString()}`); -} +```bash [npm] +npm run compile ``` -**Key Components:** - -- **`deployContract` Method:** Utilized for deploying the `ApprovalFlowPaymaster` contract. This method takes the name of the -contract and any constructor arguments needed for initialization, -mirroring the deployment process used for the `CrowdfundingCampaign` contract. -- **Funding the Paymaster:** An important step where the deployed `ApprovalFlowPaymaster` contract is funded with ETH -to cover transaction fees for users. The script sends a transaction -from the deployer's wallet to the paymaster contract, ensuring it has sufficient balance to operate. +The script to deploy the `ApprovalFlowPaymaster` contract is located at [`/deploy/4-paymaster/approval/deploy.ts`][deploy-script]. -Execute the deployment command corresponding to your package manager. The default command -deploys to the configured network in your Hardhat setup. For local deployment, append -`--network inMemoryNode` to deploy to the local in-memory node running. +The script deploys the `Crown` token and the `ApprovalFlowPaymaster` contracts. +It also funds the `ApprovalFlowPaymaster` with "0.005" ETH to use +for transaction payments. -::code-group +Run the deploy script from npm using the following command: ```bash [npm] -npx hardhat deploy-zksync --script deployApprovalFlowPaymaster.ts -# To deploy the contract on local in-memory node: -# npx hardhat deploy-zksync --script deployApprovalFlowPaymaster.ts --network inMemoryNode +npm run deploy:approval-paymaster ``` -```bash [yarn] -yarn hardhat deploy-zksync --script deployApprovalFlowPaymaster.ts -# To deploy the contract on local in-memory node: -# yarn hardhat deploy-zksync --script deployApprovalFlowPaymaster.ts --network inMemoryNode -``` - -```bash [pnpm] -pnpm exec hardhat deploy-zksync --script deployApprovalFlowPaymaster.ts -# To deploy the contract on local in-memory node: -# pnpm exec hardhat deploy-zksync --script deployApprovalFlowPaymaster.ts --network inMemoryNode -``` - -```bash [bun] -bun run hardhat deploy-zksync --script deployApprovalFlowPaymaster.ts -# To deploy the contract on local in-memory node: -# bun run hardhat deploy-zksync --script deployApprovalFlowPaymaster.ts --network inMemoryNode -``` - -:: - -Upon successful deployment, you'll receive output detailing the deployment process, -including the contract address, source, and encoded constructor arguments: +On success, the console will return the addresses of the `Crown` token +as well as the `ApprovalFlowPaymaster`. ```bash -Starting deployment process of "ApprovalFlowPaymaster"... -Estimated deployment cost: 0.0006278488 ETH - -"ApprovalFlowPaymaster" was successfully deployed: - - Contract address: 0x4653CDB4D46c7CdFc5B1ff14ca1B15Db2B0b7819 - - Contract source: contracts/ApprovalFlowPaymaster.sol:ApprovalFlowPaymaster - - Encoded constructor arguments: 0x - -Requesting contract verification... -Your verification ID is: 10923 -Contract successfully verified on ZKsync block explorer! -Paymaster ETH balance is now 5000000000000000 +CrownToken contract deployed at <0xCROWN_TOKEN_ADDRESS> +ApprovalFlowPaymaster contract deployed at <0xYOUR_PAYMASTER_ADDRESS> + +Paymaster ETH balance is now 0.005 ``` --- @@ -264,103 +63,43 @@ Paymaster ETH balance is now 5000000000000000 ## Interact with the `ApprovalFlowPaymaster` contract This section will navigate you through the steps to interact with the -[`ApprovalFlowPaymaster`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/paymaster/contracts/ApprovalFlowPaymaster.sol) -contract, using it to cover transaction fees for your operation. +`ApprovalFlowPaymaster` contract, using it to cover transaction fees for your operation. ### Obtain CROWN tokens The `ApprovalFlowPaymaster` requires CROWN tokens to cover transaction gas costs. To use this -paymaster, you will first need to acquire CROWN tokens by minting them yourself. Follow these -steps to mint CROWN tokens: - -1. Go to the [CROWN token contract on Sepolia](https://sepolia.explorer.zksync.io/address/0x927488F48ffbc32112F1fF721759649A89721F8F#contract). -2. Click on the **Contract** tab. -3. Navigate to the **Write** tab. -4. Locate the **mint** function, which is typically labeled as **6.mint**: - - In the `_to` field, enter your wallet address where you want the tokens to be deposited. - - In the `_amount` field, specify the number of tokens you wish to mint. -5. Click on the **mint** button to execute the transaction. - -Here is a visual guide to assist you: -![CROWN mint](/images/quickstart-paymasters/crown-mint.png) - -Ensure that your wallet is connected and configured for the Sepolia network before attempting to mint tokens. - -### Interaction script - -The interaction script is situated in the `/deploy/interact/` directory, named [`interactWithApprovalFlowPaymaster.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/paymaster/deploy/interact/interactWithApprovalFlowPaymaster.ts). - -Ensure the `CONTRACT_ADDRESS` and `PAYMASTER_ADDRESS` variables are set to your deployed contract and paymaster addresses, respectively. - -::drop-panel - ::panel{label="interactWithApprovalFlowPaymaster.ts"} - - ```typescript [interactWithApprovalFlowPaymaster.ts] - import * as hre from "hardhat"; - import { getWallet, getProvider } from "../utils"; - import { ethers } from "ethers"; - import { utils } from "zksync-ethers"; - - // Address of the contract to interact with - const CONTRACT_ADDRESS = "YOUR-CONTRACT-ADDRESS"; - const PAYMASTER_ADDRESS = "YOUR-PAYMASTER-ADDRESS"; - // Sepolia CROWN token address - const TOKEN_ADDRESS = "0x927488F48ffbc32112F1fF721759649A89721F8F" - - if (!CONTRACT_ADDRESS || !PAYMASTER_ADDRESS) - throw new Error("Contract and Paymaster addresses are required."); - - export default async function() { - console.log(`Running script to interact with contract ${CONTRACT_ADDRESS} using paymaster ${PAYMASTER_ADDRESS}`); - - // Load compiled contract info - const contractArtifact = await hre.artifacts.readArtifact( - "CrowdfundingCampaignV2" - ); - const provider = getProvider(); - // Initialize contract instance for interaction - const contract = new ethers.Contract( - CONTRACT_ADDRESS, - contractArtifact.abi, - getWallet() - ); - - const contributionAmount = ethers.parseEther("0.001"); - // Get paymaster params for the ApprovalBased paymaster - const paymasterParams = utils.getPaymasterParams(PAYMASTER_ADDRESS, { - type: "ApprovalBased", - token: TOKEN_ADDRESS, - minimalAllowance: 1n, - innerInput: new Uint8Array(), - }); - - const gasLimit = await contract.contribute.estimateGas({ - value: contributionAmount, - customData: { - gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, - paymasterParams: paymasterParams, - }, - }); - - const transaction = await contract.contribute({ - value: contributionAmount, - maxPriorityFeePerGas: 0n, - maxFeePerGas: await provider.getGasPrice(), - gasLimit, - // Pass the paymaster params as custom data - customData: { - gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, - paymasterParams, - }, - }); - console.log(`Transaction hash: ${transaction.hash}`); - - await transaction.wait(); - } - ``` - - :: -:: +paymaster, you will first need to acquire CROWN tokens by minting them yourself. +We will use ZKsync CLI to interact with the Crown token contract: + +Run the following in your console to mint yourself Crown tokens. +Replace the contract address with your Crown token contract's address. +The private key is for the local rich wallet used to deploy the smart contracts. + +```bash +zksync-cli contract write \ +--chain in-memory-node \ +--abi artifacts-zk/contracts/4-paymaster/approval/CrownToken.sol/CrownToken.json \ +--contract <0xCROWN_TOKEN_ADDRESS> \ +--pk 0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 +``` + +The CLI will prompt for the method, navigate with the arrow keys and press **Enter** +on `mint(address to, uint256 amount)`. + +The next prompt will ask for an address, we will use the local rich wallet address, `0x36615Cf349d7F6344891B1e7CA7C72883F5dc049`. + +The final prompt will ask for the amount to mint, type in `500000000000000000`. + + + +### Interact with the ApprovalFlowPaymaster contract + +We will interact with the `ApprovalFlowPaymaster` by creating a `CrowdfundCampaign` and have the paymaster pay for the contribution transaction. + +The interaction script is found at [`deploy/4-paymaster/approval/interact.ts`][interact-script]. + +Set the `YOUR_PAYMASTER_ADDRESS` and `YOUR_TOKEN_ADDRESS` variables to the +addresses of your paymaster and crown token addresses. **Key Components:** @@ -373,34 +112,24 @@ of the ERC20 token, and the minimum allowance set to 1. in transactions. This allows the paymaster to cover transaction fees using the `CROWN` token, providing a seamless experience for users. -Execute the command corresponding to your package manager: - -::code-group +Run the npm script that interacts with the approval paymaster: ```bash [npm] -npx hardhat deploy-zksync --script interact/interactWithApprovalFlowPaymaster.ts -``` - -```bash [yarn] -yarn hardhat deploy-zksync --script interact/interactWithApprovalFlowPaymaster.ts -``` - -```bash [pnpm] -pnpm exec hardhat deploy-zksync --script interact/interactWithApprovalFlowPaymaster.ts -``` - -```bash [bun] -bun run hardhat deploy-zksync --script interact/interactWithApprovalFlowPaymaster.ts +npm run interact:approval-paymaster ``` -:: - Upon successful usage, you'll receive output detailing the transaction: ```bash -Running script to interact with contract 0x68E8533acE01019CB8D07Eca822369D5De71b74D using paymaster 0x4653CDB4D46c7CdFc5B1ff14ca1B15Db2B0b7819 -Estimated gas limit: 459220 -Transaction hash: 0x6a5a5e8e7d7668a46260b6daf19c7a5579fa4a5ba4591977a944abb1a618187a +Deploying a CrowdfundingCampaign contract... +Contributing 0.0001 to the crowdfund contract... +Transaction hash: 0xf26ee884ef116b226a45b45ec689ca1f2e9367f83164959b27a960802f89e627 +Contribution successful! ``` -🎉 Great job! You've successfully interacted with the `CrowdfundingCampaignV2` using a paymaster to cover the transaction fees using the `CROWN` token. +🎉 Great job! You've successfully interacted with the `CrowdfundingCampaign` using a paymaster to cover the transaction fees using the `CROWN` token. + +[approval-flow-paymaster-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/4-paymaster/approval/ApprovalFlowPaymaster.sol +[crown-token-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/4-paymaster/approval/CrownToken.sol +[deploy-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/4-paymaster/approval/deploy.ts +[interact-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/4-paymaster/approval/interact.ts diff --git a/content/00.build/05.start-coding/10.zksync-101/_paymasters/_general_paymaster_flow.md b/content/00.build/05.start-coding/10.zksync-101/_paymasters/_general_paymaster_flow.md index 9d865c59..c73f11e1 100644 --- a/content/00.build/05.start-coding/10.zksync-101/_paymasters/_general_paymaster_flow.md +++ b/content/00.build/05.start-coding/10.zksync-101/_paymasters/_general_paymaster_flow.md @@ -3,126 +3,12 @@ title: General Paymaster description: Learn to deploy contract factories in the ZKsync environment. --- -Run the following command in your terminal to initialize the project. - -```sh -zksync-cli create --template qs-paymaster contract-paymaster-quickstart -cd contract-paymaster-quickstart -``` - -## Update the hardhat.config.ts - -Since we are using the "In memory node" with ZKsync CLI, we need to set the default network Hardhat uses -for deploying. - -Open up the `hardhat.config.ts` file and set the `defaultNetwork` to `inMemoryNode`. - -```ts -// ... -const config: HardhatUserConfig = { - defaultNetwork: "inMemoryNode", -// ... -``` - -::callout{icon="i-heroicons-information-circle" color="blue"} -If you have not started up a local node yet from the Getting started page, run `zksync-cli dev start`. -Make sure you are running the "In memory node" option. -:: - ---- - ## Understanding the `GaslessPaymaster` contract Let's start by reviewing the -[`contracts/GaslessPaymaster.sol`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/paymaster/contracts/GaslessPaymaster.sol) +[`contracts/4-paymaster/gasless/GaslessPaymaster.sol`][gasless-paymaster-sol] contract: -::drop-panel - ::panel{label="contracts/GaslessPaymaster.sol"} - ```solidity [contracts/GaslessPaymaster.sol] - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - - import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol"; - import {IPaymasterFlow} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol"; - import {TransactionHelper, Transaction} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; - import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; - import "@openzeppelin/contracts/access/Ownable.sol"; - - /// @notice This contract does not include any validations other than using the paymaster general flow. - contract GaslessPaymaster is IPaymaster, Ownable { - modifier onlyBootloader() { - require( - msg.sender == BOOTLOADER_FORMAL_ADDRESS, - "Only bootloader can call this method" - ); - // Continue execution if called from the bootloader. - _; - } - - function validateAndPayForPaymasterTransaction( - bytes32, - bytes32, - Transaction calldata _transaction - ) - external - payable - onlyBootloader - returns (bytes4 magic, bytes memory context) - { - // By default we consider the transaction as accepted. - magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; - require( - _transaction.paymasterInput.length >= 4, - "The standard paymaster input must be at least 4 bytes long" - ); - - bytes4 paymasterInputSelector = bytes4( - _transaction.paymasterInput[0:4] - ); - if (paymasterInputSelector == IPaymasterFlow.general.selector) { - // Note, that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit, - // neither paymaster nor account are allowed to access this context variable. - uint256 requiredETH = _transaction.gasLimit * - _transaction.maxFeePerGas; - - // The bootloader never returns any data, so it can safely be ignored here. - (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ - value: requiredETH - }(""); - require( - success, - "Failed to transfer tx fee to the Bootloader. Paymaster balance might not be enough." - ); - } else { - revert("Unsupported paymaster flow in paymasterParams."); - } - } - - function postTransaction( - bytes calldata _context, - Transaction calldata _transaction, - bytes32, - bytes32, - ExecutionResult _txResult, - uint256 _maxRefundedGas - ) external payable override onlyBootloader { - // Refunds are not supported yet. - } - - function withdraw(address payable _to) external onlyOwner { - // send paymaster funds to the owner - uint256 balance = address(this).balance; - (bool success, ) = _to.call{value: balance}(""); - require(success, "Failed to withdraw funds from paymaster."); - } - - receive() external payable {} - } - ``` - :: -:: - **Key components:** - The `GaslessPaymaster` contract ensures that transaction fees are handled automatically without user intervention. @@ -139,51 +25,13 @@ exclusively callable by the system's bootloader, adding an extra layer of securi ## Compile and deploy the `GaslessPaymaster` contract -:display-partial{path = "/_partials/_compile-solidity-contracts"} +Run the npm script `compile` to compile the contracts: -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.0 and solc v0.8.17 -Compiling 4 Solidity file -Successfully compiled 4 Solidity file +```bash [npm] +npm run compile ``` -The compiled artifacts will be located in the `/artifacts-zk` folder. - -The script to deploy the `GaslessPaymaster` is located at [`/deploy/deployGaslessPaymaster.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/paymaster/deploy/deployGaslessPaymaster.ts). - -```typescript [deploy/deployGaslessPaymaster.ts] -import { deployContract, getWallet, getProvider } from "./utils"; -import { ethers } from "ethers"; - -// An example of a basic deploy script -// It will deploy a CrowdfundingCampaign contract to selected network -// `parseEther` converts ether to wei, and `.toString()` ensures serialization compatibility. -export default async function() { - const contractArtifactName = "GaslessPaymaster"; - const constructorArguments = []; - const contract = await deployContract( - contractArtifactName, - constructorArguments - ); - const wallet = getWallet(); - const provider = getProvider(); - - // Supplying paymaster with ETH - await ( - await wallet.sendTransaction({ - to: contract.target, - value: ethers.parseEther("0.005"), - }) - ).wait(); - - let paymasterBalance = await provider.getBalance(contract.target.toString()); - console.log(`Paymaster ETH balance is now ${paymasterBalance.toString()}`); -} -``` +The script to deploy the `GaslessPaymaster` is located at [`/deploy/4-paymaster/gasless/deploy.ts][deploy-script]. **Key Components:** @@ -194,114 +42,32 @@ mirroring the deployment process used for the `CrowdfundingCampaign` contract. to cover transaction fees for users. The script sends a transaction from the deployer's wallet to the paymaster contract, ensuring it has sufficient balance to operate. -Execute the deployment command corresponding to your package manager. The default command -deploys to the configured network in your Hardhat setup. For local deployment, append -`--network inMemoryNode` to deploy to the local in-memory node running. +Execute the deploy npm script command to upgrade: ```bash [npm] -npx hardhat deploy-zksync --script deployGaslessPaymaster.ts +npm run deploy:gasless-paymaster ``` Upon successful deployment, you'll receive output detailing the deployment process, including the contract address, source, and encoded constructor arguments: ```bash -Starting deployment process of "GaslessPaymaster"... -Estimated deployment cost: 0.0004922112 ETH - -"GaslessPaymaster" was successfully deployed: - - Contract address: 0x6f72f0d7bDba2E2a923beC09fBEE64cD134680F2 - - Contract source: contracts/GaslessPaymaster.sol:GaslessPaymaster - - Encoded constructor arguments: 0x +GaslessPaymaster contract deployed at <0xYOUR_PAYMASTER_ADDRESS> -Paymaster ETH balance is now 5000000000000000 +Paymaster ETH balance is now 0.005 ``` --- ## Interact with the GaslessPaymaster contract -This section will navigate you through the steps to interact with the `GaslessPaymaster` contract, -using it to cover transaction fees for your operation. - -The interaction script is situated in the `/deploy/interact/` directory, named [`interactWithGaslessPaymaster.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/paymaster/deploy/interact/interactWithGaslessPaymaster.ts). - -Ensure the `CONTRACT_ADDRESS` and `PAYMASTER_ADDRESS` variables are set to your deployed contract and paymaster addresses, respectively. - -::drop-panel - ::panel{label="deploy/interact/interactWithGaslessPaymaster.ts"} +We will interact with the `GaslessPaymaster` by creating a `CrowdfundCampaign` +and have the paymaster pay for the transaction fees for your contribution. - ```typescript [interactWithGaslessPaymaster.ts] - import * as hre from "hardhat"; - import { getWallet, getProvider } from "../utils"; - import { ethers } from "ethers"; - import { utils } from "zksync-ethers"; +The interaction script is situated in the `/deploy/4-paymaster/gasless` directory, +named [`interact.ts`][interact-script]. - // Address of the contract to interact with - const CONTRACT_ADDRESS = "YOUR-CONTRACT-ADDRESS"; - const PAYMASTER_ADDRESS = "YOUR-PAYMASTER-ADDRESS"; - if (!CONTRACT_ADDRESS || !PAYMASTER_ADDRESS) - throw new Error("Contract and Paymaster addresses are required."); - - export default async function() { - console.log(`Running script to interact with contract ${CONTRACT_ADDRESS} using paymaster ${PAYMASTER_ADDRESS}`); - - // Load compiled contract info - const contractArtifact = await hre.artifacts.readArtifact( - "CrowdfundingCampaignV2" - ); - - // Initialize contract instance for interaction - const contract = new ethers.Contract( - CONTRACT_ADDRESS, - contractArtifact.abi, - getWallet() - ); - - const provider = getProvider(); - let balanceBeforeTransaction = await provider.getBalance(getWallet().address); - console.log(`Wallet balance before contribution: ${ethers.formatEther(balanceBeforeTransaction)} ETH`); - - const contributionAmount = ethers.parseEther("0.01"); - // Get paymaster params - const paymasterParams = utils.getPaymasterParams(PAYMASTER_ADDRESS, { - type: "General", - innerInput: new Uint8Array(), - }); - - const gasLimit = await contract.contribute.estimateGas({ - value: contributionAmount, - customData: { - gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, - paymasterParams: paymasterParams, - }, - }); - - const transaction = await contract.contribute({ - value: contributionAmount, - maxPriorityFeePerGas: 0n, - maxFeePerGas: await provider.getGasPrice(), - gasLimit, - // Pass the paymaster params as custom data - customData: { - gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, - paymasterParams, - }, - }); - console.log(`Transaction hash: ${transaction.hash}`); - - await transaction.wait(); - - let balanceAfterTransaction = await provider.getBalance(getWallet().address); - // Check the wallet balance after the transaction - // We only pay the contribution amount, so the balance should be less than before - // Gas fees are covered by the paymaster - console.log(`Wallet balance after contribution: ${ethers.formatEther(balanceAfterTransaction)} ETH`); - } - ``` - - :: -:: +Ensure the `YOUR_PAYMASTER_ADDRESS` variable is set to your deployed paymaster address. **Key Components:** @@ -313,19 +79,23 @@ used and the type of paymaster flow, which in this case is `General`. in transactions. This allows the paymaster to cover transaction fees, providing a seamless experience for users. -Execute the command corresponding to your package manager: +Run the npm script that interacts with the gasless paymaster: ```bash [npm] -npx hardhat deploy-zksync --script interact/interactWithGaslessPaymaster.ts +npm run interact:gasless-paymaster ``` Upon successful usage, you'll receive output detailing the transaction: ```bash -Running script to interact with contract 0x68E8533acE01019CB8D07Eca822369D5De71b74D using paymaster 0x6f72f0d7bDba2E2a923beC09fBEE64cD134680F2 -Wallet balance before contribution: 5.879909434005856127 ETH -Transaction hash: 0x41c463abf7905552b69b25e7918374aab27f2d7e8cbebe212a0eb6ef8deb81e8 -Wallet balance after contribution: 5.869909434005856127 ETH +Deploying a CrowdfundingCampaign contract... +Contributing 0.01 to the crowdfund contract... +Transaction hash: 0x1281592537c81f4d4ca259022b649dc582b186911af8f6b3612568383ea99b1b +Contribution successful! ``` -🎉 Great job! You've successfully interacted with the `CrowdfundingCampaignV2` using a paymaster to cover the transaction fees. +🎉 Great job! You've successfully interacted with the `CrowdfundingCampaign` using a paymaster to cover the transaction fees. + +[gasless-paymaster-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/4-paymaster/gasless/GaslessPaymaster.sol +[deploy-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/4-paymaster/gasless/deploy.ts +[interact-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/4-paymaster/gasless/interact.ts diff --git a/content/00.build/05.start-coding/10.zksync-101/_testing/_foundry_contract_testing.md b/content/00.build/05.start-coding/10.zksync-101/_testing/_foundry_contract_testing.md deleted file mode 100644 index 2f2aedf1..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_testing/_foundry_contract_testing.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -title: Foundry | Contract Testing ---- - -:display-partial{path = "/_partials/_foundry_alpha_warning"} - -Run the following command in your terminal to initialize the Foundry project. - -```sh -zksync-cli create --template qs-fs-testing foundry-contract-testing-quickstart -cd foundry-contract-testing-quickstart -``` - ---- - -## Test the `CrowdfundingCampaign` contract - -Now that our setup is complete, it's time to focus on the core of this -guide - testing our `CrowdfundingCampaign.sol` contract. Here's a quick -refresher on its structure: - -::drop-panel - ::panel{label="src/CrowdfundingCampaign.sol"} - ```solidity [src/CrowdfundingCampaign.sol] - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - - contract CrowdfundingCampaign { - address public owner; - uint256 public fundingGoal; - uint256 public totalFundsRaised; - mapping(address => uint256) public contributions; - - event ContributionReceived(address contributor, uint256 amount); - event GoalReached(uint256 totalFundsRaised); - - constructor(uint256 _fundingGoal) { - owner = msg.sender; - fundingGoal = _fundingGoal; - } - - function contribute() public payable { - require(msg.value > 0, "Contribution must be greater than 0"); - contributions[msg.sender] += msg.value; - totalFundsRaised += msg.value; - - emit ContributionReceived(msg.sender, msg.value); - - if (totalFundsRaised >= fundingGoal) { - emit GoalReached(totalFundsRaised); - } - } - - function withdrawFunds() public { - require(msg.sender == owner, "Only the owner can withdraw funds"); - require(totalFundsRaised >= fundingGoal, "Funding goal not reached"); - - uint256 amount = address(this).balance; - totalFundsRaised = 0; - - (bool success, ) = payable(owner).call{value: amount}(""); - require(success, "Transfer failed."); - } - - function getTotalFundsRaised() public view returns (uint256) { - return totalFundsRaised; - } - - function getFundingGoal() public view returns (uint256) { - return fundingGoal; - } - } - ``` - :: -:: - -Thorough testing involves scrutinizing every function and aspect of our contract, -including potential failure scenarios. In this guide, we'll focus in on the `contribute` -method to ensure it's tested. - -As a challenge to hone your testing skills further, -consider devising additional tests for the `withdrawFunds`, `getTotalFundsRaised`, -and `getFundingGoal` methods, expanding your test coverage and reinforcing the -reliability of the contract. - -### Compile contract - -Smart contracts deployed to ZKsync must be compiled using our custom compiler. -For this particular guide we are making use of `zksolc`. - -To compile the contracts in the project, run the following command: - -```bash -forge build --zksync -``` - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -[⠒] Compiling... -[⠃] Compiling 22 files with 0.8.20 -[⠊] Solc 0.8.20 finished in 736.48ms -Compiler run successful! -Compiling contracts for ZKsync Era with zksolc v1.4.0 -``` - -The compiled zkEVM artifacts will be located in the `/zkout` folder, and the solc artifacts will be -located in the `/out` folder. - -### Run the test command - -This section describes the testing `CrowdfundingCampaign.sol` contract. Let's -start by reviewing the tests for `CrowdfundingCampaign.sol` contract provided -during the initialization step in the `/test` directory, specifically the -[`CrowdfundingCampaign.t.sol` file](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/foundry/testing/test/CrowdfundingCampaign.t.sol). - -```solidity [test/CrowdfundingCampaign.t.sol] -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; -import "forge-std/console.sol"; -import "../src/CrowdfundingCampaign.sol"; - -contract CrowdfundingCampaignTest is Test { - CrowdfundingCampaign campaign; - event GoalReached(uint256 totalFundsRaised); - address owner; - address addr1; - address addr2; - - function setUp() public { - owner = address(this); - - addr1 = vm.addr(1); - addr2 = vm.addr(2); - - campaign = new CrowdfundingCampaign(1 ether); - console.log("CrowdfundingCampaign deployed at: %s", address(campaign)); - } - - function test_RejectZeroContributions() public { - vm.expectRevert("Contribution must be greater than 0"); - campaign.contribute{value: 0}(); - } - - function test_AggregateContributions() public { - uint256 initialTotal = campaign.getTotalFundsRaised(); - - vm.prank(addr1); - vm.deal(addr1, 2 ether); - campaign.contribute{value: 0.5 ether}(); - - vm.prank(addr2); - vm.deal(addr2, 2 ether); - campaign.contribute{value: 0.3 ether}(); - - assertEq(campaign.getTotalFundsRaised(), initialTotal + 0.8 ether); - } - - function test_EmitGoalReachedWhenFundingGoalMet() public { - vm.prank(addr1); - vm.deal(addr1, 2 ether); - vm.expectEmit(true, true, false, true); - emit GoalReached(1 ether); - campaign.contribute{value: 1 ether}(); - } -} -``` - -- **Environment Setup**: Leverages Foundry's `Test` contract and setup functions -to prepare the test environment, ensuring a fresh state for each test case. -- **Deployment and Address Simulation**: Deploys the `CrowdfundingCampaign` contract -within the test setup and simulates addresses using Foundry's `vm.addr()` function for -various test actors. - -**`contribute` Method Tests:** - -- **Zero Contribution Validation**: Asserts that the contract rejects contribution -attempts with zero value, testing the contract's input validation logic. -- **Contribution Aggregation**: Confirms the contract's ability to correctly tally -contributions from various addresses, ensuring accurate tracking of the total funds raised. -- **Event Emission Upon Goal Achievement**: Utilizes Foundry's `vm.expectEmit` to -anticipate the `GoalReached` event when the funding goal is met, validating the -contract's event logic and state transitions. - -Execute the test command: - -```bash -forge test --zksync -``` - -Upon completion, the test suite will provide a summary of all executed tests, -indicating their success or failure: - -```bash -Ran 3 tests for test/CrowdfundingCampaign.t.sol:CrowdfundingCampaignTest -[PASS] test_AggregateContributions() (gas: 29204) -[PASS] test_EmitGoalReachedWhenFundingGoalMet() (gas: 18862) -[PASS] test_RejectZeroContributions() (gas: 8148) -Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 44.03ms (43.94ms CPU time) - -Ran 1 test suite in 48.11ms (44.03ms CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests) -``` - -🎉 Congratulations! The `contribute` method of the `CrowdfundingCampaign` contract -has been thoroughly tested and is ready for action. diff --git a/content/00.build/05.start-coding/10.zksync-101/_testing/_hardhat_contract_testing.md b/content/00.build/05.start-coding/10.zksync-101/_testing/_hardhat_contract_testing.md deleted file mode 100644 index 13c8beb3..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_testing/_hardhat_contract_testing.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -title: Hardhat | Contract Testing ---- - -Run the following command in your terminal to initialize the project. - -```sh -zksync-cli create --template qs-testing contract-testing-quickstart -cd contract-testing-quickstart -``` - ---- - -## Local Era Node - -Testing contracts requires a more structured setup. -We'll use `hardhat-zksync` to run tests against our In-memory node, -which operates seamlessly within a separate process for an optimized testing workflow. - -::callout{icon="i-heroicons-information-circle" color="blue"} -If you have not started up a local node yet from the Getting started page, run `zksync-cli dev start`. -Make sure you are running the "In memory node" option. -:: - -Within the `hardhat.config.ts`, you'll observe the `zksync` flag set to `true` under the -`hardhat` network, indicating the integration with ZKsync's testing environment. - -```typescript [hardhat.config.ts] -const config: HardhatUserConfig = { - defaultNetwork: "hardhat" - networks: { - ... - hardhat: { - zksync: true, - }, - ... - } - ... -} -``` - -To use the In-memory node for testing, change the `defaultNetwork` to `"hardhat"`. -This setup initiates the node alongside your tests and ensures -it terminates once testing is complete. The node's port allocation starts at the default -`8011`, facilitating smooth and isolated test execution. - -Secondly within the `hardhat.config.ts`, you'll observe the importing of -`@nomicfoundation/hardhat-chai-matchers`. This plugin provides Hardhat with an extended -suite of assertion methods tailored for contract testing, significantly improving the testing -toolkit available for your project. - -```typescript -import "@nomicfoundation/hardhat-chai-matchers"; -``` - -### Test Wallet Configuration - -For testing purposes, we use pre-configured, well-funded wallets. -During this testing guide, we will use the following pre-configured wallet, -which eliminates the need for manual funding or setup: - -- **Account Address:** `0x36615Cf349d7F6344891B1e7CA7C72883F5dc049` -- **Private Key:** `0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110` - -This streamlined approach allows us to focus on writing and running effective tests. - ---- - -## Compile the `CrowdfundingCampaign` contract - -Now that our setup is complete, it's time to focus on the core of this -guide - testing our `CrowdfundingCampaign.sol` contract. Here's a quick -refresher on its structure: - -::drop-panel - ::panel{label="contracts/CrowdfundingCampaign.sol"} - ```solidity [contracts/CrowdfundingCampaign.sol] - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - - contract CrowdfundingCampaign { - address public owner; - uint256 public fundingGoal; - uint256 public totalFundsRaised; - mapping(address => uint256) public contributions; - - event ContributionReceived(address contributor, uint256 amount); - event GoalReached(uint256 totalFundsRaised); - - constructor(uint256 _fundingGoal) { - owner = msg.sender; - fundingGoal = _fundingGoal; - } - - function contribute() public payable { - require(msg.value > 0, "Contribution must be greater than 0"); - contributions[msg.sender] += msg.value; - totalFundsRaised += msg.value; - - emit ContributionReceived(msg.sender, msg.value); - - if (totalFundsRaised >= fundingGoal) { - emit GoalReached(totalFundsRaised); - } - } - - function withdrawFunds() public { - require(msg.sender == owner, "Only the owner can withdraw funds"); - require(totalFundsRaised >= fundingGoal, "Funding goal not reached"); - - uint256 amount = address(this).balance; - totalFundsRaised = 0; - - (bool success, ) = payable(owner).call{value: amount}(""); - require(success, "Transfer failed."); - } - - function getTotalFundsRaised() public view returns (uint256) { - return totalFundsRaised; - } - - function getFundingGoal() public view returns (uint256) { - return fundingGoal; - } - } - ``` - :: -:: - -Thorough testing involves scrutinizing every function and aspect of our contract, -including potential failure scenarios. In this guide, we'll focus in on the `contribute` -method to ensure it's tested. - -As a challenge to hone your testing skills further, -consider writing additional tests for the `withdrawFunds`, `getTotalFundsRaised`, -and `getFundingGoal` methods, expanding your test coverage and reinforcing the -reliability of the contract. - -:display-partial{path = "/_partials/_compile-solidity-contracts"} - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.1 and solc v0.8.17 -Compiling 15 Solidity files -Successfully compiled 15 Solidity files -``` - -The compiled artifacts will be located in the `/artifacts-zk` folder. - ---- - -## Test `CrowdfundingCampaign` - -This section describes testing the `CrowdfundingCampaign.sol` contract. Let's -start by reviewing the tests for `CrowdfundingCampaign.sol` contract -in the `/test` directory, specifically the -[`crowdFunding.test.ts` file](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/testing/test/crowdFunding.test.ts). - -```typescript [test/crowdFunding.test.ts] -import "@nomicfoundation/hardhat-chai-matchers"; -import { expect } from "chai"; -import { ethers } from "ethers"; -import { getWallet, LOCAL_RICH_WALLETS, deployContract } from "../deploy/utils"; - -describe("CrowdfundingCampaign", function () { - let campaign; - let owner, addr1, addr2; - - beforeEach(async function () { - owner = getWallet(LOCAL_RICH_WALLETS[0].privateKey); - addr1 = getWallet(LOCAL_RICH_WALLETS[1].privateKey); - addr2 = getWallet(LOCAL_RICH_WALLETS[2].privateKey); - const fundingGoalInWei = ethers.parseEther('1').toString(); - campaign = await deployContract("CrowdfundingCampaign", [fundingGoalInWei], { wallet: owner, silent: true }); - }); - - describe("Contribute", function () { - it("should reject contributions of 0", async function () { - await expect(campaign.connect(addr1).contribute({ value: ethers.parseEther("0") })).to.be.revertedWith("Contribution must be greater than 0"); - }); - - it("should aggregate contributions in totalFundsRaised", async function () { - await campaign.connect(addr1).contribute({ value: ethers.parseEther("0.5") }); - await campaign.connect(addr2).contribute({ value: ethers.parseEther("0.3") }); - expect(await campaign.getTotalFundsRaised()).to.equal(ethers.parseEther("0.8")); - }); - - it("should emit GoalReached event when funding goal is met", async function () { - await expect(campaign.connect(addr1).contribute({ value: ethers.parseEther("1") })) - .to.emit(campaign, "GoalReached") - .withArgs(ethers.parseEther("1")); - }); - }); -}); -``` - -- **Initialization**: Each test case initializes with fresh contract instances - and predefined rich wallet accounts to simulate various contributors and the contract owner. -- **Deployment**: The `CrowdfundingCampaign` contract is deployed using - the `deployContract` utility, setting a specific funding goal for each test scenario. - -**`contribute` Method Tests:** - -- **Zero Contributions**: Verifies that the contract correctly rejects contribution attempts - with zero value, ensuring the integrity of the contribution process. -- **Funds Aggregation**: Tests the contract's ability to accurately aggregate contributions - from multiple addresses and update the `totalFundsRaised` accordingly. -- **Goal Achievement**: Checks for the `GoalReached` event emission upon meeting the funding goal, - confirming the contract's responsiveness to achieving its set target. - -Execute the test command corresponding to your package manager: - -::code-group - -```bash [npm] -npx hardhat test -``` - -```bash [yarn] -yarn hardhat test -``` - -```bash [pnpm] -pnpm exec hardhat test -``` - -```bash [bun] -bunx hardhat test -``` - -:: - -Upon completion, the test suite will provide a summary of all executed tests, -indicating their success or failure: - -```bash - CrowdfundingCampaign - Contribute - ✔ should reject contributions of 0 (45ms) - ✔ should aggregate contributions in totalFundsRaised (213ms) - ✔ should emit GoalReached event when funding goal is met (113ms) - - 3 passing (1s) -``` - -🎉 Congratulations! The `contribute` method of the `CrowdfundingCampaign` contract -has been thoroughly tested and is ready for action. diff --git a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_beacon/_foundry_beacon_contract_upgradability.md b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_beacon/_foundry_beacon_contract_upgradability.md deleted file mode 100644 index 6876ce71..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_beacon/_foundry_beacon_contract_upgradability.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Foundry | Contract Upgrade ---- - -Coming soon! diff --git a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_beacon/_hardhat_beacon_contract_upgradability.md b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_beacon/_hardhat_beacon_contract_upgradability.md deleted file mode 100644 index 5610b0cc..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_beacon/_hardhat_beacon_contract_upgradability.md +++ /dev/null @@ -1,380 +0,0 @@ ---- -title: Hardhat | Contract Upgrading ---- - -Run the following command in your terminal to initialize the project. - -```sh -zksync-cli create --template qs-upgrade contract-upgrade-quickstart -cd contract-upgrade-quickstart -``` - -::callout{icon="i-heroicons-exclamation-triangle" color="amber"} -If you encounter an error while installing project dependencies using NPM as your package manager, try running `npm install --force`. -:: - -## Update the hardhat.config.ts - -Since we are using the "In memory node" with ZKsync CLI, we need to set the default network Hardhat uses -for deploying. - -Open up the `hardhat.config.ts` file and set the `defaultNetwork` to `inMemoryNode`. - -```ts -// ... -const config: HardhatUserConfig = { - defaultNetwork: "inMemoryNode", -// ... -``` - ---- - -## Adapt `CrowdfundingCampaign.sol` contract for upgradability - -To adapt our `CrowdfundingCampaign.sol` contract for upgradability, we are -transitioning to a proxy pattern. This approach separates the -contract's logic (which can be upgraded) from its persistent state -(stored in the proxy). - -In the `contracts/` directory you'll observe the refactored [`CrowdfundingCampaign` contract](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/contracts/CrowdfundingCampaign.sol) -which initializes state variables through an -`initialize` function instead of the constructor, in line with the proxy pattern. - -**Updated Contract Structure:** - -```solidity [contracts/CrowdfundingCampaign.sol] -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; - -contract CrowdfundingCampaign is Initializable { - address public owner; - uint256 public fundingGoal; - mapping(address => uint256) public contributions; - - event ContributionReceived(address contributor, uint256 amount); - event GoalReached(uint256 totalFundsRaised); - - // Remove constructor in favour of initialize method - function initialize(uint256 _fundingGoal) public initializer { - owner = msg.sender; - fundingGoal = _fundingGoal; - } - - function contribute() public payable { - // contribution logic remains the same - } - - function withdrawFunds() public { - // withdrawFunds logic remains the same - } - - function getTotalFundsRaised() public view returns (uint256) { - // getTotalFundsRaised remains the same - } - - function getFundingGoal() public view returns (uint256) { - // getFundingGoal remains the same - } -} -``` - -**Key Modifications:** - -- **Initializable**: Inherits from OpenZeppelin's `Initializable` to ensure the `initialize` function -can only be called once, similar to a constructor. -- **Initialize Function**: Replaces the constructor for setting initial state, facilitating upgrades -through new logic contracts. -- **Proxy Pattern**: Utilizes a proxy contract to delegate calls to this logic contract, -allowing for future upgrades without losing the contract's state. - -This restructuring prepares the `CrowdfundingCampaign` contract for upgradeability. - ---- - -## Compile the updated `CrowdfundingCampaign` contract - -Now that the `CrowdfundingCampaign` contract is adapted for contract upgradability, let's proceed to deploy -the contract so we may upgrade it in later steps. Since we've made changes to our contract we will -need to re-compile. - -To compile the contracts in the project, run the following command: - -```bash [npm] -npm run compile -``` - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.0 and solc v0.8.17 -Compiling 29 Solidity file -Successfully compiled 29 Solidity file -``` - -The compiled artifacts will be located in the `/artifacts-zk` folder. - -## Deploy the beacon and contract - -You'll find the necessary deployment script at [`deploy/deployBeaconProxy.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/deploy/deployBeaconProxy.ts). - -```typescript [deploy/deployBeaconProxy.ts] -import { getWallet } from "./utils"; -import { Deployer } from '@matterlabs/hardhat-zksync'; -import { ethers } from "ethers"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; - -export default async function (hre: HardhatRuntimeEnvironment) { - const wallet = getWallet(); - const deployer = new Deployer(hre, wallet); - - const contractArtifact = await deployer.loadArtifact("CrowdfundingCampaign"); - const fundingGoalInWei = ethers.parseEther('0.1').toString(); - - const beacon = await hre.zkUpgrades.deployBeacon( - getWallet(), - contractArtifact - ); - await beacon.waitForDeployment(); - - const crowdfunding = await hre.zkUpgrades.deployBeaconProxy(deployer.zkWallet, - await beacon.getAddress(), contractArtifact, [fundingGoalInWei]); - - await crowdfunding.waitForDeployment(); -} -``` - -**Key Components:** - -- **`deployBeacon` Method:** Initiates the deployment of a beacon contract, -which acts as a central point for managing future upgrades of the `CrowdfundingCampaign` -contract. The beacon's address is a critical component as it links the deployed proxy -to the actual contract logic. - -- **`deployBeaconProxy` Method:** This step involves deploying the beacon proxy, -which serves as the user-facing contract instance. It references the beacon for its logic, -allowing for seamless upgrades without altering the proxy's address. -The `fundingGoalInWei parameter`, converted from ether to wei, is passed during -this step to initialize the contract with a funding goal. - -Run the following command to deploy our contract with a beacon proxy: - -```bash [npm] -npx hardhat deploy-zksync --script deployBeaconProxy.ts -``` - -Upon successful deployment, you'll receive output detailing the deployment process, -including the contract addresses of the implementation -contract, the admin contract, and the beacon -proxy contract. - -```bash -Beacon impl deployed at 0xE3F814fa915A75bA47230537726C99f6517Da58e -Beacon deployed at: 0x26410Bebf5Df7398DCBC5f00e9EBBa0Ddf471C72 -Beacon proxy deployed at: 0xD58FA9Fb362Abf69cFc68A3545fD227165DAc167 -``` - ---- - -## Compile the `CrowdfundingCampaignV2` Contract - -With our initial setup deployed, we're ready to upgrade our `CrowdfundingCampaign.sol` -contract by incorporating a deadline for contributions. This addition not only brings -a new layer of functionality but also introduces the concept of time-based conditions -through a [`modifier`](https://docs.soliditylang.org/en/latest/contracts.html#function-modifiers). - -**Current Contract Overview:** - -The existing version of our contract allows for open-ended contributions towards a -funding goal, without any time constraints. - -**Proposed Upgrade:** - -We're introducing a `deadline` variable, initialized at contract deployment, to establish a -clear timeframe for accepting contributions. The `withinDeadline` modifier will then enforce -this constraint, ensuring contributions are made within the allowed period. - -**Enhanced Contract:** - -The upgraded contract, [`CrowdfundingCampaignV2.sol`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/contracts/CrowdfundingCampaignV2.sol), -located in the `/contracts` directory, -incorporates these changes: - -- **Deadline Variable:** A new state variable deadline defines the campaign's end time, -enhancing the contract with time-based logic. - -- **Initialization Logic:** An additional initialization method, `initializeV2`, sets the deadline -based on a duration provided during the upgrade. This function ensures that the upgrade is -backward-compatible and maintains the contract's integrity. - -- **Contribution Logic with Deadline:** The `contribute` method now includes a `withinDeadline` modifier, -ensuring all contributions are made within the set timeframe. - -- **Deadline Enforcement:** The `withinDeadline` modifier checks the current time against the deadline, -safeguarding the contract from late contributions. - -**Deadline Extension Capability:** - -To provide flexibility, a new function allows the owner to extend the deadline, -offering adaptability to changing campaign needs. - -```solidity [CrowdfundingCampaignV2.sol] -function extendDeadline(uint256 _newDuration) public { - require(msg.sender == owner, "Only the owner can extend the deadline"); - deadline = block.timestamp + _newDuration; -} -``` - -This upgrade not only introduces the element of time to the campaign but also -exemplifies the use of `modifiers` for enforcing contract conditions. - -:display-partial{path = "/_partials/_compile-solidity-contracts"} - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.0 and solc v0.8.17 -Compiling 4 Solidity file -Successfully compiled 4 Solidity file -``` - -The compiled artifacts will be located in the `/artifacts-zk` folder. - -## Upgrade to `CrowdfundingCampaignV2` - -This section describes the upgrade process to `CrowdfundingCampaignV2.sol` contract. Let's -start by reviewing the [`upgradeBeaconCrowdfundingCampaign.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/deploy/upgrade-scripts/upgradeBeaconCrowdfundingCampaign.ts) -script in the `deploy/upgrade-scripts` directory: - -Make sure to replace `YOUR_BEACON_ADDRESS_HERE` with the address of your deployed beacon and -`YOUR_PROXY_ADDRESS_HERE` with the actual address of your -deployed Beacon Proxy from the previous deployment step. - -```typescript [upgradeBeaconCrowdfundingCampaign.ts] -import { getWallet } from "../utils"; -import { Deployer } from '@matterlabs/hardhat-zksync'; -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import * as zk from 'zksync-ethers'; -import { Contract } from 'ethers'; - -export default async function (hre: HardhatRuntimeEnvironment) { - const wallet = getWallet(); - const deployer = new Deployer(hre, wallet); - - // Placeholder for the deployed beacon address - const beaconAddress = 'YOUR_BEACON_ADDRESS_HERE'; - - const contractV2Artifact = await deployer.loadArtifact('CrowdfundingCampaignV2'); - - // Upgrade the proxy to V2 - await hre.zkUpgrades.upgradeBeacon(deployer.zkWallet, beaconAddress, contractV2Artifact); - - console.log('Successfully upgraded crowdfundingCampaign to crowdfundingCampaignV2'); - - const attachTo = new zk.ContractFactory( - crowdfundingCampaignV2.abi, - crowdfundingCampaignV2.bytecode, - deployer.zkWallet, - deployer.deploymentType, - ); - - // Placeholder for the deployed beacon proxy address - const proxyAddress = 'YOUR_PROXY_ADDRESS_HERE'; - - const upgradedContract = attachTo.attach(proxyAddress); - - upgradedContract.connect(deployer.zkWallet); - // wait some time before the next call - await new Promise((resolve) => setTimeout(resolve, 2000)); - - // Initialize V2 with a new campaign duration - const durationInSeconds = 30 * 24 * 60 * 60; // For example, setting a 30-day duration - const initTx = await upgradedContract.initializeV2(durationInSeconds); - const receipt = await initTx.wait(); - - console.log(`CrowdfundingCampaignV2 initialized. Transaction Hash: ${receipt.hash}`); -} -``` - -**Key Components:** - -- **`upgradeBeacon`**: This method from the `hre.zkUpgrades` module is used to update the beacon contract -with the new version of the contract logic, `CrowdfundingCampaignV2`. -It ensures that all proxies pointing to this beacon will now execute the updated contract code. -- **`initializeV2`:** This method is specifically called post-upgrade to initialize or reconfigure any new state -variables or logic introduced in the `CrowdfundingCampaignV2`. -Here, it's used to set a new campaign duration, seamlessly -integrating new functionalities while retaining the existing contract state and funds. - -Execute the test command corresponding to your package manager: - -::code-group - -```bash [npm] -npx hardhat deploy-zksync --script upgrade-scripts/upgradeBeaconCrowdfundingCampaign.ts -``` - -```bash [yarn] -yarn hardhat deploy-zksync --script upgrade-scripts/upgradeBeaconCrowdfundingCampaign.ts -``` - -```bash [pnpm] -pnpm exec hardhat deploy-zksync --script upgrade-scripts/upgradeBeaconCrowdfundingCampaign.ts -``` - -```bash [bun] -bun run hardhat deploy-zksync --script upgrade-scripts/upgradeBeaconCrowdfundingCampaign.ts -``` - -:: - -Upon successful deployment, you'll receive output detailing the upgrade process, -including the new beacon address, and transaction hash: - -```bash -New beacon impl deployed at 0x58BD5adb462CF087E5838d53aE38A3Fe0EAf7A31 -Successfully upgraded crowdfundingCampaign to crowdfundingCampaignV2 0x26410Bebf5Df7398DCBC5f00e9EBBa0Ddf471C72 -CrowdfundingCampaignV2 initialized! 0x5f3131c77fcac19390f5f644a3ad1f0e7719dee4b4b5b4746c992de00db743f7 -Fundraising goal: 100000000000000000 -``` - ---- - -## Verify upgradable contracts - -::callout{icon="i-heroicons-exclamation-triangle" color="amber"} -Since we are using inMemoryNode for our smart contracts, we do not have the feature -available to verify the smart contract. - -The following explains how you will need to verify an upgraded smart contract on testnet or mainnet. -:: - -For the verification of our upgradable contracts, it's essential to utilize the proxy address that was specified in our -upgrade script. - -To proceed with verification, execute the following command: - -```bash [npm] -npx hardhat verify -``` - -Upon successful verification, you'll receive output detailing the verification process: - -```bash -Verifying implementation: 0x58BD5adb462CF087E5838d53aE38A3Fe0EAf7A31 -Your verification ID is: 10547 -Contract successfully verified on ZKsync block explorer! -Verifying beacon: 0x26410Bebf5Df7398DCBC5f00e9EBBa0Ddf471C72 -Your verification ID is: 10548 -Contract successfully verified on ZKsync block explorer! -Verifying beacon proxy: 0xD58FA9Fb362Abf69cFc68A3545fD227165DAc167 -Your verification ID is: 10549 -Contract successfully verified on ZKsync block explorer! -``` - -🎉 Congratulations! The `CrowdfundingCampaignV2` contract has been upgraded and verified! diff --git a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_beacon_proxy_contract_upgradability.md b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_beacon_proxy_contract_upgradability.md index 0c10b584..15945a69 100644 --- a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_beacon_proxy_contract_upgradability.md +++ b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_beacon_proxy_contract_upgradability.md @@ -3,8 +3,9 @@ title: Beacon Proxy Contract Upgradeability --- ### What is a beacon proxy upgradeable contract? -Beacon Proxy Upgradeable Contracts leverage a beacon to manage upgrades, allowing -for centralized logic updates across multiple proxies. The structure includes: +Beacon Proxy Upgradeable Contracts allows +for centralized logic updates across multiple proxies. +The structure includes: 1. **Beacon Contract**: Acts as the central point holding the address of the current logic contract. It enables updating the logic for all associated proxies through a single transaction. @@ -21,14 +22,198 @@ features or fixes while maintaining a uniform interface for users. --- -::content-switcher +## Adapt the Crowdfunding Campaign contract for upgradeability + +To adapt our Crowdfunding Campaign contract for upgradeability, we are +transitioning to a proxy pattern. This approach separates the +contract's logic (which can be upgraded) from its persistent state +(stored in the proxy). + +In the `contracts/3-proxy-contracts/beacon` directory you'll observe the refactored +[`BeaconCrowdfundingCampaign` contract][beacon-crowdfunding-campaign-sol] +which initializes state variables through an +`initialize` function instead of the constructor, in line with the proxy pattern. + +**Key Modifications:** + +- **Initializable**: Inherits from OpenZeppelin's `Initializable` to ensure the `initialize` function +can only be called once, similar to a constructor. +- **Initialize Function**: Replaces the constructor for setting initial state, facilitating upgrades +through new logic contracts. +- **Proxy Pattern**: Utilizes a proxy contract to delegate calls to this logic contract, +allowing for future upgrades without losing the contract's state. + +This restructuring prepares the `BeaconCrowdfundingCampaign` contract for upgradeability. + --- -items: [{ - label: 'Hardhat', - partial: '_upgrading/_beacon/_hardhat_beacon_contract_upgradability' -}, { - label: 'Foundry', - partial: '_upgrading/_beacon/_foundry_beacon_contract_upgradability' -}] + +## Compile the `BeaconCrowdfundingCampaign` contract + +Now that the `BeaconCrowdfundingCampaign` contract is adapted for contract upgradeability, let's proceed to deploy +the contract so we may upgrade it in later steps. + +To compile the contracts in the project, run the following command: + +```bash [npm] +npm run compile +``` + +## Deploy the beacon and contract + +You'll find the necessary deployment script at [`deploy/3-proxy-contracts/beacon/deploy.ts`][deploy-script]. + +**Key Components:** + +- **`deployBeacon` Method:** Initiates the deployment of a beacon contract, +which acts as a central point for managing future upgrades of the `BeaconCrowdfundingCampaign` +contract. The beacon's address is a critical component as it links the deployed proxy +to the actual contract logic. + +- **`deployBeaconProxy` Method:** This step involves deploying the beacon proxy, +which serves as the user-facing contract instance. It references the beacon for its logic, +allowing for seamless upgrades without altering the proxy's address. + +Run the following command to deploy our contract with a beacon proxy: + +```bash [npm] +npm run deploy:beacon-proxy +``` + +Upon successful deployment, you'll receive output detailing the deployment process, +including the contract addresses of the implementation +contract, the admin contract, and the beacon +proxy contract. + +```bash +Beacon impl deployed at 0xE3F814fa915A75bA47230537726C99f6517Da58e +Beacon deployed at: 0x26410Bebf5Df7398DCBC5f00e9EBBa0Ddf471C72 +Beacon proxy deployed at: 0xD58FA9Fb362Abf69cFc68A3545fD227165DAc167 +``` + --- + +## Compile the `V2_BeaconCrowdfundingCampaign` Contract + +With our initial setup deployed, we're ready to upgrade our `BeaconCrowdfundingCampaign.sol` +contract by incorporating a deadline for contributions. This addition not only brings +a new layer of functionality but also introduces the concept of time-based conditions +through a [`modifier`](https://docs.soliditylang.org/en/latest/contracts.html#function-modifiers). + +**Current Contract Overview:** + +The existing version of our contract allows for open-ended contributions towards a +funding goal, without any time constraints. + +**Proposed Upgrade:** + +We're introducing a `deadline` variable, initialized at contract deployment, to establish a +clear timeframe for accepting contributions. +The `withinDeadline` modifier will then enforce this constraint, +ensuring contributions are made within the allowed period. + +**Enhanced Contract:** + +The upgraded contract, [`V2_BeaconCrowdfundingCampaign.sol`][v2-beacon-crowdfunding-campaign-sol], +located in the `/contracts/3-proxy-contracts/beacon` directory, +incorporates these changes: + +- **Deadline Variable:** A new state variable deadline defines the campaign's end time, +enhancing the contract with time-based logic. + +- **Initialization Logic:** An additional initialization method, `initializeV2`, sets the deadline +based on a duration provided during the upgrade. This function ensures that the upgrade is +backward-compatible and maintains the contract's integrity. + +- **Contribution Logic with Deadline:** The `contribute` method now includes a `withinDeadline` modifier, +ensuring all contributions are made within the set timeframe. + +- **Deadline Enforcement:** The `withinDeadline` modifier checks the current time against the deadline in the `contribute` method, +safeguarding the contract from late contributions. + +**Deadline Extension Capability:** + +To provide flexibility, a new method `extendDeadline` allows the owner to extend the deadline, +offering adaptability to changing campaign needs. + +### Compile the `V2_UUPSCrowdfundingCampaign` contract + +Run the npm script `compile` to compile the contracts: + +```bash [npm] +npm run compile +``` + +## Deploy the upgrade to `V2_BeaconCrowdfundingCampaign` + +This section describes the upgrade process to the `V2_BeaconCrowdfundingCampaign.sol` contract. Let's +start by reviewing the [`deploy/3-proxy-contracts/beacon/upgrade.ts`][upgrade-script] +script. + +Make sure to replace `YOUR_BEACON_ADDRESS_HERE` with the address of your deployed beacon and +`YOUR_PROXY_ADDRESS_HERE` with the actual address of your +deployed Beacon Proxy from the previous deployment step. + +**Key Components:** + +- **`upgradeBeacon`**: This method from the `hre.zkUpgrades` module is used to update the beacon contract +with the new version of the contract logic, `V2_BeaconCrowdfundingCampaign`. +It ensures that all proxies pointing to this beacon will now execute the updated contract code. +- **`initializeV2`:** This method is specifically called post-upgrade to initialize or reconfigure any new state +variables or logic introduced in the `V2_BeaconCrowdfundingCampaign`. +Here, it's used to set a new campaign duration, seamlessly +integrating new functionalities while retaining the existing contract state and funds. + +Run the upgrade npm script: + +```bash [npm] +npm run upgrade:beacon-proxy +``` + +Upon successful deployment, you'll receive output detailing the upgrade process, +including the new beacon address, and transaction hash: + +```bash +New beacon impl deployed at 0x58BD5adb462CF087E5838d53aE38A3Fe0EAf7A31 +Successfully upgraded crowdfundingCampaign to crowdfundingCampaignV2 0x26410Bebf5Df7398DCBC5f00e9EBBa0Ddf471C72 +CrowdfundingCampaignV2 initialized! 0x5f3131c77fcac19390f5f644a3ad1f0e7719dee4b4b5b4746c992de00db743f7 +Fundraising goal: 100000000000000000 +``` + +--- + +## Verify upgradeable contracts + +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +Since we are using the in memory node for our smart contracts, we do not have the feature +available to verify the smart contract. + +The following explains how you can verify an upgraded smart contract on testnet or mainnet. :: + +For the verification of our upgradeable contracts, it's essential to utilize the proxy address that was specified in our +upgrade script. + +To proceed with verification, execute the following command: + +```bash [npm] +npx hardhat verify +``` + +Upon successful verification, you'll receive output detailing the verification process: + +```bash +Verifying implementation: 0x58BD5adb462CF087E5838d53aE38A3Fe0EAf7A31 +Your verification ID is: 10547 +Contract successfully verified on ZKsync block explorer! +Verifying beacon: 0x26410Bebf5Df7398DCBC5f00e9EBBa0Ddf471C72 +Your verification ID is: 10548 +Contract successfully verified on ZKsync block explorer! +Verifying beacon proxy: 0xD58FA9Fb362Abf69cFc68A3545fD227165DAc167 +Your verification ID is: 10549 +Contract successfully verified on ZKsync block explorer! +``` + +[beacon-crowdfunding-campaign-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/3-proxy-contracts/beacon/BeaconCrowdfundingCampaign.sol +[deploy-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/3-proxy-contracts/beacon/deploy.ts +[v2-beacon-crowdfunding-campaign-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/3-proxy-contracts/beacon/V2_BeaconCrowdfundingCampaign.sol +[upgrade-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/3-proxy-contracts/beacon/upgrade.ts diff --git a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_transparent/_foundry_transparent_contract_upgradability.md b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_transparent/_foundry_transparent_contract_upgradability.md deleted file mode 100644 index 6876ce71..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_transparent/_foundry_transparent_contract_upgradability.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Foundry | Contract Upgrade ---- - -Coming soon! diff --git a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_transparent/_hardhat_transparent_contract_upgradability.md b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_transparent/_hardhat_transparent_contract_upgradability.md deleted file mode 100644 index 9848eee1..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_transparent/_hardhat_transparent_contract_upgradability.md +++ /dev/null @@ -1,341 +0,0 @@ ---- -title: Hardhat | Contract Upgrading ---- -Run the following command in your terminal to initialize the project. - -```sh -zksync-cli create --template qs-upgrade contract-upgrade-quickstart -cd contract-upgrade-quickstart -``` - -## Update the hardhat.config.ts - -Since we are using the "In memory node" with ZKsync CLI, we need to set the default network Hardhat uses -for deploying. - -Open up the `hardhat.config.ts` file and set the `defaultNetwork` to `inMemoryNode`. - -```ts -// ... -const config: HardhatUserConfig = { - defaultNetwork: "inMemoryNode", -// ... -``` - ---- - -## Adapt `CrowdfundingCampaign.sol` contract for upgradability - -To adapt our `CrowdfundingCampaign.sol` contract for upgradability, we're -transitioning to a proxy pattern. This approach separates the -contract's logic (which can be upgraded) from its persistent state -(stored in the proxy). - -### Refactoring for Proxy Compatibility - -In the `contracts/` directory you'll observe the refactored [`CrowdfundingCampaign` contract](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/contracts/CrowdfundingCampaign.sol) -which initializes state variables through an -`initialize` function instead of the constructor, in line with the -Transparent Proxy pattern. - -**Updated Contract Structure:** - -```solidity [contracts/CrowdfundingCampaign.sol] -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; - -contract CrowdfundingCampaign is Initializable { - address public owner; - uint256 public fundingGoal; - mapping(address => uint256) public contributions; - - event ContributionReceived(address contributor, uint256 amount); - event GoalReached(uint256 totalFundsRaised); - - // Remove constructor in favour of initialize method - function initialize(uint256 _fundingGoal) public initializer { - owner = msg.sender; - fundingGoal = _fundingGoal; - } - - function contribute() public payable { - // contribution logic remains the same - } - - function withdrawFunds() public { - // withdrawFunds logic remains the same - } - - function getTotalFundsRaised() public view returns (uint256) { - // getTotalFundsRaised remains the same - } - - function getFundingGoal() public view returns (uint256) { - // getFundingGoal remains the same - } -} -``` - -**Key Modifications:** - -- **Initializable**: Inherits from OpenZeppelin's `Initializable` to ensure the `initialize` function -can only be called once, similar to a constructor. -- **Initialize Function**: Replaces the constructor for setting initial state, facilitating upgrades -through new logic contracts. -- **Proxy Pattern**: Utilizes a proxy contract to delegate calls to this logic contract, -allowing for future upgrades without losing the contract's state. - -This restructuring prepares the `CrowdfundingCampaign` contract for upgradability. - ---- - -## Deploy the `CrowdfundingCampaign` contract - -Now that the `CrowdfundingCampaign` contract is adapted for contract upgradability, let's proceed to deploy -the contract so we may upgrade it in later steps. Since we've made changes to our contract we will -need to re-compile. - -To compile the contracts in the project, run the following command: - -```bash [npm] -npm run compile -``` - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.0 and solc v0.8.17 -Compiling 3 Solidity file -Successfully compiled 3 Solidity file -``` - -The compiled artifacts will be located in the `/artifacts-zk` folder. - -The deployment script is located at [`/deploy/deployTransparentProxy.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/deploy/deployTransparentProxy.ts). - -```typescript [deploy/deployTransparentProxy.ts] -import { getWallet } from "./utils"; -import { Deployer } from '@matterlabs/hardhat-zksync'; -import { ethers } from "ethers"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; - -export default async function (hre: HardhatRuntimeEnvironment) { - const wallet = getWallet(); - const deployer = new Deployer(hre, wallet); - - const contractArtifact = await deployer.loadArtifact("CrowdfundingCampaign"); - const fundingGoalInWei = ethers.parseEther('0.1').toString(); - // Deploy the contract using a transparent proxy - const crowdfunding = await hre.zkUpgrades.deployProxy( - getWallet(), - contractArtifact, - [fundingGoalInWei], - { initializer: 'initialize' } - ); - - await crowdfunding.waitForDeployment(); -} -``` - -**Key Components:** - -- **`hre.zkUpgrades.deployProxy`**: The method call to deploy the `CrowdfundingCampaign` -contract via a transparent proxy, leveraging Hardhat's runtime environment for ZKsync upgrades. -This ensures the deployed contract can be upgraded in the future without losing its state or funds. -- **`initializer`**: Specifies the initialization method of the contract, `initialize` in this case, -which is required for setting up the proxy's state upon deployment. - -```bash [npm] -npx hardhat deploy-zksync --script deployTransparentProxy.ts -``` - -Upon successful deployment, you'll receive output detailing the deployment process, -including the contract addresses of the implementation -contract, the admin contract, and the transparent -proxy contract. - -```bash -Implementation contract was deployed to 0xE3F814fa915A75bA47230537726C99f6517Da58e -Admin was deployed to 0x05198D9f93cBDfa3e332776019115512d8e0c809 -Transparent proxy was deployed to 0x68E8533acE01019CB8D07Eca822369D5De71b74D -``` - ---- - -## Upgrade the `CrowdfundingCampaign` Contract - -With our initial setup deployed, we're ready to update our `CrowdfundingCampaign.sol` -contract by incorporating a deadline for contributions. This addition not only brings -a new layer of functionality but also introduces the concept of time-based conditions -through a `modifier`. - -**Current Contract Overview:** - -The existing version of our contract allows for open-ended contributions towards a -funding goal, without any time constraints. - -**Proposed Upgrade:** - -We're introducing a `deadline` variable, initialized at contract deployment, to establish a -clear timeframe for accepting contributions. The `withinDeadline` modifier will then enforce -this constraint, ensuring contributions are made within the allowed period. - -**Enhanced Contract:** - -The upgraded contract, [`CrowdfundingCampaignV2.sol`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/contracts/CrowdfundingCampaignV2.sol), -located in the `/contracts` directory, incorporates these changes: - -- **Deadline Variable:** A new state variable deadline defines the campaign's end time, -enhancing the contract with time-based logic. - -- **Initialization Logic:** An additional initialization method, `initializeV2`, sets the deadline -based on a duration provided during the upgrade. This function ensures that the upgrade is -backward-compatible and maintains the contract's integrity. - -- **Contribution Logic with Deadline:** The `contribute` method now includes a `withinDeadline` modifier, -ensuring all contributions are made within the set timeframe. - -- **Deadline Enforcement:** The `withinDeadline` modifier checks the current time against the deadline, -safeguarding the contract from late contributions. - -**Deadline Extension Capability:** - -To provide flexibility, a new function allows the owner to extend the deadline, -offering adaptability to changing campaign needs. - -```solidity [contracts/CrowdfundingCampaignV2.sol] -function extendDeadline(uint256 _newDuration) public { - require(msg.sender == owner, "Only the owner can extend the deadline"); - deadline = block.timestamp + _newDuration; -} -``` - -This upgrade not only introduces the element of time to the campaign but also -demonstrates the use of [`modifiers`](https://docs.soliditylang.org/en/latest/contracts.html#function-modifiers) for enforcing contract conditions. - -### Compile contract -:display-partial{path = "_partials/_compile-solidity-contracts"} - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.0 and solc v0.8.17 -Compiling 29 Solidity file -Successfully compiled 29 Solidity file -``` - -The compiled artifacts will be located in the `/artifacts-zk` folder. - -### Upgrade to `CrowdfundingCampaignV2` - -This section guides you through upgrading the `CrowdfundingCampaign` contract -to its second version, `CrowdfundingCampaignV2`. -Review the [`deploy/upgrade-scripts/upgradeCrowdfundingCampaign.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/deploy/upgrade-scripts/upgradeCrowdfundingCampaign.ts) -script to begin. - -Replace `YOUR_PROXY_ADDRESS_HERE` with the actual address of your -deployed Transparent Proxy from the previous deployment step. - -```typescript [deploy/upgrade-scripts/upgradeCrowdfundingCampaign.ts] -import { getWallet } from "../utils"; -import { Deployer } from '@matterlabs/hardhat-zksync'; -import { HardhatRuntimeEnvironment } from "hardhat/types"; - -export default async function (hre: HardhatRuntimeEnvironment) { - const wallet = getWallet(); - const deployer = new Deployer(hre, wallet); - - // Placeholder for the deployed proxy address - const proxyAddress = 'YOUR_PROXY_ADDRESS_HERE'; - - const contractV2Artifact = await deployer.loadArtifact('CrowdfundingCampaignV2'); - - // Upgrade the proxy to V2 - const upgradedContract = await hre.zkUpgrades.upgradeProxy(deployer.zkWallet, proxyAddress, contractV2Artifact); - - console.log('Successfully upgraded crowdfundingCampaign to crowdfundingCampaignV2'); - - upgradedContract.connect(deployer.zkWallet); - // wait some time before the next call - await new Promise((resolve) => setTimeout(resolve, 2000)); - - // Initialize V2 with a new campaign duration - const durationInSeconds = 30 * 24 * 60 * 60; // For example, setting a 30-day duration - const initTx = await upgradedContract.initializeV2(durationInSeconds); - const receipt = await initTx.wait(); - - console.log(`CrowdfundingCampaignV2 initialized. Transaction Hash: ${receipt.hash}`); -} -``` - -**Key Components:** - -- **`upgradeProxy`:** A critical method from the `hre.zkUpgrades` module that -performs the contract upgrade. It takes the wallet, the proxy address, and the -new contract artifact as arguments to transition the proxy to use the `CrowdfundingCampaignV2` logic. - -- **`initializeV2`:** Post-upgrade, this function is invoked to initialize the new -variables or logic introduced in `CrowdfundingCampaignV2`. In this example, -it sets a new campaign duration, illustrating how contract upgrades can add -functionalities without losing the existing state or funds. - -Execute the command corresponding to your package manager: - -```bash [npm] -npx hardhat deploy-zksync --script upgrade-scripts/upgradeCrowdfundingCampaign.ts -``` - -Upon successful deployment, you'll receive output detailing the upgrade process, -including the contract address, and transaction hash: - -```bash -Contract successfully upgraded to 0x58BD5adb462CF087E5838d53aE38A3Fe0EAf7A31 with tx 0xe30c017c52376507ab55bb51bc27eb300832dc46b8b9ac14549d2f9014cee97e -Successfully upgraded crowdfundingCampaign to crowdfundingCampaignV2 -CrowdfundingCampaignV2 initialized! 0x5adfe360187195d98d3603a82a20ffe7304cd4dec030d1bdf456fa1690879668 -Fundraising goal: 100000000000000000 -``` - ---- - -## Verify upgradable contracts - -::callout{icon="i-heroicons-exclamation-triangle" color="amber"} -Since we are using inMemoryNode for our smart contracts, we do not have the feature -available to verify the smart contract. - -The following explains how you will need to verify an upgraded smart contract on testnet or mainnet. -:: - -For the verification of our upgradable contracts, it's essential to utilize the proxy address that was specified in our -upgrade script. - -To proceed with verification, execute the following command: - -Replace with the actual proxy address from your deployment. -This is the address from the earlier deployment message: `Contract successfully upgraded to `. - -```bash [npm] -npx hardhat verify -``` - -Upon successful verification, you'll receive output detailing the verification process: - -```bash -Verifying implementation: 0x58BD5adb462CF087E5838d53aE38A3Fe0EAf7A31 -Your verification ID is: 10543 -Contract successfully verified on ZKsync block explorer! -Verifying proxy: 0x68E8533acE01019CB8D07Eca822369D5De71b74D -Your verification ID is: 10544 -Contract successfully verified on ZKsync block explorer! -Verifying proxy admin: 0x05198D9f93cBDfa3e332776019115512d8e0c809 -Your verification ID is: 10545 -Contract successfully verified on ZKsync block explorer! -``` - -🎉 Congratulations! The `CrowdfundingCampaignV2` contract has been upgraded and verified! diff --git a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_transparent_proxy_contract_upgradability.md b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_transparent_proxy_contract_upgradability.md index e9cfbbe3..1a372cd9 100644 --- a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_transparent_proxy_contract_upgradability.md +++ b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_transparent_proxy_contract_upgradability.md @@ -3,6 +3,7 @@ title: Transparent Upgradeable Proxy Contract --- ### What is a transparent upgradeable proxy contract? + Transparent upgradeable contracts utilize the proxy pattern to facilitate post-deployment logic updates while preventing accidental function collisions. They consist of: @@ -20,14 +21,200 @@ consistent user interface. --- -::content-switcher +## Adapt `CrowdfundingCampaign.sol` contract for upgradeability + +To adapt our `CrowdfundingCampaign.sol` contract for upgradeability, we're +transitioning to a proxy pattern. This approach separates the +contract's logic (which can be upgraded) from its persistent state +(stored in the proxy). + +### Refactoring for Proxy Compatibility + +In the `contracts/3-proxy-contracts/transparent` directory you'll observe the refactored +[`ProxyableCrowdfundingCampaign.sol` contract][proxyable-crowdfunding-campaign-sol] +which initializes state variables through an +`initialize` function instead of the constructor, in line with the +Transparent Proxy pattern. + +**Key Modifications:** + +- **Initializable**: Inherits from OpenZeppelin's `Initializable` to ensure the `initialize` function +can only be called once, similar to a constructor. +- **Initialize Function**: Replaces the constructor for setting initial state, facilitating upgrades +through new logic contracts. +- **Proxy Pattern**: Utilizes a proxy contract to delegate calls to this logic contract, +allowing for future upgrades without losing the contract's state. + +This restructuring makes the `ProxyableCrowdfundingCampaign` contract upgradeable. + --- -items: [{ - label: 'Hardhat', - partial: '_upgrading/_transparent/_hardhat_transparent_contract_upgradability' -}, { - label: 'Foundry', - partial: '_upgrading/_transparent/_foundry_transparent_contract_upgradability' -}] + +## Deploy the `ProxyableCrowdfundingCampaign` contract + +Now that the `ProxyableCrowdfundingCampaign` contract is adapted for contract upgradeability, let's proceed to deploy +the contract so we may upgrade it in later steps. + +To compile the contracts in the project, run the following command: + +```bash [npm] +npm run compile +``` + +The deployment script is located at +[`/deploy/3-proxy-contracts/transparent/deploy.ts`][deploy-script]. + +**Key Components:** + +- **`hre.zkUpgrades.deployProxy`**: This method call deploys the `ProxyableCrowdfundingCampaign` +contract via a transparent proxy, leveraging Hardhat's runtime environment for ZKsync upgrades. +This ensures the deployed contract can be upgraded in the future without losing its state or funds. +- **`initializer`**: Specifies the initialization method of the contract, `initialize` in this case, +which is required for setting up the proxy's state upon deployment. + +```bash [npm] +npm run deploy:transparent-proxy +``` + +Upon successful deployment, you'll receive output detailing the deployment process, +including the contract addresses of the implementation +contract, the admin contract, and the transparent +proxy contract. + +```bash +Implementation contract was deployed to 0xE3F814fa915A75bA47230537726C99f6517Da58e +Admin was deployed to 0x05198D9f93cBDfa3e332776019115512d8e0c809 +Transparent proxy was deployed to 0x68E8533acE01019CB8D07Eca822369D5De71b74D +``` + --- + +## Upgrade the `ProxyableCrowdfundingCampaign` Contract + +With our initial setup deployed, we're ready to update our `ProxyableCrowdfundingCampaign.sol` +contract by incorporating a deadline for contributions. This addition not only brings +a new layer of functionality but also introduces the concept of time-based conditions +through a `modifier`. + +**Current Contract Overview:** + +The existing version of our contract allows for open-ended contributions towards a +funding goal, without any time constraints. + +**Proposed Upgrade:** + +We're introducing a dealine on creation of a crowdfunding campaign. +Contributions can only be made within the allowed time period. + +**Enhanced Contract:** + +The upgraded contract, +[`/contracts/3-proxy-contracts/transparent/V2_ProxyableCrowdfundingCampaign.sol`][v2-proxyable-crowdfunding-campaign-sol], +incorporates these changes: + +- **Deadline Variable:** A new state variable deadline defines the campaign's end time, +enhancing the contract with time-based logic. + +- **Initialization Logic:** An additional initialization method, `initializeV2`, sets the deadline +based on a duration provided during the upgrade. This function ensures that the upgrade is +backward-compatible and maintains the contract's integrity. +Campaigns that were made before the upgrade can still continue to fund without the deadline logic affecting them. + +- **Contribution Logic with Deadline:** The `contribute` method now includes a `withinDeadline` modifier, +ensuring all contributions are made within the set timeframe. + +- **Deadline Enforcement:** The `withinDeadline` modifier checks that the campaign is a V2 version and checks the current time against the deadline, +safeguarding the contract from late contributions. + +**Deadline Extension Capability:** + +To provide flexibility, a new function allows the owner to extend the deadline, +offering adaptability to changing campaign needs. + +This upgrade not only introduces the element of time to the campaign but also +demonstrates the use of [`modifiers`](https://docs.soliditylang.org/en/latest/contracts.html#function-modifiers) for enforcing contract conditions. + +### Compile contract + +Run the npm script `compile` to compile the contracts: + +```bash [npm] +npm run compile +``` + +### Update to `V2_ProxyableCrowdfundingCampaign` + +This section guides you through upgrading the `ProxyableCrowdfundingCampaign` contract +to its second version, `V2_ProxyableCrowdfundingCampaign`. +Review the [`deploy/3-proxy-contracts/upgrade-transparent.ts`][upgrade-script] +script to begin. + +Replace `YOUR_PROXY_ADDRESS_HERE` with the actual address of your +deployed Transparent Proxy from the previous deployment step. + +**Key Components:** + +- **`upgradeProxy`:** A critical method from the `hre.zkUpgrades` module that +performs the contract upgrade. It takes the wallet, the proxy address, and the +new contract artifact as arguments to transition the proxy to use the `V2CrowdfundingCampaign` logic. + +- **`initializeV2`:** Post-upgrade, this function is invoked to initialize the new +variables or logic introduced in `V2_ProxyableCrowdfundingCampaign`. In this example, +it sets a new campaign duration, illustrating how contract upgrades can add +functionalities without losing the existing state or funds. + +Run the following command to upgrade to the `V2_ProxyableCrowdfundingCampaign`: + +```bash [npm] +npm run upgrade:transparent-proxy +``` + +Upon successful deployment, you'll receive output detailing the upgrade process, +including the contract address, and transaction hash: + +```bash +Contract successfully upgraded to 0x094499Df5ee555fFc33aF07862e43c90E6FEe501 with tx 0xe281c711b08cab3177b3a542af2e7e3def6602e8d34284127a4343b8e95dcf82 +Successfully upgraded ProxyableCrowdfundingCampaign to V2_ProxyableCrowdfundingCampaign +V2CrowdfundingCampaign initialized. Transaction Hash: 0x3a7cbf9d584457bc6b452964f41e1971f22393724f103e41984e0282bd8cb5cc +``` + +--- + +## Verify upgradable contracts + +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +Since we are using in memory node for our smart contracts, we do not have the feature +available to verify the smart contract. + +The following explains how you can verify an upgraded smart contract on testnet or mainnet. :: + +For the verification of our upgradable contracts, it's essential to utilize the proxy address that was specified in our +upgrade script. + +To proceed with verification, execute the following command: + +Replace with the actual proxy address from your deployment. +This is the address from the earlier deployment message: `Contract successfully upgraded to `. + +```bash [npm] +npx hardhat verify +``` + +Upon successful verification, you'll receive output detailing the verification process: + +```bash +Verifying implementation: 0x58BD5adb462CF087E5838d53aE38A3Fe0EAf7A31 +Your verification ID is: 10543 +Contract successfully verified on ZKsync block explorer! +Verifying proxy: 0x68E8533acE01019CB8D07Eca822369D5De71b74D +Your verification ID is: 10544 +Contract successfully verified on ZKsync block explorer! +Verifying proxy admin: 0x05198D9f93cBDfa3e332776019115512d8e0c809 +Your verification ID is: 10545 +Contract successfully verified on ZKsync block explorer! +``` + +[proxyable-crowdfunding-campaign-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/3-proxy-contracts/transparent/ProxyableCrowdfundingCampaign.sol +[deploy-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/3-proxy-contracts/transparent/deploy.ts +[v2-proxyable-crowdfunding-campaign-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/3-proxy-contracts/transparent/V2_ProxyableCrowdfundingCampaign.sol +[upgrade-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/3-proxy-contracts/transparent/upgrade.ts diff --git a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_uups/_foundry_uups_contract_upgradability.md b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_uups/_foundry_uups_contract_upgradability.md deleted file mode 100644 index 6876ce71..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_uups/_foundry_uups_contract_upgradability.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Foundry | Contract Upgrade ---- - -Coming soon! diff --git a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_uups/_hardhat_uups_contract_upgradability.md b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_uups/_hardhat_uups_contract_upgradability.md deleted file mode 100644 index 94804bea..00000000 --- a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_uups/_hardhat_uups_contract_upgradability.md +++ /dev/null @@ -1,342 +0,0 @@ ---- -title: Hardhat | Contract Upgrading ---- - -Run the following command in your terminal to initialize the project. - -```sh -zksync-cli create --template qs-upgrade contract-upgrade-quickstart -cd contract-upgrade-quickstart -``` - -## Update the hardhat.config.ts - -Since we are using the "In memory node" with ZKsync CLI, we need to set the default network Hardhat uses -for deploying. - -Open up the `hardhat.config.ts` file and set the `defaultNetwork` to `inMemoryNode`. - -```ts -// ... -const config: HardhatUserConfig = { - defaultNetwork: "inMemoryNode", -// ... -``` - ---- - -## Adapt the `CrowdfundingCampaign.sol` for UUPS Upgradability - -To align the `CrowdfundingCampaign.sol` contract with UUPS (Universal Upgradeable Proxy Standard) upgradability, -we're integrating OpenZeppelin's UUPSUpgradeable contracts. This method offers a more secure and gas-efficient -approach to contract upgrades by embedding the upgrade logic within the contract itself. - -### Refactoring for UUPS Compatibility - -We've refactored the contract to support UUPS upgradability, ensuring the contract's logic -is upgradeable while maintaining a persistent state. This is achieved by utilizing initializer -functions and the UUPS upgrade mechanism. - -**UUPS-Enabled Contract Structure:** - -```solidity [contracts/CrowdfundingCampaign_UUPS.sol] -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Import UUPS from OpenZeppelin -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; - -contract CrowdfundingCampaign_UUPS is Initializable, UUPSUpgradeable, OwnableUpgradeable { - uint256 public fundingGoal; - uint256 public totalFundsRaised; - mapping(address => uint256) public contributions; - - event ContributionReceived(address contributor, uint256 amount); - event GoalReached(uint256 totalFundsRaised); - - // Initializer function, replaces constructor for upgradeable contracts - function initialize(uint256 _fundingGoal) public initializer { - __Ownable_init(); // Initialize ownership to the deployer - __UUPSUpgradeable_init(); // Initialize UUPS upgradeability - - fundingGoal = _fundingGoal; - } - - function contribute() public payable { - // Contribution logic remains the same - } - - function withdrawFunds() public onlyOwner { - // WithdrawFunds logic remains the same - } - - function getTotalFundsRaised() public view returns (uint256) { - // getTotalFundsRaised remains the same - } - - function getFundingGoal() public view returns (uint256) { - // getFundingGoal remains the same - } - - // Ensure only the owner can upgrade the contract - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} -} -``` - -**Key Adaptations:** - -- **Initializable & UUPSUpgradeable**: The contract inherits from `Initializable` and `UUPSUpgradeable`, -ensuring initialization follows the proxy pattern and enabling the UUPS upgrade mechanism. -- **OwnableUpgradeable**: Utilizes `OwnableUpgradeable` to manage ownership through an initializer, -important for secure upgrade authorization. -- **_authorizeUpgrade**: A safeguard function ensuring only the contract owner can perform upgrades, -reinforcing the contract's security. - -By adopting the UUPS pattern, the [`CrowdfundingCampaign_UUPS`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/contracts/CrowdfundingCampaign_UUPS.sol) -contract becomes efficiently upgradeable, offering enhanced security and reduced gas costs, setting a solid foundation for future enhancements. - ---- - -## Compile the `CrowdfundingCampaign_UUPS` contract - -Now that the `CrowdfundingCampaign_UUPS` contract is adapted for contract upgradability, let's proceed to deploy -the contract so we may upgrade it in later steps. Since we've made changes to our contract we will -need to re-compile. - -To compile the contracts in the project, run the following command: - -```bash [npm] -npm run compile -``` - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.0 and solc v0.8.17 -Compiling 4 Solidity file -Successfully compiled 4 Solidity file -``` - -The compiled artifacts will be located in the `/artifacts-zk` folder. - -## Deploy the updated contract - -The script to deploy the `CrowdfundingCampaign_UUPS` contract is located at [`/deploy/deployUUPS.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/deploy/deployUUPS.ts). - -```typescript [deploy/deployUUPS.ts] -import { getWallet } from "./utils"; -import { Deployer } from '@matterlabs/hardhat-zksync'; -import { ethers } from "ethers"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; - -export default async function (hre: HardhatRuntimeEnvironment) { - const wallet = getWallet(); - const deployer = new Deployer(hre, wallet); - - const contractArtifact = await deployer.loadArtifact("CrowdfundingCampaign_UUPS"); - const fundingGoalInWei = ethers.parseEther('0.1').toString(); - - const crowdfunding = await hre.zkUpgrades.deployProxy( - getWallet(), - contractArtifact, - [fundingGoalInWei], - { initializer: 'initialize' } - ); - - await crowdfunding.waitForDeployment(); -} -``` - -**Key Components:** - -- **`deployProxy` Method:** This method is responsible for deploying the `CrowdfundingCampaign` -contract as a UUPS upgradeable contract. It initializes the contract with the specified parameters, -such as the `fundingGoalInWei`, ensuring that the contract is ready for immediate use after deployment. -The use of the UUPS pattern provides a secure and efficient mechanism for future upgrades. - -- **`initializer` Option:** Specifies the initialization method of the contract, in this case, `initialize`. -This is used for setting up the initial state of the contract upon deployment, particularly important -for upgradeable contracts where constructor usage is not possible. - -```bash [npm] -npx hardhat deploy-zksync --script deployUUPS.ts -``` - -Upon successful deployment, you'll receive output detailing the deployment process, -including the contract addresses of the implementation -contract, the admin contract, and the transparent -proxy contract. - -```bash -Implementation contract was deployed to 0xF0De77041F3cF6D9C905A10ce59858b17E57E3B9 -UUPS proxy was deployed to 0x56882194aAe8E4B6d18cD84e4D7B0F807e0100Cb -``` - ---- - -## Upgrade to the `CrowdfundingCampaignV2_UUPS` Contract - -With our initial setup deployed, we're ready to upgrade our `CrowdfundingCampaign_UUPS.sol` -contract by incorporating a deadline for contributions. This addition not only brings -a new layer of functionality but also introduces the concept of time-based conditions -through a `modifier`. - -**Current Contract Overview:** - -The existing version of our contract allows for open-ended contributions towards a -funding goal, without any time constraints. - -**Proposed Upgrade:** - -We're introducing a `deadline` variable, initialized at contract deployment, to establish a -clear timeframe for accepting contributions. The `withinDeadline` modifier will then enforce -this constraint, ensuring contributions are made within the allowed period. - -**Enhanced Contract:** - -The upgraded contract, [`CrowdfundingCampaignV2_UUPS.sol`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/contracts/CrowdfundingCampaignV2_UUPS.sol), -located in the `/contracts` directory, incorporates these changes: - -- **Deadline Variable:** A new state variable deadline defines the campaign's end time, -enhancing the contract with time-based logic. - -- **Initialization Logic:** An additional initialization method, `initializeV2`, sets the deadline -based on a duration provided during the upgrade. This function ensures that the upgrade is -backward-compatible and maintains the contract's integrity. - -- **Contribution Logic with Deadline:** The `contribute` method now includes a `withinDeadline` modifier, -ensuring all contributions are made within the set timeframe. - -- **Deadline Enforcement:** The `withinDeadline` modifier checks the current time against the deadline, -safeguarding the contract from late contributions. - -**Deadline Extension Capability:** - -To provide flexibility, a new function allows the owner to extend the deadline, -offering adaptability to changing campaign needs. - -```solidity [CrowdfundingCampaignV2_UUPS.sol] -function extendDeadline(uint256 _newDuration) public { - require(msg.sender == owner, "Only the owner can extend the deadline"); - deadline = block.timestamp + _newDuration; -} -``` - -This upgrade not only introduces the element of time to the campaign but also -demonstrates the use of `modifiers` for enforcing contract conditions. - -### Compile the `CrowdfundingCampaignV2_UUPS` contract - -:display-partial{path = "_partials/_compile-solidity-contracts"} - -Upon successful compilation, you'll receive output detailing the -`zksolc` and `solc` versions used during compiling and the number -of Solidity files compiled. - -```bash -Compiling contracts for ZKsync Era with zksolc v1.4.0 and solc v0.8.17 -Compiling 4 Solidity file -Successfully compiled 4 Solidity file -``` - -The compiled artifacts will be located in the `/artifacts-zk` folder. - -### Upgrade to `CrowdfundingCampaignV2_UUPS` - -This section describes the initiating the upgrade to `CrowdfundingCampaignV2_UUPS.sol` contract. -Let's start by reviewing the [`deploy/upgrade-scripts/upgradeUUPSCrowdfundingCampaign.ts`](https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/quickstart/hardhat/upgradability/deploy/upgrade-scripts/upgradeUUPSCrowdfundingCampaign.ts) -script: - -Replace `YOUR_PROXY_ADDRESS_HERE` with the actual address of your -deployed UUPS Proxy from the previous deployment step. - -```typescript [deploy/upgrade-scripts/upgradeUUPSCrowdfundingCampaign.ts] -import { getWallet } from "../utils"; -import { Deployer } from '@matterlabs/hardhat-zksync'; -import { HardhatRuntimeEnvironment } from "hardhat/types"; - -export default async function (hre: HardhatRuntimeEnvironment) { - const wallet = getWallet(); - const deployer = new Deployer(hre, wallet); - - // Placeholder for the deployed proxy address - const proxyAddress = 'YOUR_PROXY_ADDRESS_HERE'; - - // Upgrade the proxy to V2 - const contractV2Artifact = await deployer.loadArtifact('CrowdfundingCampaignV2_UUPS'); - const upgradedContract = await hre.zkUpgrades.upgradeProxy(deployer.zkWallet, proxyAddress, contractV2Artifact); - console.log('Successfully upgraded crowdfundingCampaign_UUPS to crowdfundingCampaignV2_UUPS'); - - upgradedContract.connect(deployer.zkWallet); - // wait some time before the next call - await new Promise((resolve) => setTimeout(resolve, 2000)); - - const durationInSeconds = 30 * 24 * 60 * 60; // For example, setting a 30-day duration - - const initTx = await upgradedContract.initializeV2(durationInSeconds); - const receipt = await initTx.wait(); - - console.log('CrowdfundingCampaignV2_UUPS initialized!', receipt.hash); -} -``` - -**Key Components:** - -- **`upgradeProxy`:** A critical method from the `hre.zkUpgrades` module that -performs the contract upgrade. It takes the wallet, the proxy address, and the -new contract artifact as arguments to transition the proxy to use the `CrowdfundingCampaignV2_UUPS` logic. - -- **`initializeV2`:** Post-upgrade, this function is invoked to initialize the new -variables or logic introduced in `CrowdfundingCampaignV2_UUPS`. In this example, -it sets a new campaign duration, illustrating how contract upgrades can add -functionalities without losing the existing state or funds. - -Execute the test command corresponding to your package manager: - -```bash [npm] -npx hardhat deploy-zksync --script upgrade-scripts/upgradeUUPSCrowdfundingCampaign.ts -``` - -Upon successful deployment, you'll receive output detailing the upgrade process, -including the new beacon address, and transaction hash: - -```bash -Contract successfully upgraded to 0x9BE22706966D717d7b0C8aEC99A1a9d1b3bFeC50 with tx 0x24ad582828b23b98d207ec7c057cd6a9c911bea22dbe85e0affd7479b00d90e9 -Successfully upgraded crowdfundingCampaign_UUPS to crowdfundingCampaignV2_UUPS -CrowdfundingCampaignV2_UUPS initialized! 0xab959f588b64dc6dee1e94d5fa0da2ae205c7438cf097d26d3ba73690e2b09e8 -``` - ---- - -## Verify upgradable contracts - -::callout{icon="i-heroicons-exclamation-triangle" color="amber"} -Since we are using inMemoryNode for our smart contracts, we do not have the feature -available to verify the smart contract. - -The following explains how you will need to verify an upgraded smart contract on testnet or mainnet. -:: - -To verify our upgradable contracts we need to the proxy address we previously used in our upgrade script. -With that execute the following command: - -```bash [npm] -npx hardhat verify -``` - -Upon successful verification, you'll receive output detailing the verification process: - -```bash -Verifying implementation: 0x9BE22706966D717d7b0C8aEC99A1a9d1b3bFeC50 -Your verification ID is: 10618 -Contract successfully verified on ZKsync block explorer! -Verifying proxy: 0x91921fDb0F8942c18eCeE4E3896b369ca0650483 -Your verification ID is: 10619 -Contract successfully verified on ZKsync block explorer! -``` - -🎉 Congratulations! The `CrowdfundingCampaignV2_UUPS` contract has been upgraded and verified! diff --git a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_uups_contract_upgradability.md b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_uups_contract_upgradability.md index 44c6049a..3cd1ba61 100644 --- a/content/00.build/05.start-coding/10.zksync-101/_upgrading/_uups_contract_upgradability.md +++ b/content/00.build/05.start-coding/10.zksync-101/_upgrading/_uups_contract_upgradability.md @@ -3,6 +3,7 @@ title: UUPS Proxy Contract Upgradeability --- ### What is a UUPS upgradeable contract? + UUPS (Universal Upgradeable Proxy Standard) Upgradeable Contracts embed the upgrade logic within the contract itself, simplifying upgrades and enhancing security. The components are: @@ -22,14 +23,195 @@ with a stable user experience. --- -::content-switcher +## Adapt the crowdfunding campaign code for UUPS Upgradability + +To align the Crowdfunding Campaign contract with UUPS (Universal Upgradeable Proxy Standard) upgradeability, +we're integrating OpenZeppelin's UUPSUpgradeable contracts. This method offers a more secure and gas-efficient +approach to contract upgrades by embedding the upgrade logic within the contract itself. + +### Refactoring for UUPS Compatibility + +We've refactored the contract to support UUPS upgradeability, ensuring the contract's logic +is upgradeable while maintaining a persistent state. This is achieved by utilizing initializer +functions and the UUPS upgrade mechanism. + +**UUPS-Enabled Contract Structure:** + +In the `contracts/3-proxy-contracts/uups` directory you'll find the refactored +[`UUPSCrowdfundingCampaign.sol` contract][uups-crowdfunding-campaign-sol]. + +**Key Adaptations:** + +- **Initializable & UUPSUpgradeable**: The contract inherits from `Initializable` and `UUPSUpgradeable`, +ensuring initialization follows the proxy pattern and enabling the UUPS upgrade mechanism. +- **OwnableUpgradeable**: Utilizes `OwnableUpgradeable` to manage ownership through an initializer, +important for secure upgrade authorization. +- **_authorizeUpgrade**: A safeguard function ensuring only the contract owner can perform upgrades, +reinforcing the contract's security. + +--- + +## Compile the `UUPSCrowdfundingCampaign` contract + +Now that the `UUPSCrowdfundingCampaign` contract is adapted for contract upgradeability, let's proceed to deploy +the contract so we may upgrade it in later steps. + +To compile the contracts in the project, run the following command: + +```bash [npm] +npm run compile +``` + +## Deploy the updated contract + +The script to deploy the `UUPSCrowdfundingCampaign` contract is located at [`/deploy/3-proxy-contracts/uups/deployUUPS.ts`][deploy-script]. + +**Key Components:** + +- **`deployProxy` Method:** This method is responsible for deploying the `UUPSCrowdfundingCampaign` +contract as a UUPS upgradeable contract. It initializes the contract with the specified parameters, +such as the `fundingGoalInWei`, ensuring that the contract is ready for immediate use after deployment. +The use of the UUPS pattern provides a secure and efficient mechanism for future upgrades. + +- **`initializer` Option:** Specifies the initialization method of the contract, in this case, `initialize`. +This is used for setting up the initial state of the contract upon deployment, particularly important +for upgradeable contracts where constructor usage is not possible. + +```bash [npm] +npm run deploy:uups-proxy +``` + +Upon successful deployment, you'll receive output detailing the deployment process, +including the contract addresses of the implementation +contract, the admin contract, and the transparent +proxy contract. + +```bash +Implementation contract was deployed to 0xF0De77041F3cF6D9C905A10ce59858b17E57E3B9 +UUPS proxy was deployed to 0x56882194aAe8E4B6d18cD84e4D7B0F807e0100Cb +``` + --- -items: [{ - label: 'Hardhat', - partial: '_upgrading/_uups/_hardhat_uups_contract_upgradability' -}, { - label: 'Foundry', - partial: '_upgrading/_uups/_foundry_uups_contract_upgradability' -}] + +## Upgrade to the `V2_UUPSCrowdfundingCampaign` Contract + +With our initial setup deployed, we're ready to upgrade our `UUPSCrowdfundingCampaign.sol` +contract by incorporating a deadline for contributions. This addition not only brings +a new layer of functionality but also introduces the concept of time-based conditions +through a `modifier`. + +**Current Contract Overview:** + +The existing version of our contract allows for open-ended contributions towards a +funding goal, without any time constraints. + +**Proposed Upgrade:** + +We're introducing a `deadline` variable, initialized at contract deployment, to establish a +clear timeframe for accepting contributions. +The `withinDeadline` modifier will then enforce this constraint, +ensuring contributions are made within the allowed period. + +**Enhanced Contract:** + +The upgraded contract, [`V2_UUPSCrowdfundingCampaign.sol`][v2-uups-crowdfunding-campaign-sol], +located in the `/contracts/3-proxy-contracts/uups` directory, incorporates these changes: + +- **Deadline Variable:** A new state variable deadline defines the campaign's end time, +enhancing the contract with time-based logic. + +- **Initialization Logic:** An additional initialization method, `initializeV2`, sets the deadline +based on a duration provided during the upgrade. This function ensures that the upgrade is +backward-compatible and maintains the contract's integrity. + +- **Contribution Logic with Deadline:** The `contribute` method now includes a `withinDeadline` modifier, +ensuring all contributions are made within the set timeframe. + +- **Deadline Enforcement:** The `withinDeadline` modifier checks that the campaign is a V2 version and checks the current time against the deadline, +safeguarding the contract from late contributions. + +**Deadline Extension Capability:** + +To provide flexibility, a new method `extendDeadline` allows the owner to extend the deadline, +offering adaptability to changing campaign needs. + +This upgrade not only introduces the element of time to the campaign but also +demonstrates the use of [`modifiers`](https://docs.soliditylang.org/en/latest/contracts.html#function-modifiers) for enforcing contract conditions. + +### Compile the `V2_UUPSCrowdfundingCampaign` contract + +Run the npm script `compile` to compile the contracts: + +```bash [npm] +npm run compile +``` + +### Deploy the upgrade to `V2_UUPSCrowdfundingCampaign` + +This section describes upgrading from the original +crowdfunding campaign contract to the updated `V2_UUPSCrowdfundingCampaign.sol` contract. +Let's start by reviewing the [`deploy/3-proxy-contracts/uups/upgrade.ts`][upgrade-script] +script. + +Replace `YOUR_PROXY_ADDRESS_HERE` with the actual address of your +deployed UUPS Proxy from the previous deployment step. + +**Key Components:** + +- **`upgradeProxy`:** A critical method from the `hre.zkUpgrades` module that +performs the contract upgrade. It takes the wallet, the proxy address, and the +new contract artifact as arguments to transition the proxy to use the `V2_UUPSCrowdfundingCampaign` logic. + +- **`initializeV2`:** Post-upgrade, this function is invoked to initialize the new +variables or logic introduced in `V2_UUPSCrowdfundingCampaign`. In this example, +it sets a new campaign duration, illustrating how contract upgrades can add +functionalities without losing the existing state or funds. + +Execute the upgrade npm script command to upgrade: + +```bash [npm] +npm run upgrade:uups-proxy +``` + +Upon successful deployment, you'll receive output detailing the upgrade process, +including the new uups address, and transaction hash: + +```bash +Contract successfully upgraded to 0x9BE22706966D717d7b0C8aEC99A1a9d1b3bFeC50 with tx 0x24ad582828b23b98d207ec7c057cd6a9c911bea22dbe85e0affd7479b00d90e9 +Successfully upgraded UUPSCrowdfundingCampaign to V2_UUPSCrowdfundingCampaign +V2_UUPSCrowdfundingCampaign initialized! 0xab959f588b64dc6dee1e94d5fa0da2ae205c7438cf097d26d3ba73690e2b09e8 +``` + --- + +## Verify upgradable contracts + +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +Since we are using the in memory node for our smart contracts, we do not have the feature +available to verify the smart contract. + +The following explains how you can verify an upgraded smart contract on testnet or mainnet. :: + +To verify our upgradable contracts we need to the proxy address we previously used in our upgrade script. +With that execute the following command: + +```bash [npm] +npx hardhat verify +``` + +Upon successful verification, you'll receive output detailing the verification process: + +```bash +Verifying implementation: 0x9BE22706966D717d7b0C8aEC99A1a9d1b3bFeC50 +Your verification ID is: 10618 +Contract successfully verified on ZKsync block explorer! +Verifying proxy: 0x91921fDb0F8942c18eCeE4E3896b369ca0650483 +Your verification ID is: 10619 +Contract successfully verified on ZKsync block explorer! +``` + +[uups-crowdfunding-campaign-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/3-proxy-contracts/uups/UUPSCrowdfundingCampaign.sol +[deploy-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/3-proxy-contracts/uups/deploy.ts +[v2-uups-crowdfunding-campaign-sol]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/contracts/3-proxy-contracts/uups/V2_UUPSCrowdfundingCampaign.sol +[upgrade-script]: https://github.com/matter-labs/zksync-contract-templates/blob/main/templates/101/deploy/3-proxy-contracts/uups/upgrade.ts diff --git a/cspell-config/cspell-zksync.txt b/cspell-config/cspell-zksync.txt index c913f9e9..31a35bdb 100644 --- a/cspell-config/cspell-zksync.txt +++ b/cspell-config/cspell-zksync.txt @@ -11,6 +11,7 @@ zkevm zkforge zkout zksolc +zkcli- ZK Chain !ZKChain !zkStack diff --git a/public/images/quickstart-paymasters/crown-mint.png b/public/images/quickstart-paymasters/crown-mint.png deleted file mode 100644 index 97722741..00000000 Binary files a/public/images/quickstart-paymasters/crown-mint.png and /dev/null differ