Why Verify Smart Contract?
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:
azeezabidoye / quote-dapp
Decentralized App for creating quotes
React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- @vitejs/plugin-react uses Babel for Fast Refresh
- @vitejs/plugin-react-swc uses SWC for Fast Refresh
Bonus: How to create Etherscan API Key π
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.
- Sign up/Sign in on etherscan.io
- Select your profile at the top right corner and choose
API Key
from the options.
- Select
Add
button to generate a new API key
- Provide a name for your project and select
Create New API Key
Development π·ββοΈ
Here is a step-by-step guideΒ to the needed technique for developing and verifying source code of decentralized applications on Morph blockchain.
Step #1: Create a new React project
npm create vite@latest quote-dapp --template react
- Navigate into the new project directory.
cd quote-dapp
Step #2: Install Hardhat dependency using yarn
.
yarn add hardhat
Step #3: Initialize Hardhat for development.
yarn hardhat init
- Select
Create a JavaScript project
- Approve
y
"Yes" togitignore
Step #4: Setup environment variables
- Install an NPM module that loads environment variable from
.env
file
yarn add --dev dotenv
- Create a new file in the root directory named
.env
. - Create three (3) variables needed for configuration
PRIVATE_KEY="Insert-Private-Key"
ETHERSCAN_API_KEY="Insert-Etherscan-Api-Key"
CONTRACT_ADDRESS="Insert-Contract-Address"
βοΈ An example of the file is included in the source code above. Rename the
.env_example
to.env
and populate the variables therein accordingly
Step #5: Setup Hardhat configuration for development
- Navigate to
hardhat.config.cjs
file to setup Hardhat configuration
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
const { PRIVATE_KEY, ETHERSCAN_API_KEY} = process.env;
module.exports = {
solidity: "0.8.24",
networks: {
hardhat: { chainId: 1337 },
morphTestnet: {
url: "https://rpc-quicknode-holesky.morphl2.io",
accounts: [PRIVATE_KEY],
chainId: 2810,
gasPrice: 2000000000,
}
}
};
Step #6: Write the smart contract code
- Create a new file named
QuoteDapp.sol
in thecontracts
directory
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract QuoteDapp {
event QuoteDapp__AddNewQuote(string username, string content);
error QuoteNotFound();
struct Quote {
string username;
string content;
}
// Array of quotes
Quote[] public quotes;
function addQuote(string memory _username, string memory _content) public {
// Add new quote to Quotes Array
quotes.push(Quote(_username, _content));
// Emit new event
emit QuoteDapp__AddNewQuote(_username, _content);
}
function getQuote(uint _index) public view returns (string memory, string memory) {
// Check for availability of quote
if (_index > quotes.length) {
revert QuoteNotFound();
}
// Create a new variable to store found quote
Quote storage quote = quotes[_index];
// Return quote if found
return (quote.username, quote.content);
}
function getQuoteCount() public view returns (uint) {
return quotes.length;
}
}
Step #7: Compile smart contract code
- Specify the directory where the ABI should be stored
module.exports = {
solidity: "0.8.24",
paths: {
artifacts: "./src/artifacts",
},
networks: {
// Code here...
}
};
- Run the command below in the terminal
yarn hardhat compile
βοΈ 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
yarn add --dev @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers
- Set up a deployer account in the Hardhat configuration file
networks: {
// Code Here
},
namedAccounts: {
deployer: {
default: 0,
}
}
- The deploy script needed to be updated with the following code
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy, log } = deployments;
const { deployer } = await getNamedAccounts();
const args = [];
await deploy("QuoteDapp", {
contract: "QuoteDapp",
args: args,
from: deployer,
log: true,
});
};
module.exports.tags = ["QuoteDapp"];
- Run this command to deploy the contract on the Morph Holesky testnet
yarn hardhat deploy --network morphTestnet
βοΈ Copy the address of the deployed contract. To update the environment variable initially created for it
Step #9: Update Hardhat configuration for automatic source code verification
- Update your Hardhat configuration
require("@nomicfoundation/hardhat-verify");
- Add the following Etherscan configs to your
hardhat.config.cjs
file:
module.exports = {
namedAccounts: {
// Code here...
},
etherscan: {
apiKey: ETHERSCAN_API_KEY,
customChains: [
{
networks: "morphTestnet",
chainId: 2810,
urls: {
apiURL: "https://explorer-api-holesky.morphl2.io/api?",
browserURL: "https://explorer-holesky.morphl2.io/",
},
},
],
},
};
- Create a new folder for utilities in the root directory
mkdir utils
Create a new file named
verify.cjs
in theutils
directory for the verification logicUpdate
verify.cjs
with the following code:
const { run } = require("hardhat");
const verify = async (contractAddress, args) => {
console.log(`Contract verification in progress...`);
try {
await run("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 } = await getNamedAccounts();
const args = [];
const quoteDapp = await deploy("QuoteDapp", {
contract: "QuoteDapp",
args: args,
from: deployer,
log: true, // Logs statements to console
});
await verify(quoteDapp.target, args);
log("Contract verified successfully...");
log("---------------------------------");
};
module.exports.tags = ["QuoteDapp"];
- To verify the contract...open the terminal and run the command below:
yarn hardhat verify [CONTRACT_ADDRESS] [CONSTRUCTOR_ARGS] --network sepolia
In our case, the smart contract doesn't contain a function constructor, therefore we can skip the arguments
Run:
yarn hardhat verify [CONTRACT_ADDRESS] --network morphTestnet
βοΈ 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 thetest
directory.Update the
quote-dapp-test.cjs
file with the following code.
const { expect } = require("chai");
describe("QuoteDapp", function () {
let QuoteDapp;
let quoteDapp;
before(async function () {
// Deploy contract before running tests
QuoteDapp = await ethers.getContractFactory("QuoteDapp");
quoteDapp = await QuoteDapp.deploy();
await quoteDapp.waitForDeployment();
});
describe("addQuote", function () {
it("Should add a new quote and emit the correct event", async function () {
const username = "Lechero";
const content = "Hello! Feels good to know blockchain";
// Add a new quote
await expect(quoteDapp.addQuote(username, content))
.to.emit(quoteDapp, "QuoteDapp__AddNewQuote")
.withArgs(username, content);
// Check if the quote count is 1
expect(await quoteDapp.getQuoteCount()).to.equal(1);
// Retrieve the quote and check its content
const quote = await quoteDapp.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", async function () {
await expect(quoteDapp.getQuote(999)).to.be.revertedWithCustomError(
quoteDapp,
"QuoteNotFound"
);
});
it("Should return the correct quote", async function () {
const username = "Isabella";
const content = "Do more of Web3";
// Add another quote
await quoteDapp.addQuote(username, content);
// Check if the quote count is 2
expect(await quoteDapp.getQuoteCount()).to.equal(2);
// Retrieve the new quote
const quote = await quoteDapp.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", async function () {
expect(await quoteDapp.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.
Top comments (1)
Good Work Ser