diff --git a/contracts/BlockHeaderToken.sol b/contracts/BlockHeaderToken.sol new file mode 100644 index 0000000..ffc35eb --- /dev/null +++ b/contracts/BlockHeaderToken.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); + } + + +} + diff --git a/contracts/BlockMarketPlace.sol b/contracts/BlockMarketPlace.sol new file mode 100644 index 0000000..799748b --- /dev/null +++ b/contracts/BlockMarketPlace.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: SEE LICENSE IN LICENSE +pragma solidity 0.8.28; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +struct Listing { + address owner; + uint256 tokenId; + IERC20 paymentToken; + address NftToken; + bool isNative; + uint256 price; + bool sold; + uint minOffer; +} + +struct OfferDetails { + uint256 listId; + uint256 offerAmount; + address offerrer; + bool status; +} + +contract BlockMarketPlace { + mapping(uint256 listid => Listing list) public idToListing; + mapping(uint256 offerid => OfferDetails offer) public idToOffer; + + uint256 public lastUpdatedid; + uint256 public lastOfferId; + address public marketOwner; + + constructor() { + marketOwner = msg.sender; + } + + function listNft(Listing memory list) external { + uint listId = lastUpdatedid++; + require(list.price > 0, "Invalid price"); + require(list.minOffer > 0, "Invalid min offer"); + if (list.isNative) { + require( + address(list.paymentToken) == address(0), + "ERC20 Payment is not supported" + ); + } + Listing memory listing; + listing.owner = msg.sender; + listing.tokenId = list.tokenId; + listing.paymentToken = list.paymentToken; + listing.price = list.price; + listing.isNative = list.isNative; + listing.minOffer = list.minOffer; + listing.NftToken = list.NftToken; + idToListing[listId] = listing; + + IERC721(list.NftToken).transferFrom( + msg.sender, + address(this), + list.tokenId + ); + } + function getListing(uint256 listId) external view returns (Listing memory) { + return idToListing[listId]; + } + + //buy NFT function + function buyNft(uint256 listId) external payable { + Listing memory l = idToListing[listId]; + require(!l.sold, "Already Sold"); + idToListing[listId].sold = true; + + if (l.isNative) { + require(msg.value == l.price, "Incorrect price"); + (bool s, ) = l.owner.call{value: (l.price * 97) / 100}(""); + (bool ss, ) = marketOwner.call{value: (l.price * 3) / 100}(""); + require(s, "Owner transfer failed"); + require(ss, "MarketOwner Transfer failed"); + } else { + l.paymentToken.transferFrom( + msg.sender, + l.owner, + (l.price * 97) / 100 + ); + l.paymentToken.transferFrom( + msg.sender, + marketOwner, + (l.price * 3) / 100 + ); + } + IERC721(l.NftToken).transferFrom(address(this), msg.sender, l.tokenId); + } + + //offer function + function offer(uint256 listid, uint256 offerAmount) external payable { + uint256 offerId = lastOfferId++; + Listing memory l = idToListing[listid]; + require(!l.sold, "Already sold"); + if (l.isNative) { + require(msg.value >= l.minOffer, "Invalid offer"); + require(offerAmount == 0, "Cannot offer erc20"); + } else { + require(offerAmount >= l.minOffer, "Invalid offer"); + l.paymentToken.transferFrom(msg.sender, address(this), offerAmount); + } + require(msg.sender != l.owner, "Owner cannot offer"); + OfferDetails memory offer_; + offer_.listId = listid; + offer_.offerrer = msg.sender; + offer_.offerAmount = l.isNative ? msg.value : offerAmount; + + idToOffer[offerId] = offer_; + } + + //getter function for offerDetailStruct + function getOffer( + uint256 offerId + ) external view returns (OfferDetails memory o) { + o = idToOffer[offerId]; + } + + function acceptOffer(uint256 offerid) external { + OfferDetails memory offer_ = idToOffer[offerid]; + Listing memory l = idToListing[offer_.listId]; + // Checks + require(l.owner == msg.sender, "Unauthorized seller"); + require(!l.sold, "Already Sold"); + require(offer_.offerrer != address(0), "Invalid offer"); + // Effects + idToListing[offer_.listId].sold = true; + idToOffer[offerid].status = true; + // Interactions + if (l.isNative) { + (bool success, ) = l.owner.call{ + value: (offer_.offerAmount * 97) / 100 + }(""); + (bool success2, ) = marketOwner.call{ + value: (offer_.offerAmount * 3) / 100 + }(""); + require(success, "Failed owner transfer"); + require(success2, "Failed marketPlace commission transfer"); + } else { + l.paymentToken.transfer(l.owner, (offer_.offerAmount * 97) / 100); + l.paymentToken.transfer( + marketOwner, + (offer_.offerAmount * 3) / 100 + ); + } + IERC721(l.NftToken).transferFrom( + address(this), + offer_.offerrer, + l.tokenId + ); + } + + //function to cancel order + function cancelOffer(uint256 offerid) external { + OfferDetails memory offer_ = idToOffer[offerid]; + Listing memory l = idToListing[offer_.listId]; + // Checks + require(!offer_.status, "Offer already accepted"); + require(msg.sender == offer_.offerrer, "Unauthorized offerrer"); + // Effects + delete idToOffer[offerid]; + // Interactions + if (l.isNative) { + (bool s, ) = offer_.offerrer.call{value: offer_.offerAmount}(""); + require(s, "Failed refund"); + } else { + l.paymentToken.transfer(offer_.offerrer, offer_.offerAmount); + } + } + + function cancelListing(uint256 listid) external { + Listing memory l = idToListing[listid]; + // Checks + require(msg.sender == l.owner, "Unauthorized user"); + require(!l.sold, "Already sold"); + // Effects + delete idToListing[listid]; + // Interaction + IERC721(l.NftToken).transferFrom(address(this), l.owner, l.tokenId); + } +} diff --git a/contracts/BlockNft.sol b/contracts/BlockNft.sol new file mode 100644 index 0000000..5c66aee --- /dev/null +++ b/contracts/BlockNft.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: SEE LICENSE IN LICENSE +pragma solidity ^0.8.28; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract BlockNft is ERC721{ + uint256 public tokenID; + constructor() ERC721("BlockNft", "BKN"){} + + function mint(address recepient) external returns(uint256){ + tokenID++; + uint256 tokenId = tokenID; + _safeMint(recepient, tokenId); + return tokenId; + } +} \ 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/contracts/Lock.sol b/contracts/Lock.sol deleted file mode 100644 index 2f385f7..0000000 --- a/contracts/Lock.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.28; - -// Uncomment this line to use console.log -// import "hardhat/console.sol"; - -contract Lock { - uint public unlockTime; - address payable public owner; - - event Withdrawal(uint amount, uint when); - - constructor(uint _unlockTime) payable { - require( - block.timestamp < _unlockTime, - "Unlock time should be in the future" - ); - - unlockTime = _unlockTime; - owner = payable(msg.sender); - } - - function withdraw() public { - // Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal - // console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp); - - require(block.timestamp >= unlockTime, "You can't withdraw yet"); - require(msg.sender == owner, "You aren't the owner"); - - emit Withdrawal(address(this).balance, block.timestamp); - - owner.transfer(address(this).balance); - } -} diff --git a/package-lock.json b/package-lock.json index 656a34f..cc2bf83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,9 @@ "packages": { "": { "name": "hardhat-project", + "dependencies": { + "@openzeppelin/contracts": "^5.4.0" + }, "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^5.0.0", "hardhat": "^2.26.1" @@ -1305,6 +1308,12 @@ "node": ">= 12" } }, + "node_modules/@openzeppelin/contracts": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz", + "integrity": "sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==", + "license": "MIT" + }, "node_modules/@scure/base": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", diff --git a/package.json b/package.json index 0586079..a7c3ef4 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,13 @@ "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^5.0.0", "hardhat": "^2.26.1" - }, + }, "scripts": { - "test": "npx hardhat test", - "compile": "npx hardhat compile", + "test": "npx hardhat test", + "compile": "npx hardhat compile", "node": "npx hardhat node" + }, + "dependencies": { + "@openzeppelin/contracts": "^5.4.0" } } diff --git a/test/BlockMarketPlace.js b/test/BlockMarketPlace.js new file mode 100644 index 0000000..e008fdd --- /dev/null +++ b/test/BlockMarketPlace.js @@ -0,0 +1,925 @@ +const { + loadFixture, +} = require("@nomicfoundation/hardhat-toolbox/network-helpers"); +// const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); +const { expect, assert } = require("chai"); +const { ethers } = require("hardhat"); +const { bigint } = require("hardhat/internal/core/params/argumentTypes"); + +// util functon +const deployBlockMarketPlace = async () => { + // target the BlockMarketPlace contract within our contract folder + const [owner_, addr1, addr2,addr3,addr4] = await ethers.getSigners(); + const BlockMarketPlaceContract = await ethers.getContractFactory( + "BlockMarketPlace" + ); // target BlockMarketPlace.sol + const BlockNftContract = await ethers.getContractFactory("BlockNft"); + const BlockTokenContract = await ethers.getContractFactory("BlockToken"); + let name_ = "BlockToken"; + let symbol_ = "BCT"; + const BlockToken = await BlockTokenContract.deploy( + name_, + symbol_, + owner_.address + ); // deploy the BlockToken contract + const blocknft = await BlockNftContract.deploy(); + const marketplace = await BlockMarketPlaceContract.connect(owner_).deploy(); + // deploy the BlockMarketPlace contract + return { marketplace, blocknft, BlockToken, owner_, addr1, addr2,addr3,addr4}; // return the deployed instance of our BlockMarketPlace contract +}; + +describe("BlockMarketPlace Test Suite", () => { + describe("Deployment", () => { + it("Should return set values upon deployment", async () => { + const { marketplace, owner_ } = await loadFixture(deployBlockMarketPlace); + expect(await marketplace.marketOwner()).to.eq(owner_); + }); + }); + + describe("Listing", () => { + it("Should list Nft accordingly", async () => { + const { marketplace, addr1, BlockToken, blocknft } = await loadFixture( + deployBlockMarketPlace + ); + let tokenId = 1; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + await marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 100000, + sold: false, + minOffer: 10, + }); + + expect(await blocknft.ownerOf(tokenId)).to.eq( + await marketplace.getAddress() + ); + }); + + it("Should revert upon setting unaccepted values", async () => { + const { marketplace, addr1, BlockToken, blocknft } = await loadFixture( + deployBlockMarketPlace + ); + let tokenId = 1; + await blocknft.connect(addr1).mint(addr1); // mint nft + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); // approve + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 0, + sold: false, + minOffer: 10, + }); // list + + await expect(tx1).to.be.revertedWith("Invalid price"); + // tx2 + let tx2 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 10000, + sold: false, + minOffer: 0, + }); + + await expect(tx2).to.be.revertedWith("Invalid min offer"); + + // tx3 + let tx3 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: true, + price: 10000, + sold: false, + minOffer: 10, + }); + + await expect(tx3).to.be.revertedWith("ERC20 Payment is not supported"); + + let ZeroAddress = "0x0000000000000000000000000000000000000000"; + marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: ZeroAddress, + NftToken: blocknft.getAddress(), + isNative: true, + price: 10000, + sold: false, + minOffer: 10, + }); + + let [, , paymentToken, , ,] = await marketplace.getListing(1); + console.log(paymentToken); + + expect(await paymentToken).to.eq(ZeroAddress); + }); + it("Should return correct lastUpdatedid after multiple listings", async () => { + //Verify lastUpdatedid increments correctly + const { marketplace, blocknft, addr1, BlockToken } = await loadFixture(deployBlockMarketPlace); + let tokenId1 = 1; + let tokenId2 = 2; + await blocknft.connect(addr1).mint(addr1); + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft.connect(addr1).setApprovalForAll(marketplace.getAddress(), true); + await marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId1, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }); + await marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId2, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }); + expect(await marketplace.lastUpdatedid()).to.eq(2); + }); + }); + + describe("Buy NFT", async () => { + it("should revert if the NFT already sold or should buy succeefully", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + await BlockToken.connect(owner_).mint(1000,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(1000); + await BlockToken.connect(addr2).approve(marketplace,100); + await marketplace.connect(addr2).buyNft(listId); + expect(await blocknft.ownerOf(tokenId)).to.eq(addr2); + const listing = await marketplace.getListing(listId); + expect(listing.sold).to.equal(true); + + + + }) + it("should verify if funds are split currectly between seller and market owner", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).buyNft(listId); + expect(await blocknft.ownerOf(tokenId)).to.eq(addr2); + const listing = await marketplace.getListing(listId); + expect(listing.sold).to.equal(true); + console.log(await BlockToken.balanceOf(addr1)); + console.log(await BlockToken.balanceOf(addr2)); + expect(await BlockToken.balanceOf(addr1)).to.eq(48); + expect(await BlockToken.balanceOf(owner_)).to.eq(1); + }) + + it("should transfer NFT to buyer", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).buyNft(listId); + expect(await blocknft.ownerOf(tokenId)).to.eq(addr2); + }) + + + + + + + + + it("should allow listing with native currency and zero address payment token", async () => { + // Verify that a native currency listing with zero address payment token succeeds + const { marketplace, blocknft, addr1 } = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + await blocknft.connect(addr1).mint(addr1); + const zeroAddress = "0x0000000000000000000000000000000000000000"; + await blocknft.connect(addr1).setApprovalForAll(marketplace.getAddress(), true); + await marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: zeroAddress, + NftToken: blocknft.getAddress(), + isNative: true, + price:10, + sold: false, + minOffer: 2, + }); + const listing = await marketplace.getListing(0); + expect(listing.isNative).to.eq(true); + expect(listing.paymentToken).to.eq(zeroAddress); + expect(listing.owner).to.eq(addr1.address); + expect(await blocknft.ownerOf(tokenId)).to.eq(await marketplace.getAddress()); + }); + + it("should revert if native currency listing has non-zero payment token", async () => { + // Test that isNative=true with non-zero paymentToken reverts + const { marketplace, blocknft, BlockToken, addr1 } = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft.connect(addr1).setApprovalForAll(marketplace.getAddress(), true); + await expect( + marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: true, + price: ethers.parseEther("1"), + sold: false, + minOffer: ethers.parseEther("0.1"), + }) + ).to.be.revertedWith("ERC20 Payment is not supported"); + + }); + + + + + + + it("should revert if incorrect ETH amount sent for native currency", async () => { + // Purpose: Test that buying with incorrect ETH amount for isNative=true reverts + const { marketplace, blocknft, addr1, addr2 } = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + const zeroAddress = "0x0000000000000000000000000000000000000000"; + await blocknft.connect(addr1).setApprovalForAll(marketplace.getAddress(), true); + await marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: zeroAddress, + NftToken: blocknft.getAddress(), + isNative: true, + price: ethers.parseEther("1"), + sold: false, + minOffer: ethers.parseEther("0.1"), + }); + await expect( + marketplace.connect(addr2).buyNft(listId, { value: ethers.parseEther("0.5") }) + ).to.be.revertedWith("Incorrect price"); + }); + + it("should allow buying with ERC20 for non-native currency", async () => { + // Purpose: Verify that buying with ERC20 for isNative=false succeeds + const { marketplace, blocknft, BlockToken, owner_, addr1, addr2 } = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft.connect(addr1).setApprovalForAll(marketplace.getAddress(), true); + const price = 50; + await marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }); + await BlockToken.connect(owner_).mint(100, addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(100); + await BlockToken.connect(addr2).approve(marketplace, price); + await marketplace.connect(addr2).buyNft(listId); + expect(await blocknft.ownerOf(tokenId)).to.eq(addr2.address); + expect(await BlockToken.balanceOf(addr1)).to.eq(48); + expect(await BlockToken.balanceOf(owner_)).to.eq(1); + const listing = await marketplace.getListing(listId); + expect(listing.sold).to.equal(true); + }); + + + + + + + + + + +describe("make offer", async()=> { + + it("should accept a valid offer", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 30, + sold: false, + minOffer: 10, + }) + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await expect(marketplace.connect(addr2).offer(listId,5)).to.be.revertedWith("Invalid offer"); + + }) + + it("should revert if NFT already sold", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).buyNft(listId); + expect(await blocknft.ownerOf(tokenId)).to.eq(addr2); + const listing = await marketplace.getListing(listId); + expect(listing.sold).to.equal(true); + + await BlockToken.connect(owner_).mint(50,addr3); + expect( await BlockToken.balanceOf(addr3)).to.eq(50); + await BlockToken.connect(addr3).approve(marketplace,50); + await expect(marketplace.connect(addr3).offer(listId,30)).to.be.revertedWith("Already sold"); + + }) + it("should revert if owner tries to make an offer", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + await BlockToken.connect(owner_).mint(50,addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(50); + await BlockToken.connect(addr1).approve(marketplace,50); + await expect(marketplace.connect(addr1).offer(listId,30)).to.be.revertedWith("Owner cannot offer"); + + + }) + it("should lock funds in contract", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).offer(listId,20); + expect(await BlockToken.balanceOf(marketplace)).to.eq(20); + }) + + it("should revert if ETH offer is below minimum offer", async () => { + // Purpose: Verify that an offer with insufficient ETH reverts with "Invalid offer" + const { marketplace, blocknft, addr1, addr2 } = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + const zeroAddress = "0x0000000000000000000000000000000000000000"; + await blocknft.connect(addr1).setApprovalForAll(marketplace.getAddress(), true); + // List NFT with native currency + await marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: zeroAddress, + NftToken: blocknft.getAddress(), + isNative: true, + price: 10, + sold: false, + minOffer: 3, + }); + // Offer with insufficient ETH + await expect( + marketplace.connect(addr2).offer(listId, 0) + ).to.be.revertedWith("Invalid offer"); + }); + + it("should revert if non-zero offerAmount is provided for native currency", async () => { + // Purpose: Verify that a non-zero offerAmount for a native currency listing reverts + const { marketplace, blocknft, addr1, addr2 } = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + const zeroAddress = "0x0000000000000000000000000000000000000000"; + await blocknft.connect(addr1).setApprovalForAll(marketplace.getAddress(), true); + // List NFT with native currency + await marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: zeroAddress, + NftToken: blocknft.getAddress(), + isNative: true, + price: ethers.parseEther("1"), + sold: false, + minOffer: ethers.parseEther("0.1"), + }); + // Offer with non-zero offerAmount + await expect( + marketplace.connect(addr2).offer(listId, ethers.parseEther("0.1"), { value: ethers.parseEther("0.1") }) + ).to.be.revertedWith("Cannot offer erc20"); + }); + + + describe("Accept offer", async()=>{ + it("should allow seller to accept valid offer", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + let offerId = await marketplace.getOffer(0); + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).offer(listId,20); + await marketplace.connect(addr1).acceptOffer(offerId.offerrer); + + + }) + + it("should revert if not seller", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + let offerId = await marketplace.getOffer(0); + console.log(offerId); + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).offer(listId,20); + await expect(marketplace.connect(addr3).acceptOffer(offerId.offerrer)).to.be.revertedWith("Unauthorized seller"); + + }) + + + it("should transfer NFT to offerer", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + let offerId = await marketplace.getOffer(0); + + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).offer(listId,20); + await marketplace.connect(addr1).acceptOffer(offerId.offerrer); + expect(await blocknft.ownerOf(tokenId)).is.eq(addr2); + }) + + it("should distribute funds properly", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + let offerId = await marketplace.getOffer(0); + + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).offer(listId,19); + await marketplace.connect(addr1).acceptOffer(offerId.offerrer); + expect(await blocknft.ownerOf(tokenId)).is.eq(addr2); + expect(await BlockToken.balanceOf(addr1)).to.eq(18); + expect( await BlockToken.balanceOf(marketplace)).to.eq(1); + + }) + }); + + describe("cancel offer", async()=>{ + + it("should allow offerer to cancel before it's accepted", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + let offerId = await marketplace.getOffer(0); + + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).offer(listId,20); + await marketplace.connect(addr2).cancelOffer(offerId.offerrer); + expect(await blocknft.ownerOf(tokenId)).to.eq(await marketplace.getAddress()); + + }) + + it("should revert if not offerer", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + let offerId = await marketplace.getOffer(0); + + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).offer(listId,20); + await expect(marketplace.connect(addr3).cancelOffer(offerId.offerrer)).to.be.revertedWith("Unauthorized offerrer"); + }) + + it("should return funds back", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + let offerId = await marketplace.getOffer(0); + + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).offer(listId,20); + await marketplace.connect(addr2).cancelOffer(offerId.offerrer); + expect(await BlockToken.balanceOf(addr2)).be.eq(50); + }) + it("should delete offer from storage", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + let offerId = await marketplace.getOffer(0); + + await BlockToken.connect(owner_).mint(50,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(50); + await BlockToken.connect(addr2).approve(marketplace,50); + await marketplace.connect(addr2).offer(listId,20); + await marketplace.connect(addr2).cancelOffer(offerId.offerrer); + + const deleteOffer = await marketplace.connect(addr2).getOffer(0); + expect(deleteOffer.offerrer).to.equal(ethers.ZeroAddress); + }) + + }) + + describe("cancel listing", async ()=>{ + + it("should cancel listing properly", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + const cancelListing = await marketplace.getListing(listId); + await marketplace.connect(addr1).cancelListing(listId); + }) + + }) + + it("should revert if not owner", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + await expect(marketplace.connect(addr2).cancelListing(listId)).to.be.revertedWith("Unauthorized user"); + }) + + it("should revert if already sold", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + await BlockToken.connect(owner_).mint(1000,addr2); + expect(await BlockToken.balanceOf(addr2)).to.eq(1000); + await BlockToken.connect(addr2).approve(marketplace,100); + await marketplace.connect(addr2).buyNft(listId); + expect(await blocknft.ownerOf(tokenId)).to.eq(addr2); + const listing = await marketplace.getListing(listId); + expect(listing.sold).to.equal(true); + await expect(marketplace.connect(addr1).cancelListing(listId)).to.be.revertedWith("Already sold"); + }) + + it("should return NFT back to owner", async ()=>{ + const {marketplace,blocknft,BlockToken, owner_, addr1, addr2, addr3} = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft + .connect(addr1) + .setApprovalForAll(marketplace.getAddress(), true); + let tx1 = marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }) + const cancelListing = await marketplace.getListing(listId); + await marketplace.connect(addr1).cancelListing(cancelListing.tokenId); + expect(await blocknft.ownerOf(tokenId)).to.eq(addr1.address); + + }) + + it("should delete listing from storage and return NFT to owner", async () => { + // Purpose: Verify that cancelListing deletes the listing from idToListing and transfers NFT back to owner + const { marketplace, blocknft, BlockToken, owner_, addr1 } = await loadFixture(deployBlockMarketPlace); + let tokenId = 1; + let listId = 0; + await blocknft.connect(addr1).mint(addr1); + let token = await ethers.getContractAt("IERC20", BlockToken); + await blocknft.connect(addr1).setApprovalForAll(marketplace.getAddress(), true); + await marketplace.connect(addr1).listNft({ + owner: addr1, + tokenId: tokenId, + paymentToken: token, + NftToken: blocknft.getAddress(), + isNative: false, + price: 50, + sold: false, + minOffer: 10, + }); + // Verify listing exists before cancellation + const listingBefore = await marketplace.getListing(listId); + expect(listingBefore.owner).to.eq(addr1.address); + expect(listingBefore.tokenId).to.eq(tokenId); + // Cancel listing + await marketplace.connect(addr1).cancelListing(listId); + // Verify listing is deleted (all fields should be default/zero) + const listingAfter = await marketplace.getListing(listId); + expect(listingAfter.owner).to.eq(ethers.ZeroAddress); + expect(listingAfter.tokenId).to.eq(0); + expect(listingAfter.price).to.eq(0); + expect(listingAfter.minOffer).to.eq(0); + expect(listingAfter.sold).to.eq(false); + expect(listingAfter.isNative).to.eq(false); + expect(listingAfter.paymentToken).to.eq(ethers.ZeroAddress); + expect(listingAfter.NftToken).to.eq(ethers.ZeroAddress); + // Verify NFT is transferred back to owner + expect(await blocknft.ownerOf(tokenId)).to.eq(addr1.address); + }); + }); + +}); +}); diff --git a/test/BlockToken.js b/test/BlockToken.js new file mode 100644 index 0000000..48a1708 --- /dev/null +++ b/test/BlockToken.js @@ -0,0 +1,82 @@ +// const { +// loadFixture, +// } = require("@nomicfoundation/hardhat-toolbox/network-helpers"); +// // const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); +// const { expect } = require("chai"); + +// // util functon +// const deployBlockToken = async () => { +// // target the BlockToken contract within our contract folder +// let name_ = "BlockToken"; +// let symbol_ = "BCT"; +// const [owner_, addr1, addr2] = await ethers.getSigners(); +// const BlockTokenContract = await ethers.getContractFactory("BlockToken"); // target BlockToken.sol +// const BlockToken = await BlockTokenContract.deploy( +// name_, +// symbol_, +// owner_.address +// ); // deploy the BlockToken contract +// return { BlockToken, owner_, addr1, addr2, name_, symbol_ }; // return the deployed instance of our BlockToken contract +// }; + +// // BlockToken Test Suite +// describe("BlockToken Test Suite", () => { +// describe("Deployment", () => { +// it("Should return set values upon deployment", async () => { +// const { BlockToken, name_, symbol_, owner_ } = await loadFixture( +// deployBlockToken +// ); +// expect(await BlockToken.name()).to.eq(name_); +// expect(await BlockToken.symbol()).to.eq(symbol_); +// expect(await BlockToken.owner()).to.eq(owner_); +// }); + +// it("Should revert if owner is zero address", async () => { +// const BlockTokenContract = await ethers.getContractFactory("BlockToken"); +// let ZeroAddress = "0x0000000000000000000000000000000000000000"; +// await expect( +// BlockTokenContract.deploy("hh", "tt", ZeroAddress) +// ).to.be.revertedWith("BlockToken:: Zero address not supported"); +// }); +// }); + +// describe("Minting", () => { +// it("Should allow onlyOwner Mint", async () => { +// const { BlockToken, owner_, addr1 } = await loadFixture(deployBlockToken); +// // test owner mints successfully +// await BlockToken.connect(owner_).mint(1000, addr1); +// expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + +// // test that another user cant call successfully +// let malicioustxn = BlockToken.connect(addr1).mint(1000, addr1); +// await expect(malicioustxn).to.be.revertedWith( +// "BlockToken:: Unauthorized User" +// ); +// }); + +// it("Should revert if minting amount is zero", async () => { +// const { BlockToken, owner_, addr1 } = await loadFixture(deployBlockToken); +// 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(deployBlockToken); +// await expect( +// BlockToken.connect(addr1).burn(1000) +// ).to.be.revertedWithCustomError(BlockToken, "ERC20InsufficientBalance"); +// }); + +// it("Should Burn Tokens Successfully", async () => { +// const { BlockToken, owner_, addr1 } = await loadFixture(deployBlockToken); +// await BlockToken.connect(owner_).mint(1000, owner_); +// expect(await BlockToken.balanceOf(owner_)).to.eq(1000); + +// await BlockToken.connect(owner_).burn(100); +// expect(await BlockToken.balanceOf(owner_)).to.eq(900); +// }); +// }); +// }); 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 diff --git a/test/ERC20.js b/test/ERC20.js new file mode 100644 index 0000000..0a29e65 --- /dev/null +++ b/test/ERC20.js @@ -0,0 +1,226 @@ +const { + loadFixture, + } = require("@nomicfoundation/hardhat-toolbox/network-helpers"); + // const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); + const { expect } = require("chai"); + + // util functon + const deployBlockToken = async () => { + // target the BlockToken contract within our contract folder + let name_ = "BlockToken"; + let symbol_ = "BCT"; + const [owner_, addr1, addr2] = await ethers.getSigners(); + const BlockTokenContract = await ethers.getContractFactory("BlockToken"); // target BlockToken.sol + const BlockToken = await BlockTokenContract.deploy( + name_, + symbol_, + owner_.address + ); // deploy the BlockToken contract + return { BlockToken, owner_, addr1, addr2, name_, symbol_ }; // return the deployed instance of our BlockToken contract + }; + + // BlockToken Test Suite + describe("BlockToken Test Suite", () => { + describe("Deployment", () => { + it("Should return set values upon deployment", async () => { + const { BlockToken, name_, symbol_, owner_ } = await loadFixture( + deployBlockToken + ); + expect(await BlockToken.name()).to.eq(name_); + expect(await BlockToken.symbol()).to.eq(symbol_); + expect(await BlockToken.owner()).to.eq(owner_); + }); + + it("Should revert if owner is zero address", async () => { + const BlockTokenContract = await ethers.getContractFactory("BlockToken"); + let ZeroAddress = "0x0000000000000000000000000000000000000000"; + await expect( + BlockTokenContract.deploy("hh", "tt", ZeroAddress) + ).to.be.revertedWith("BlockToken:: Zero address not supported"); + }); + }); + + describe("Minting", () => { + it("Should allow onlyOwner Mint", async () => { + const { BlockToken, owner_, addr1 } = await loadFixture(deployBlockToken); + // test owner mints successfully + await BlockToken.connect(owner_).mint(1000, addr1); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + // test that another user cant call successfully + let malicioustxn = BlockToken.connect(addr1).mint(1000, addr1); + await expect(malicioustxn).to.be.revertedWith( + "BlockToken:: Unauthorized User" + ); + }); + + it("Should revert if minting amount is zero", async () => { + const { BlockToken, owner_, addr1 } = await loadFixture(deployBlockToken); + 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, addr1 } = await loadFixture(deployBlockToken); + await expect( + BlockToken.connect(addr1).burn(1000) + ).to.be.revertedWithCustomError(BlockToken, "ERC20InsufficientBalance"); + }); + + it("Should Burn Tokens Successfully", async () => { + const { BlockToken, owner_, addr1 } = await loadFixture(deployBlockToken); + await BlockToken.connect(owner_).mint(1000, owner_); + expect(await BlockToken.balanceOf(owner_)).to.eq(1000); + + await BlockToken.connect(owner_).burn(100); + expect(await BlockToken.balanceOf(owner_)).to.eq(900); + }); + }); + + describe("burnFrom", ()=> { + it("should revert if non-owner call burn from", async ()=>{ + const{BlockToken, owner_, addr1,addr2} = await loadFixture(deployBlockToken); + + await BlockToken.connect(owner_).mint(1000,addr1.address); + expect(await BlockToken.balanceOf(addr1)).to.eq(1000); + + await BlockToken.connect(addr1).burn(100); + expect(await BlockToken.balanceOf(addr1)).to.eq(900); + + let maliciousUser = BlockToken.connect(addr2).burnFrom(addr1,900); + await expect(maliciousUser).to.be.revertedWith("BlockToken:: Unauthorized User"); + }) + + it("should revert on zero amount", async ()=>{ + const{BlockToken, owner_,addr1, addr2} = await loadFixture(deployBlockToken); + await BlockToken.connect(owner_).mint(1000,addr1.address); + + await BlockToken.connect(addr1).burn(100); + expect(await BlockToken.balanceOf(addr1)).to.eq(900); + + let setZeroValue = BlockToken.connect(owner_).burnFrom(addr1,0); + await expect(setZeroValue).to.be.revertedWith("BlockToken:: Zero amount not supported"); + }) + it("should burn from user account", async()=>{ + const{BlockToken, owner_,addr1, addr2} = await loadFixture(deployBlockToken); + await BlockToken.connect(owner_).mint(1000,addr1.address); + + let burnFrom = await BlockToken.connect(owner_).burnFrom(addr1,1000); + expect(await BlockToken.balanceOf(addr1)).to.eq(0); + }) + }) + + describe("transfer", () =>{ + it("should revert on transfer to Zero address", async()=>{ + const {BlockToken , owner_, addr1,addr2} = await loadFixture(deployBlockToken); + let zeroAddress = "0x0000000000000000000000000000000000000000"; + //transfer oto zero address + await expect(BlockToken.connect(owner_).transfer(zeroAddress,800)).to.be.reverted; + }) + + it("should transfer tokens from one user to another", async()=>{ + const{BlockToken, owner_, addr1, addr2} = await loadFixture(deployBlockToken); + await BlockToken.connect(owner_).mint(1000,owner_); + expect(await BlockToken.balanceOf(owner_)).to.eq(1000); + + //transfer to another account + + await BlockToken.connect(owner_).transfer(addr1,300); + expect(await BlockToken.balanceOf(owner_)).to.eq(700); + expect(await BlockToken.balanceOf(addr1)).to.eq(300); + + await BlockToken.connect(addr1).transfer(owner_,300); + expect(await BlockToken.balanceOf(addr1)).to.eq(0); + expect (await BlockToken.balanceOf(owner_)).to.eq(1000); + + }) + it("should revert if amount to transer is lower than balance", async() => { + const{BlockToken,owner_,addr1,addr2} = await loadFixture(deployBlockToken); + 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(addr1)).to.eq(1000); + expect(await BlockToken.balanceOf(owner_)).to.eq(0); + //transfer with lower balance + + 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); + + }) + }) + + describe("TransferFrom",()=>{ + it("should test, 'approve' updated to 'allowance'", async()=>{ + const{BlockToken,owner_,addr1,addr2} = await loadFixture(deployBlockToken); + 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 transferFrom owner to addr2 and the caller should be addr1", async()=>{ + const{BlockToken,owner_,addr1, addr2} = await loadFixture(deployBlockToken); + 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,500); + expect(await BlockToken.balanceOf(addr2)).to.eq(500) + + }) + + + it("should revert if balance 'from' has insufficient allowance", async ()=>{ + const{BlockToken,owner_,addr1,addr2} = await loadFixture(deployBlockToken); + 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 revert if 'to' is address zero", async () => { + const{BlockToken,owner_,addr1,addr2} = await loadFixture(deployBlockToken); + 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 incremented and not replaced", async () => { + const{BlockToken,owner_,addr1,addr2} = await loadFixture(deployBlockToken); + + 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/Lock.js b/test/Lock.js deleted file mode 100644 index 8856064..0000000 --- a/test/Lock.js +++ /dev/null @@ -1,126 +0,0 @@ -// const { -// time, -// loadFixture, -// } = require("@nomicfoundation/hardhat-toolbox/network-helpers"); -// const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); -// const { expect } = require("chai"); - -// describe("Lock", function () { -// // We define a fixture to reuse the same setup in every test. -// // We use loadFixture to run this setup once, snapshot that state, -// // and reset Hardhat Network to that snapshot in every test. -// async function deployOneYearLockFixture() { -// const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; -// const ONE_GWEI = 1_000_000_000; - -// const lockedAmount = ONE_GWEI; -// const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; - -// // Contracts are deployed using the first signer/account by default -// const [owner, otherAccount] = await ethers.getSigners(); - -// const Lock = await ethers.getContractFactory("Lock"); -// const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); - -// return { lock, unlockTime, lockedAmount, owner, otherAccount }; -// } - -// describe("Deployment", function () { -// it("Should set the right unlockTime", async function () { -// const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); - -// expect(await lock.unlockTime()).to.equal(unlockTime); -// }); - -// it("Should set the right owner", async function () { -// const { lock, owner } = await loadFixture(deployOneYearLockFixture); - -// expect(await lock.owner()).to.equal(owner.address); -// }); - -// it("Should receive and store the funds to lock", async function () { -// const { lock, lockedAmount } = await loadFixture( -// deployOneYearLockFixture -// ); - -// expect(await ethers.provider.getBalance(lock.target)).to.equal( -// lockedAmount -// ); -// }); - -// it("Should fail if the unlockTime is not in the future", async function () { -// // We don't use the fixture here because we want a different deployment -// const latestTime = await time.latest(); -// const Lock = await ethers.getContractFactory("Lock"); -// await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith( -// "Unlock time should be in the future" -// ); -// }); -// }); - -// describe("Withdrawals", function () { -// describe("Validations", function () { -// it("Should revert with the right error if called too soon", async function () { -// const { lock } = await loadFixture(deployOneYearLockFixture); - -// await expect(lock.withdraw()).to.be.revertedWith( -// "You can't withdraw yet" -// ); -// }); - -// it("Should revert with the right error if called from another account", async function () { -// const { lock, unlockTime, otherAccount } = await loadFixture( -// deployOneYearLockFixture -// ); - -// // We can increase the time in Hardhat Network -// await time.increaseTo(unlockTime); - -// // We use lock.connect() to send a transaction from another account -// await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith( -// "You aren't the owner" -// ); -// }); - -// it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { -// const { lock, unlockTime } = await loadFixture( -// deployOneYearLockFixture -// ); - -// // Transactions are sent using the first signer by default -// await time.increaseTo(unlockTime); - -// await expect(lock.withdraw()).not.to.be.reverted; -// }); -// }); - -// describe("Events", function () { -// it("Should emit an event on withdrawals", async function () { -// const { lock, unlockTime, lockedAmount } = await loadFixture( -// deployOneYearLockFixture -// ); - -// await time.increaseTo(unlockTime); - -// await expect(lock.withdraw()) -// .to.emit(lock, "Withdrawal") -// .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg -// }); -// }); - -// describe("Transfers", function () { -// it("Should transfer the funds to the owner", async function () { -// const { lock, unlockTime, lockedAmount, owner } = await loadFixture( -// deployOneYearLockFixture -// ); - -// await time.increaseTo(unlockTime); - -// await expect(lock.withdraw()).to.changeEtherBalances( -// [owner, lock], -// [lockedAmount, -lockedAmount] -// ); -// }); -// }); -// }); -// });