From d690bb55d811245e0003274a0ce58bebb280e7f5 Mon Sep 17 00:00:00 2001 From: passygigi Date: Wed, 30 Jul 2025 06:54:53 +0100 Subject: [PATCH] chore:erc20testing --- .gitignore | 17 --- contracts/BlockToken.sol | 38 +++++ contracts/Counter.sol | 48 ------- test/BlockToken.js | 292 +++++++++++++++++++++++++++++++++++++++ test/Counter.js | 49 ------- 5 files changed, 330 insertions(+), 114 deletions(-) create mode 100644 contracts/BlockToken.sol delete mode 100644 contracts/Counter.sol create mode 100644 test/BlockToken.js delete mode 100644 test/Counter.js diff --git a/.gitignore b/.gitignore index e8c12ff..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +0,0 @@ -node_modules -.env - -# Hardhat files -/cache -/artifacts - -# TypeChain files -/typechain -/typechain-types - -# solidity-coverage files -/coverage -/coverage.json - -# Hardhat Ignition default folder for deployments against a local node -ignition/deployments/chain-31337 diff --git a/contracts/BlockToken.sol b/contracts/BlockToken.sol new file mode 100644 index 0000000..9e127da --- /dev/null +++ b/contracts/BlockToken.sol @@ -0,0 +1,38 @@ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract BlockToken is ERC20{ + + address public owner; + + modifier onlyOwner { + require(msg.sender == owner, "BlockToken:: Unauthorized User"); + _; + } + + modifier notAmount0(uint256 _amount){ + require(_amount != 0, "BlockToken:: Zero amount not supported"); + _; + } + constructor(string memory _name, string memory _symbol, address _owner) ERC20(_name, _symbol){ + require(_owner != address(0), "BlockToken:: Zero address not supported"); + owner = _owner; + } + + function mint(uint256 _amount, address _recepient) onlyOwner notAmount0(_amount) external { + _mint(_recepient, _amount); + } + + function burn(uint256 _amount) notAmount0(_amount) external { + _burn(msg.sender, _amount); + } + + function burnFrom(address _user, uint256 _amount)onlyOwner notAmount0(_amount) external { + _burn(_user, _amount); + } + + +} \ No newline at end of file diff --git a/contracts/Counter.sol b/contracts/Counter.sol deleted file mode 100644 index fa8560c..0000000 --- a/contracts/Counter.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.28; - -interface ICounter { - function setCount(uint256 _count) external; - function increaseCountByOne() external; - function getCount() external view returns(uint256); - -} - -contract Counter is ICounter { - uint256 public count; - - function setCount(uint256 _count) external { - count = _count; - } - - function increaseCountByOne() public { - count += 1; - } - - function getCount() public view returns(uint256) { - return count; - } -} - - -// contract F { -// // Initializing interface IC -// IC public _ic; -// // Initializing the contract address -// address public contractCAddress; - -// constructor(address _contractCAddress) { -// // Set the contract address to the state variable contract address -// contractCAddress = _contractCAddress; -// // Passing the contract address into interface using the address instance of another contract -// _ic = IC(_contractCAddress); -// } - -// function setCount(uint256 _count) public { -// _ic.setCount(_count); -// } - -// function getCount() public view returns(uint256) { -// return _ic.getCount(); -// } -// } \ No newline at end of file diff --git a/test/BlockToken.js b/test/BlockToken.js new file mode 100644 index 0000000..2d891e3 --- /dev/null +++ b/test/BlockToken.js @@ -0,0 +1,292 @@ +const { + loadFixture, +} = require("@nomicfoundation/hardhat-toolbox/network-helpers"); +const { expect } = require("chai"); + +const deployContract = async () => { + let _name = "BlockToken"; + let _symbol = "BCT"; + const [_owner, addr1, addr2] = await ethers.getSigners(); + const BlockTokenContract = await ethers.getContractFactory("BlockToken"); + const BlockToken = await BlockTokenContract.deploy( + _name, + _symbol, + _owner.address + ); + + return { BlockToken, _owner, addr1, addr2, _name, _symbol }; +}; + +describe("BlockToken Test Suite", () => { + describe("Deployment", () => { + it("Should return set values upon deployment", async () => { + const { BlockToken, _name, _symbol, _owner } = await loadFixture( + deployContract + ); + // console.log(_owner.address); + expect(await BlockToken.name()).to.eq(_name); + expect(await BlockToken.symbol()).to.eq(_symbol); + expect(await BlockToken.owner()).to.eq(_owner.address); + }); + + it("Should revert when owner is zero address", async () => { + const BlockTokenContract = await ethers.getContractFactory("BlockToken"); + const zeroAddress = "0x0000000000000000000000000000000000000000"; + + await expect( + BlockTokenContract.deploy("Token", "TKN", zeroAddress) + ).to.be.revertedWith("BlockToken:: Zero address not supported"); + }); + }); + + describe("Minting", () => { + it("Only the owner can call mint", async () => { + const { BlockToken, _owner, addr1, addr2 } = await loadFixture( + deployContract + ); + + await BlockToken.connect(_owner).mint(10, addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(10); + + await expect( + BlockToken.connect(addr1).mint(1000, addr2) + ).to.be.revertedWith("BlockToken:: Unauthorized User"); + }); + + it("Should revert if minting amount is zero", async () => { + const { BlockToken, _owner, addr1 } = await loadFixture(deployContract); + await expect( + BlockToken.connect(_owner).mint(0, addr1) + ).to.be.revertedWith("BlockToken:: Zero amount not supported"); + }); + }); + + describe("Burning", () => { + it("Should not burn if user doesn't have tokens", async () => { + const { BlockToken, _owner, addr1 } = await loadFixture(deployContract); + + await expect( + BlockToken.connect(addr1).burn(1000) + ).to.be.revertedWithCustomError(BlockToken, "ERC20InsufficientBalance"); + }); + + it("Should burn tokens successfully", async () => { + const { BlockToken, _owner } = await loadFixture(deployContract); + + await BlockToken.connect(_owner).mint(1000, _owner); + + const balance = await BlockToken.balanceOf(_owner); + expect(balance).to.eq(1000); + + await BlockToken.connect(_owner).burn(100); + const balance2 = await BlockToken.balanceOf(_owner); + expect(balance2).to.eq(900); + }); + + it("burnFrom should be restricted to the owner", async () => { + const { BlockToken, _owner, addr1, addr2 } = await loadFixture( + deployContract + ); + + await BlockToken.connect(_owner).mint(1000, addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + // Check if address 1 can burn from his address + await expect( + BlockToken.connect(addr1).burnFrom(addr1, 800) + ).to.be.revertedWith("BlockToken:: Unauthorized User"); + + await BlockToken.connect(_owner).mint(900, addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(900); + + // Check if address 1 can burn from another address + await expect( + BlockToken.connect(addr1).burnFrom(addr2, 800) + ).to.be.revertedWith("BlockToken:: Unauthorized User"); + + // Check if owner can burn from successfully + await BlockToken.connect(_owner).burnFrom(addr1, 200); + + const balance = await BlockToken.balanceOf(addr1); + expect(balance).to.eq(800); + }); + + it("burn should fail if the amount is zero", async () => { + const { BlockToken, _owner, addr1 } = await loadFixture(deployContract); + + await BlockToken.connect(_owner).mint(1000, addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + await expect( + BlockToken.connect(_owner).burnFrom(addr1, 0) + ).to.be.revertedWith("BlockToken:: Zero amount not supported"); + }); + }); + + describe("Transactions", () => { + describe("Transfers", () => { + it("Transfers tokens from sender to recipient correctly", async () => { + const { BlockToken, _owner, addr1, addr2 } = await loadFixture( + deployContract + ); + + const zeroAddress = "0x0000000000000000000000000000000000000000"; + + // Transfer to zero address + await expect(BlockToken.connect(_owner).transfer(zeroAddress, 800)).to + .be.reverted; + + await BlockToken.connect(_owner).mint(1000, _owner); + expect(await BlockToken.balanceOf(_owner)).to.eq(1000); + + // Transfer from owner to other account + await BlockToken.connect(_owner).transfer(addr1, 800); + expect(await BlockToken.balanceOf(_owner)).to.eq(200); + expect(await BlockToken.balanceOf(addr1)).to.eq(800); + + // Transfer from account to account + await BlockToken.connect(addr1).transfer(addr2, 400); + expect(await BlockToken.balanceOf(addr1)).to.eq(400); + expect(await BlockToken.balanceOf(addr2)).to.eq(400); + }); + + it("Transfer should succeed if amount is less than or equal to balance", async () => { + const { BlockToken, _owner, addr1 } = await loadFixture(deployContract); + + await BlockToken.connect(_owner).mint(1000, _owner); + expect(await BlockToken.balanceOf(_owner)).to.eq(1000); + + await BlockToken.connect(_owner).transfer(addr1, 1000); + expect(await BlockToken.balanceOf(_owner)).to.eq(0); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + await expect( + BlockToken.connect(_owner).transfer(addr1, 100) + ).to.be.revertedWithCustomError(BlockToken, "ERC20InsufficientBalance"); + expect(await BlockToken.balanceOf(_owner)).to.eq(0); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + }); + + it("Should allow owner to approve a spender for transferFrom", async () => { + const { BlockToken, _owner, addr1 } = await loadFixture(deployContract); + + await BlockToken.connect(_owner).mint(1000, _owner); + expect(await BlockToken.balanceOf(_owner)).to.eq(1000); + + await BlockToken.connect(_owner).approve(addr1, 500); + expect(await BlockToken.allowance(_owner, addr1)).to.eq(500); + }); + + it("Should execute transferFrom only when approved", async () => { + const { BlockToken, _owner, addr1, addr2 } = await loadFixture( + deployContract + ); + + await BlockToken.connect(_owner).mint(1000, addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + await BlockToken.connect(addr1).approve(_owner, 500); + expect(await BlockToken.connect(addr1).allowance(addr1, _owner)).to.eq( + 500 + ); + await BlockToken.connect(_owner).transferFrom(addr1, addr2, 400); + + expect(await BlockToken.connect(addr2).balanceOf(addr2)).to.eq(400); + }); + + it("Should revert if the from address has insufficient balance", async () => { + const { BlockToken, _owner, addr1, addr2 } = await loadFixture( + deployContract + ); + + await BlockToken.connect(_owner).mint(1000, addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + await BlockToken.connect(addr1).approve(_owner, 500); + expect(await BlockToken.connect(addr1).allowance(addr1, _owner)).to.eq( + 500 + ); + await expect( + BlockToken.connect(_owner).transferFrom(addr1, addr2, 700) + ).to.be.revertedWithCustomError( + BlockToken, + "ERC20InsufficientAllowance" + ); + }); + + it("Should not allow transfer if from balance is too low", async () => { + const { BlockToken, _owner, addr1, addr2 } = await loadFixture( + deployContract + ); + + await BlockToken.connect(_owner).mint(1000, addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + await BlockToken.connect(addr1).approve(_owner, 500); + expect(await BlockToken.connect(addr1).allowance(addr1, _owner)).to.eq( + 500 + ); + await expect( + BlockToken.connect(_owner).transferFrom(addr1, addr2, 700) + ).to.be.revertedWithCustomError( + BlockToken, + "ERC20InsufficientAllowance" + ); + }); + + it("Reverts if spender attempts transfer with zero allowance", async () => { + const { BlockToken, _owner, addr1, addr2 } = await loadFixture( + deployContract + ); + + await BlockToken.connect(_owner).mint(1000, addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + await BlockToken.connect(addr1).approve(_owner, 0); + expect(await BlockToken.connect(addr1).allowance(addr1, _owner)).to.eq( + 0 + ); + await expect( + BlockToken.connect(_owner).transferFrom(addr1, addr2, 700) + ).to.be.revertedWithCustomError( + BlockToken, + "ERC20InsufficientAllowance" + ); + }); + + it("Should not allow transfers to the zero address", async () => { + const { BlockToken, _owner, addr1, addr2 } = await loadFixture( + deployContract + ); + const zeroAddress = "0x0000000000000000000000000000000000000000"; + + await BlockToken.connect(_owner).mint(1000, addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + await BlockToken.connect(addr1).approve(_owner, 500); + expect(await BlockToken.connect(addr1).allowance(addr1, _owner)).to.eq( + 500 + ); + await expect( + BlockToken.connect(_owner).transferFrom(addr1, zeroAddress, 400) + ).to.be.revertedWithCustomError(BlockToken, "ERC20InvalidReceiver"); + }); + + it("Should revert if allowance is changed without first setting it to zero", async () => { + const { BlockToken, _owner, addr1, addr2 } = await loadFixture( + deployContract + ); + + await BlockToken.connect(_owner).mint(1000, addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + await BlockToken.connect(addr1).approve(_owner, 500); + + await BlockToken.connect(addr1).approve(_owner, 700); + expect(await BlockToken.connect(addr1).allowance(addr1, _owner)).to.eq( + 700 + ); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/Counter.js b/test/Counter.js deleted file mode 100644 index 99b2931..0000000 --- a/test/Counter.js +++ /dev/null @@ -1,49 +0,0 @@ -const {loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers"); -// const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); -const { expect } = require("chai"); - -// util functon -const deployCounter = async () => { - // target the Counter contract within our contract folder - const CounterContract = await ethers.getContractFactory("Counter"); // target Counter.sol - const counter = await CounterContract.deploy(); // deploy the Counter contract - return counter ; // return the deployed instance of our counter contract -} - -// Counter Test Suite -describe("Counter Test Suite", () => { - describe("Deployment", () => { - it("Should return default values upon deployment", async () => { - const counter = await loadFixture(deployCounter); - expect(await counter.count()).to.eq(0); // assert that count = 0 upon deployment - }) - }) - - describe("Transactions", () => { - describe("SetCount", () => { - it("Should set appropriate count values", async () => { - const counter = await loadFixture(deployCounter); // extract deployed counter instace - let count1 = await counter.getCount(); // check initial count value before txn - expect(count1).to.eq(0); - await counter.setCount(10) // assert that count = 0 upon deployment - - let count2 = await counter.getCount(); // check initial count value before txn - expect(count2).to.eq(10) // check final count = 10 - }) - - it("Should set appropriate values for multiple setCount txns", async () => { - - }) - }) - - describe("IncreaseCountByOne", () => { - it("Should set appropriate increaseCountByOne value", async () => { - - }) - - it("Should set appropriate values for multiple increaseCountByOne txns", async () => { - - }) - }) - }) -}) \ No newline at end of file