Decentralized finance (DeFi) has been revolutionizing the financial industry, with Ethereum at the core of this transformation. In this guide, we'll walk you through the process of creating a simple DeFi protocol on Ethereum using Hardhat, a powerful development environment for Ethereum. By the end of this tutorial, you'll have a basic understanding of the tools and techniques required to build your own DeFi protocol.
Disclaimer: This article is for educational purposes only. Do not use this information to build a real-world DeFi protocol without proper research, security audits, and legal advice.
To follow this tutorial, you'll need:
We'll use Hardhat, a popular development environment for Ethereum. To install Hardhat, open your terminal and run:
npm install --global hardhat
Navigate to your desired project folder and run:
npx hardhat
Select "Create an empty hardhat.config.js" to create a new Hardhat project.
To interact with Ethereum, you'll need the following packages:
npm install --save-dev @nomiclabs/hardhat-ethers ethers @nomiclabs/hardhat-waffle ethereum-waffle chai
Add the following code to your hardhat.config.js
file to configure the Hardhat project:
require("@nomiclabs/hardhat-ethers");
module.exports = {
solidity: "0.8.4",
};
In the "contracts" folder, create a new file called SimpleDeFi.sol
. This file will contain the Solidity code for our DeFi protocol. Here's an example contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract SimpleDeFi {
using SafeERC20 for IERC20;
IERC20 public token;
address public owner;
constructor(address _token) {
token = IERC20(_token);
owner = msg.sender;
}
function deposit(uint256 amount) external {
require(amount > 0, "Amount must be greater than zero");
token.safeTransferFrom(msg.sender, address(this), amount);
}
function withdraw(uint256 amount) external {
require(token.balanceOf(address(this)) >= amount, "Insufficient balance");
require(msg.sender == owner, "Only owner can withdraw");
token.safeTransfer(owner, amount);
}
}
This simple DeFi protocol allows the owner to deposit and withdraw ERC20 tokens.
In the "test" folder, create a new file called SimpleDeFi.test.js
. Write tests to ensure the smart contract behaves as expected:
const { expect } = require("chai");
describe("SimpleDeFi", function () {
let SimpleDeFi, simpleDeFi, Token, token, owner, addr1;
beforeEach(async () => {
Token = await ethers.getContractFactory("ERC20Mock");
token = await Token.deploy();
await token.deployed();
SimpleDeFi = await ethers.getContractFactory("SimpleDeFi");
simpleDeFi = await SimpleDeFi.deploy(token.address);
await simpleDeFi.deployed();
[owner, addr1] = await ethers.getSigners();
});
describe("Deployment", () => {
it("Should set the correct owner", async () => {
expect(await simpleDeFi.owner()).to.equal(owner.address);
});
it("Should set the correct token", async () => {
expect(await simpleDeFi.token()).to.equal(token.address);
});
});
describe("Deposit and Withdraw", () => {
it("Should deposit tokens to the contract", async () => {
const amount = ethers.utils.parseEther("100");
await token.transfer(owner.address, amount);
await token.connect(owner).approve(simpleDeFi.address, amount);
await simpleDeFi.connect(owner).deposit(amount);
expect(await token.balanceOf(simpleDeFi.address)).to.equal(amount);
});
it("Should allow the owner to withdraw tokens", async () => {
const depositAmount = ethers.utils.parseEther("100");
const withdrawAmount = ethers.utils.parseEther("50");
await token.transfer(owner.address, depositAmount);
await token.connect(owner).approve(simpleDeFi.address, depositAmount);
await simpleDeFi.connect(owner).deposit(depositAmount);
await simpleDeFi.connect(owner).withdraw(withdrawAmount);
expect(await token.balanceOf(simpleDeFi.address)).to.equal(depositAmount.sub(withdrawAmount));
expect(await token.balanceOf(owner.address)).to.equal(withdrawAmount);
});
it("Should not allow non-owners to withdraw tokens", async () => {
const depositAmount = ethers.utils.parseEther("100");
await token.transfer(owner.address, depositAmount);
await token.connect(owner).approve(simpleDeFi.address, depositAmount);
await simpleDeFi.connect(owner).deposit(depositAmount);
await expect(simpleDeFi.connect(addr1).withdraw(depositAmount)).to.be.revertedWith("Only owner can withdraw");
});
});
});
In the terminal, execute the following command to run the tests:
npx hardhat test
This will execute the tests and show the results.
Create a deployment script in the "scripts" folder. Name it deploy.js
and add the following code:
async function main() {
const Token = await ethers.getContractFactory("ERC20Mock");
const token = await Token.deploy();
await token.deployed();
console.log("Token deployed to:", token.address);
const SimpleDeFi = await ethers.getContractFactory("SimpleDeFi");
const simpleDeFi = await SimpleDeFi.deploy(token.address);
await simpleDeFi.deployed();
console.log("SimpleDeFi deployed to:", simpleDeFi.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
To deploy the smart contract, run the following command:
npx hardhat run scripts/deploy.js
You can use Hardhat's console to interact with the deployed DeFi protocol. First, create a console.js
script in the "scripts" folder with the following code:
const hre = require("hardhat");
async function main() {
const [owner, addr1] = await hre.ethers.getSigners();
const tokenAddress = "YOUR_TOKEN_ADDRESS";
const simpleDeFiAddress = "YOUR_SIMPLEDEFI_ADDRESS";
const Token = await hre.ethers.getContractFactory("ERC20Mock");
const token = Token.attach(tokenAddress);
const SimpleDeFi = await hre.ethers.getContractFactory("SimpleDeFi");
const simpleDeFi = SimpleDeFi.attach(simpleDeFiAddress);
// Example: Deposit tokens
const depositAmount = hre.ethers.utils.parseEther("100");
await token.connect(owner).approve(simpleDeFi.address, depositAmount);
await simpleDeFi.connect(owner).deposit(depositAmount);
// Example: Check the contract's token balance
const balance = await token.balanceOf(simpleDeFi.address);
console.log("Contract balance:", hre.ethers.utils.formatEther(balance));
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Don't forget to replace YOUR_TOKEN_ADDRESS
and YOUR_SIMPLEDEFI_ADDRESS
with the actual addresses from your deployment.
Run the console.js
script using the following command:
npx hardhat run scripts/console.js
You can modify the console.js
script to interact with your DeFi protocol in different ways, such as depositing, withdrawing, and checking balances.
Conclusion:
In this tutorial, we've shown you how to create a simple DeFi protocol on Ethereum using Hardhat. We covered the basics of setting up a Hardhat project, writing a smart contract, testing, deploying, and interacting with the deployed contract. This is just the beginning; you can now extend and modify this basic DeFi protocol to create more complex and feature-rich financial applications on the Ethereum blockchain.