Listen to this story
Writing colorful characters in text files since 2011
The code in this story is for educational purposes. The readers are solely responsible for whatever they build with it.
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.