Trustlessness is the primary purpose for developing smart contracts, which means that users should not have to trust third parties (such as developers and organisations) before engaging with a contract.
Smart contracts written in a high-level language (such as Solidity) compile to the same bytecode that is executed at the contract address.
In one word, source code verification validates that the published contract code matches the code running at the contract address on the Ethereum blockchain.
Understanding smart contract verification is key for web3 developers, but enabling it programmatically is much more important.
In this tutorial, I will guide you through the steps necessary to create and deploy smart contracts and decentralized apps on the Morph Holesky Testnet, as well as the practical technique for verifying the deployed contract on Morphscan.
What is Morph?
Morph is the first optimistic zkEVM Ethereum Layer 2 solution that is 100% EVM compatible. Building on Morph is just like building on Ethereum. If you’re experienced in Ethereum development, you'll find your existing code, tooling, and dependencies are fully compatible with Morph.
Prerequisites 📚
Node JS (v16 or later)
NPM (v6 or later)
Metamask
Testnet ethers
Etherscan API Key
Dev Tools 🛠️
Yarn
npm install-g yarn
The source code for this tutorial is located here:
Smart contract verification can be performed manually on Etherscan, but it is advisable for developers to handle this programmatically. This can be achieved using an Etherscan API key, Hardhat plugins, and some custom logic.
Create a new file named QuoteDapp.sol in the contracts directory
// SPDX-License-Identifier: MITpragmasolidity^0.8.24;contractQuoteDapp{eventQuoteDapp__AddNewQuote(stringusername,stringcontent);errorQuoteNotFound();structQuote{stringusername;stringcontent;}// Array of quotesQuote[]publicquotes;functionaddQuote(stringmemory_username,stringmemory_content)public{// Add new quote to Quotes Arrayquotes.push(Quote(_username,_content));// Emit new eventemitQuoteDapp__AddNewQuote(_username,_content);}functiongetQuote(uint_index)publicviewreturns (stringmemory,stringmemory){// Check for availability of quoteif (_index>quotes.length){revertQuoteNotFound();}// Create a new variable to store found quoteQuotestoragequote=quotes[_index];// Return quote if found return (quote.username,quote.content);}functiongetQuoteCount()publicviewreturns (uint){returnquotes.length;}}
Step #7: Compile smart contract code
Specify the directory where the ABI should be stored
✍️ It should be noted that specifying the intended route for the Application Binary Interface (ABI) has no effect on the integration with the frontend. However, it is best practice to keep the ABI in the src folder because that is where all of our frontend code will go.
Step #8: Configure DApp for deployment
Create a new folder for deployment scripts in the root directory
mkdir deploy
Create a file for the deployment scripts in the deploy directory like this: 00-deploy-quote-dapp.cjs
Install an Hardhat plugin for the contract deployment
yarn add --dev hardhat-deploy
Import hardhat-deploy package into Hardhat configuration
require("hardhat-deploy")
Install another Hardhat plugin to override the @nomiclabs/hardhat-ethers package
Create a new folder for utilities in the root directory
mkdir utils
Create a new file named verify.cjs in the utils directory for the verification logic
Update verify.cjs with the following code:
const{run}=require("hardhat");constverify=async (contractAddress,args)=>{console.log(`Contract verification in progress...`);try{awaitrun("verify:verify",{address:contractAddress,constructorArguments:args,});}catch (e){if (e.message.toLowerCase().includes("verify")){console.log("Contract is already verified!");}else{console.log(e);}}};module.exports={verify};
Restructure the deploy script with the verification logic
Here is what your updated 00-deploy-quote-dapp.cjs should look like:
const{verify}=require("../utils/verify.cjs");module.exports=async ({getNamedAccounts,deployments})=>{const{deploy,log}=deployments;const{deployer}=awaitgetNamedAccounts();constargs=[];constquoteDapp=awaitdeploy("QuoteDapp",{contract:"QuoteDapp",args:args,from:deployer,log:true,// Logs statements to console});awaitverify(quoteDapp.target,args);log("Contract verified successfully...");log("---------------------------------");};module.exports.tags=["QuoteDapp"];
To verify the contract...open the terminal and run the command below:
✍️ The result of the verification should look like this... search the provided link in the browser. Yours will definitely be different from mine.
Successfully submitted source code for contract
contracts/QuoteDapp.sol:QuoteDapp at 0xF1a2D02114Ea3F9fF1C25d491BDDC2Ba0554B220
for verification on the block explorer. Waiting for verification result...
Successfully verified contract QuoteDapp on the block explorer.
https://explorer-holesky.morphl2.io/address/0xF1a2D02114Ea3F9fF1C25d491BDDC2Ba0554B220#code
Hurray! We've achieved our goal. We deployed and verified our smart contract successfully on Morph Holesky Testnet 🎉
Congratulations if you made it this far. However, a few more steps are required to complete the development of a full-stack decentralized application.
Here are a few more steps we need to complete in order to construct a full-stack DApp:
✅ Create unit tests with Mocha and Chai.
✅ Create and connect UI components.
✅ Interact with our Dapp.
Step #10: Write the unit tests
Install the Mocha and Chai dependencies for unit testing
yarn add --dev mocha chai@4.3.7
Create a new file named quote-dapp-test.cjs in the test directory.
Update the quote-dapp-test.cjs file with the following code.
const{expect}=require("chai");describe("QuoteDapp",function (){letQuoteDapp;letquoteDapp;before(asyncfunction (){// Deploy contract before running testsQuoteDapp=awaitethers.getContractFactory("QuoteDapp");quoteDapp=awaitQuoteDapp.deploy();awaitquoteDapp.waitForDeployment();});describe("addQuote",function (){it("Should add a new quote and emit the correct event",asyncfunction (){constusername="Lechero";constcontent="Hello! Feels good to know blockchain";// Add a new quoteawaitexpect(quoteDapp.addQuote(username,content)).to.emit(quoteDapp,"QuoteDapp__AddNewQuote").withArgs(username,content);// Check if the quote count is 1expect(awaitquoteDapp.getQuoteCount()).to.equal(1);// Retrieve the quote and check its contentconstquote=awaitquoteDapp.getQuote(0);expect(quote[0]).to.equal(username);expect(quote[1]).to.equal(content);});});describe("getQuote",function (){it("Should revert if the quote index is out of bounds",asyncfunction (){awaitexpect(quoteDapp.getQuote(999)).to.be.revertedWithCustomError(quoteDapp,"QuoteNotFound");});it("Should return the correct quote",asyncfunction (){constusername="Isabella";constcontent="Do more of Web3";// Add another quoteawaitquoteDapp.addQuote(username,content);// Check if the quote count is 2expect(awaitquoteDapp.getQuoteCount()).to.equal(2);// Retrieve the new quoteconstquote=awaitquoteDapp.getQuote(1);expect(quote[0]).to.equal(username);expect(quote[1]).to.equal(content);});});describe("getQuoteCount",function (){it("Should return the correct number of quotes",asyncfunction (){expect(awaitquoteDapp.getQuoteCount()).to.equal(2);});});});
Here is the command to run the tests.
yarn hardhat test
The result of the test should look like this.
QuoteDapp
addQuote
✔ Should add a new quote and emit the correct event
getQuote
✔ Should revert if the quote index is out of bounds
✔ Should return the correct quote
getQuoteCount
✔ Should return the correct number of quotes
4 passing (492ms)
✨ Done in 3.28s.
Additionally, we will utilize Solidity Coverage, a testing tool designed for developers to evaluate how code performs during execution. This helps to discover vulnerabilities that are not covered by the scope of the test.
Install solidity-coverage dependency.
yarn add --dev solidity-coverage
Require the plugin in the hardhat.config.cjs file.
require("solidity-coverage");
Run
yarn hardhat coverage
Having completely tested the smart contract code, the final result of the test should look like this.
Version
=======> solidity-coverage: v0.8.13
Instrumenting for coverage...
=============================> QuoteDapp.sol
Compilation:
============
Compiled 1 Solidity file successfully (evm target: paris).
Network Info
============> HardhatEVM: v2.22.10
> network: hardhat
QuoteDapp
addQuote
✔ Should add a new quote and emit the correct event
getQuote
✔ Should revert if the quote index is out of bounds
✔ Should return the correct quote
getQuoteCount
✔ Should return the correct number of quotes
4 passing (87ms)----------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
----------------|----------|----------|----------|----------|----------------|
contracts/ | 100 | 100 | 100 | 100 | |
QuoteDapp.sol | 100 | 100 | 100 | 100 | |
----------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
----------------|----------|----------|----------|----------|----------------|
> Istanbul reports written to ./coverage/ and ./coverage.json
✍️ This command will automatically create a directory for the coverage report. The directory and file have been included in the .gitignore file. Endeavour to accomplish this manually if necessary.
Frontend:
The next step is to create the DApp's frontend. So, if you have a smart contract on the blockchain, the only way to interact with it is through the command line. That is why you must provide a frontend that enables users to interact with the smart contract.
Decentralized apps are built by integrating a smart contract and a frontend.
The frontend can be either a mobile app or a web app, with the latter being more popular. A DApp's frontend is almost identical to that of a web application, including HTML, CSS, Javascript, and, optionally, a frontend framework like React.
Reason, we started the project by creating a React application. There are two blockchain-specific issues to address. The first is the integration method, followed by the integration with the wallet. Here's an overview of the main purpose of the React application:
Allow users create new quotes.
Retrieve quotes according to their index.
Ascertain the total number of published quotes.
Here are the few steps we need to take to achieve these goals listed above:
Create input fields and some local state so that users may make new posts.
Create an input field and some local state to obtain the total number of quotes that were published.
Allow the application to connect to the user's wallet for signing transactions.
Create functions for reading and writing to the blockchain.
Key Takeaways
✅ Create a Solidity smart contract.
✅ Generate Etherscan API key.
✅ Successfully deployed smart contract on Morph Holesky Testnet.
✅ Automatic verification of smart contract source code.
✅ Complete unit testing using solidity-coverage plugin.
Conclusion
This tutorial demonstrates how Morph facilitates the creation of decentralized applications. This is a comprehensive guide for developers and their teams to create creative solutions on the Morph L2 blockchain. I would be delighted to see you contribute to the concept of this project, and when you do, please leave a comment with a link to your code so that we can build together.
Join our Discord channel or follow us on Twitter to learn more.