├── .gitignore ├── README.md ├── argument.ts ├── contracts ├── Lock.sol └── tokenVotingContract.sol ├── hardhat.config.ts ├── package-lock.json ├── package.json ├── scripts ├── deploy.ts └── votingContract.ts ├── test └── Lock.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | # Hardhat files 9 | cache 10 | artifacts 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sample Hardhat Project 2 | 3 | This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract. 4 | 5 | Try running some of the following tasks: 6 | 7 | ```shell 8 | npx hardhat help 9 | npx hardhat test 10 | REPORT_GAS=true npx hardhat test 11 | npx hardhat node 12 | npx hardhat run scripts/deploy.ts 13 | ``` 14 | -------------------------------------------------------------------------------- /argument.ts: -------------------------------------------------------------------------------- 1 | module.exports = ["FITEX", "FTT", 100000000000]; -------------------------------------------------------------------------------- /contracts/Lock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.9; 3 | 4 | // Uncomment this line to use console.log 5 | // import "hardhat/console.sol"; 6 | 7 | contract Lock { 8 | uint public unlockTime; 9 | address payable public owner; 10 | 11 | event Withdrawal(uint amount, uint when); 12 | 13 | constructor(uint _unlockTime) payable { 14 | require( 15 | block.timestamp < _unlockTime, 16 | "Unlock time should be in the future" 17 | ); 18 | 19 | unlockTime = _unlockTime; 20 | owner = payable(msg.sender); 21 | } 22 | 23 | function withdraw() public { 24 | // Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal 25 | // console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp); 26 | 27 | require(block.timestamp >= unlockTime, "You can't withdraw yet"); 28 | require(msg.sender == owner, "You aren't the owner"); 29 | 30 | emit Withdrawal(address(this).balance, block.timestamp); 31 | 32 | owner.transfer(address(this).balance); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/tokenVotingContract.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.17; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract tokenizedVotingDao is ERC20 { 8 | 9 | uint256 tokenCost = 1000 gwei; 10 | address public owner; 11 | address[] contestantsPool; 12 | uint256[] public votingPools; 13 | uint256[] results; 14 | uint256 public votingCost = 50; 15 | uint256 public contestCreationCost = 200; 16 | uint256 voteId = 10010; 17 | mapping(address => Result) voteCount; 18 | mapping(uint256 => address[]) contendersList; 19 | mapping(uint256 => mapping(address => uint256)) pointCount; 20 | mapping(uint256 => bool) voteStatus; 21 | mapping(uint256 => address) votingAdmin; 22 | mapping(address => mapping(uint256 => bool)) voteCheck; 23 | 24 | struct Result { 25 | address _address; 26 | uint256 _totalPoints; 27 | } 28 | 29 | event LogBoughtTokens( 30 | address _address, 31 | uint256 tokensPurchased, 32 | string message 33 | ); 34 | event LogWithdrawal(address _address, uint256 amount, string message); 35 | event Contest(uint256 voteId, string message); 36 | event Success(address _address, string message); 37 | 38 | constructor( 39 | string memory _name, 40 | string memory _symbol, 41 | uint256 _tokenQuantity 42 | ) ERC20(_name, _symbol) { 43 | _mint(address(this), _tokenQuantity * (10**decimals())); 44 | } 45 | 46 | function buyTokens() public payable { 47 | uint256 tokensPurchased = msg.value / tokenCost; 48 | _transfer(address(this), msg.sender, tokensPurchased); 49 | emit LogBoughtTokens( 50 | msg.sender, 51 | tokensPurchased, 52 | "Your purchase is successful" 53 | ); 54 | } 55 | 56 | modifier onlyOwner(address _address) { 57 | require(owner == _address, "Not owner"); 58 | _; 59 | } 60 | 61 | function withdrawFunds(uint256 amount) 62 | public 63 | payable 64 | onlyOwner(msg.sender) 65 | { 66 | require(msg.sender != address(0), "Can't send to address(0)"); 67 | payable(msg.sender).transfer(amount * (10**decimals())); 68 | emit LogWithdrawal( 69 | msg.sender, 70 | amount, 71 | "You have successfully withdrawn" 72 | ); 73 | } 74 | 75 | function contestCreation( 76 | address _contender1, 77 | address _contender2, 78 | address _contender3 79 | ) 80 | public 81 | noRepeat(_contender1, _contender2, _contender3) 82 | returns (uint256 yourVoteId) 83 | { 84 | require( 85 | balanceOf(msg.sender) >= contestCreationCost, 86 | "INSUFFICIENT FUND, GET MORE TOKEN" 87 | ); 88 | transfer(address(this), contestCreationCost); 89 | votingPools.push(voteId); 90 | voteStatus[voteId] = true; 91 | contestantsPool.push(_contender1); 92 | contestantsPool.push(_contender2); 93 | contestantsPool.push(_contender3); 94 | contendersList[voteId] = contestantsPool; 95 | contestantsPool.pop(); 96 | contestantsPool.pop(); 97 | contestantsPool.pop(); 98 | votingAdmin[voteId] = msg.sender; 99 | yourVoteId = voteId; 100 | emit Contest(voteId, "Vote contest created successfully"); 101 | voteId++; 102 | } 103 | 104 | function displayContenders(uint256 _voteId) 105 | public 106 | view 107 | returns (address[] memory contenders) 108 | { 109 | contenders = contendersList[_voteId]; 110 | } 111 | 112 | function displayOwner(uint256 _voteId) 113 | public 114 | view 115 | returns (address _owner) 116 | { 117 | _owner = votingAdmin[_voteId]; 118 | } 119 | 120 | function displayTotalVotes(uint256 _voteId) 121 | public 122 | view 123 | returns (uint256 TotalPoints) 124 | { 125 | address[] memory _contendersList = contendersList[_voteId]; 126 | address contender1 = _contendersList[0]; 127 | address contender2 = _contendersList[1]; 128 | address contender3 = _contendersList[2]; 129 | TotalPoints = 130 | pointCount[_voteId][contender1] + 131 | pointCount[_voteId][contender2] + 132 | pointCount[_voteId][contender3]; 133 | } 134 | 135 | function vote( 136 | uint256 _voteId, 137 | address _contender1, 138 | address _contender2, 139 | address _contender3 140 | ) public noRepeat(_contender1, _contender2, _contender3) { 141 | //norepeat 142 | require( 143 | balanceOf(msg.sender) >= votingCost, 144 | "INSUFFICIENT FUND, GET MORE TOKEN" 145 | ); 146 | transfer(address(this), votingCost); 147 | 148 | require(voteStatus[_voteId], "This contest is over, voting closed"); 149 | bool status = voteCheck[msg.sender][_voteId]; 150 | require(!status, "You Have Already Voted"); 151 | pointAssigner(_voteId, _contender1, _contender2, _contender3); 152 | uint256 voteIdN = _voteId; 153 | voteCheck[msg.sender][voteIdN] = true; 154 | emit Success(msg.sender, "Voted Successfully"); 155 | } 156 | 157 | function pointAssigner( 158 | uint256 _voteId, 159 | address _contender1, 160 | address _contender2, 161 | address _contender3 162 | ) private { 163 | pointCount[_voteId][_contender1] += 3; 164 | pointCount[_voteId][_contender2] += 2; 165 | pointCount[_voteId][_contender3] += 1; 166 | } 167 | 168 | function closeVotingPool(uint256 _voteId) public { 169 | voteStatus[_voteId] = false; 170 | emit Contest(_voteId, "Contest Closed"); 171 | } 172 | 173 | function displayWinner(uint256 _voteId) 174 | public 175 | view 176 | returns (address winner) 177 | { 178 | bool status = voteStatus[_voteId]; 179 | require(!status, "VOTING NOT OVER YET"); 180 | address[] memory _contendersList = contendersList[_voteId]; 181 | uint256 contender1 = pointCount[_voteId][_contendersList[0]]; 182 | uint256 contender2 = pointCount[_voteId][_contendersList[1]]; 183 | uint256 contender3 = pointCount[_voteId][_contendersList[2]]; 184 | 185 | if (contender1 > contender2 && contender1 > contender3) { 186 | winner = _contendersList[0]; 187 | } else if (contender2 > contender3) { 188 | winner = _contendersList[1]; 189 | } else { 190 | winner = _contendersList[2]; 191 | } 192 | } 193 | 194 | receive() external payable {} 195 | 196 | fallback() external payable {} 197 | 198 | modifier noRepeat( 199 | address _contenders1, 200 | address _contenders2, 201 | address _contenders3 202 | ) { 203 | require(_contenders1 != _contenders2, "ADDRESS 1 AND 2 ARE THE SAME"); 204 | require(_contenders1 != _contenders3, "ADDRESS 1 AND 3 ARE THE SAME"); 205 | require(_contenders2 != _contenders3, "ADDRESS 2 AND 3 ARE THE SAME"); 206 | _; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | import "@nomicfoundation/hardhat-toolbox"; 3 | import dotenv from "dotenv"; 4 | dotenv.config(); 5 | 6 | const config: HardhatUserConfig = { 7 | solidity: "0.8.17", 8 | 9 | defaultNetwork: "goerli", 10 | networks: { 11 | hardhat: { 12 | }, 13 | goerli: { 14 | url: process.env.GOERLI_RPC, 15 | //@ts-ignore 16 | accounts: [process.env.PRIVATE_KEY1, process.env.PRIVATE_KEY2], 17 | } 18 | }, 19 | 20 | etherscan: { 21 | apiKey: process.env.API_KEY, 22 | }, 23 | }; 24 | 25 | export default config; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tokenvotingcontract", 3 | "version": "1.0.0", 4 | "description": "A token based voting contract", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Faith M. Roberts", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@openzeppelin/contracts": "^4.8.1", 13 | "dotenv": "^16.0.3", 14 | "hardhat": "^2.12.7" 15 | }, 16 | "devDependencies": { 17 | "@ethersproject/abi": "^5.7.0", 18 | "@ethersproject/providers": "^5.7.2", 19 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", 20 | "@nomicfoundation/hardhat-network-helpers": "^1.0.8", 21 | "@nomicfoundation/hardhat-toolbox": "^2.0.1", 22 | "@nomiclabs/hardhat-ethers": "^2.2.2", 23 | "@nomiclabs/hardhat-etherscan": "^3.1.6", 24 | "@typechain/ethers-v5": "^10.2.0", 25 | "@typechain/hardhat": "^6.1.5", 26 | "@types/chai": "^4.3.4", 27 | "@types/mocha": "^10.0.1", 28 | "@types/node": "^18.13.0", 29 | "chai": "^4.3.7", 30 | "ethers": "^5.7.2", 31 | "hardhat-gas-reporter": "^1.0.9", 32 | "solidity-coverage": "^0.8.2", 33 | "ts-node": "^10.9.1", 34 | "typechain": "^8.1.1", 35 | "typescript": "^4.9.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | async function main() { 4 | const currentTimestampInSeconds = Math.round(Date.now() / 1000); 5 | const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; 6 | const unlockTime = currentTimestampInSeconds + ONE_YEAR_IN_SECS; 7 | 8 | const lockedAmount = ethers.utils.parseEther("1"); 9 | 10 | const Lock = await ethers.getContractFactory("Lock"); 11 | const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); 12 | 13 | await lock.deployed(); 14 | 15 | console.log(`Lock with 1 ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}`); 16 | } 17 | 18 | // We recommend this pattern to be able to use async/await everywhere 19 | // and properly handle errors. 20 | main().catch((error) => { 21 | console.error(error); 22 | process.exitCode = 1; 23 | }); 24 | -------------------------------------------------------------------------------- /scripts/votingContract.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | async function main() { 4 | const [owner, owner2] = await ethers.getSigners(); 5 | const VoteToken = await ethers.getContractFactory("tokenizedVotingDao"); 6 | const voteToken = await VoteToken.deploy("FITEX", "FTT", 100000000000); 7 | await voteToken.deployed(); 8 | 9 | console.log(`Your voteToken Address is ${voteToken.address}`); 10 | //////////////////////////////////////////////////////////// 11 | // DEPLOYMENT COMPLETED.. CONTRACT INTERACTION BEGINS 12 | //////////////////////////////////////////////////////////// 13 | const buyToken = await voteToken.buyTokens({value: ethers.utils.parseEther("0.01")}) 14 | console.log(buyToken); 15 | 16 | const createPool = await voteToken.contestCreation("0x31D97fdb6E31955Ad79899Eb0D28F2d0431684A0","0xa3d008b205d97892fBf937D1fA2Fc5568dB2A254", "0xC5cb2013586755CCeAE6229da853d6Ef96FB26AD"); 17 | let event = await createPool.wait(); 18 | console.log(event); 19 | 20 | let child = await event.events[1].args; 21 | let voteId = child[0]; 22 | console.log(`VOTE POOL ID IS ${voteId}`); 23 | 24 | const contenders = voteToken.displayContenders(voteId); 25 | const contenders2 = await contenders; 26 | console.log(await contenders2); 27 | 28 | const vote = await voteToken.vote(voteId, "0x31D97fdb6E31955Ad79899Eb0D28F2d0431684A0", "0xC5cb2013586755CCeAE6229da853d6Ef96FB26AD", "0xa3d008b205d97892fBf937D1fA2Fc5568dB2A254"); 29 | 30 | // close voting pool 31 | await voteToken.closeVotingPool(voteId); 32 | const closePool = voteToken.closeVotingPool(voteId); 33 | const win = await voteToken.displayWinner(voteId); 34 | const winner = await win; 35 | console.log(winner); 36 | } 37 | 38 | // We recommend this pattern to be able to use async/await everywhere 39 | // and properly handle errors. 40 | main().catch((error) => { 41 | console.error(error); 42 | process.exitCode = 1; 43 | }); -------------------------------------------------------------------------------- /test/Lock.ts: -------------------------------------------------------------------------------- 1 | import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; 2 | import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; 3 | import { expect } from "chai"; 4 | import { ethers } from "hardhat"; 5 | 6 | describe("Lock", function () { 7 | // We define a fixture to reuse the same setup in every test. 8 | // We use loadFixture to run this setup once, snapshot that state, 9 | // and reset Hardhat Network to that snapshot in every test. 10 | async function deployOneYearLockFixture() { 11 | const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; 12 | const ONE_GWEI = 1_000_000_000; 13 | 14 | const lockedAmount = ONE_GWEI; 15 | const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; 16 | 17 | // Contracts are deployed using the first signer/account by default 18 | const [owner, otherAccount] = await ethers.getSigners(); 19 | 20 | const Lock = await ethers.getContractFactory("Lock"); 21 | const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); 22 | 23 | return { lock, unlockTime, lockedAmount, owner, otherAccount }; 24 | } 25 | 26 | describe("Deployment", function () { 27 | it("Should set the right unlockTime", async function () { 28 | const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); 29 | 30 | expect(await lock.unlockTime()).to.equal(unlockTime); 31 | }); 32 | 33 | it("Should set the right owner", async function () { 34 | const { lock, owner } = await loadFixture(deployOneYearLockFixture); 35 | 36 | expect(await lock.owner()).to.equal(owner.address); 37 | }); 38 | 39 | it("Should receive and store the funds to lock", async function () { 40 | const { lock, lockedAmount } = await loadFixture( 41 | deployOneYearLockFixture 42 | ); 43 | 44 | expect(await ethers.provider.getBalance(lock.address)).to.equal( 45 | lockedAmount 46 | ); 47 | }); 48 | 49 | it("Should fail if the unlockTime is not in the future", async function () { 50 | // We don't use the fixture here because we want a different deployment 51 | const latestTime = await time.latest(); 52 | const Lock = await ethers.getContractFactory("Lock"); 53 | await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith( 54 | "Unlock time should be in the future" 55 | ); 56 | }); 57 | }); 58 | 59 | describe("Withdrawals", function () { 60 | describe("Validations", function () { 61 | it("Should revert with the right error if called too soon", async function () { 62 | const { lock } = await loadFixture(deployOneYearLockFixture); 63 | 64 | await expect(lock.withdraw()).to.be.revertedWith( 65 | "You can't withdraw yet" 66 | ); 67 | }); 68 | 69 | it("Should revert with the right error if called from another account", async function () { 70 | const { lock, unlockTime, otherAccount } = await loadFixture( 71 | deployOneYearLockFixture 72 | ); 73 | 74 | // We can increase the time in Hardhat Network 75 | await time.increaseTo(unlockTime); 76 | 77 | // We use lock.connect() to send a transaction from another account 78 | await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith( 79 | "You aren't the owner" 80 | ); 81 | }); 82 | 83 | it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { 84 | const { lock, unlockTime } = await loadFixture( 85 | deployOneYearLockFixture 86 | ); 87 | 88 | // Transactions are sent using the first signer by default 89 | await time.increaseTo(unlockTime); 90 | 91 | await expect(lock.withdraw()).not.to.be.reverted; 92 | }); 93 | }); 94 | 95 | describe("Events", function () { 96 | it("Should emit an event on withdrawals", async function () { 97 | const { lock, unlockTime, lockedAmount } = await loadFixture( 98 | deployOneYearLockFixture 99 | ); 100 | 101 | await time.increaseTo(unlockTime); 102 | 103 | await expect(lock.withdraw()) 104 | .to.emit(lock, "Withdrawal") 105 | .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg 106 | }); 107 | }); 108 | 109 | describe("Transfers", function () { 110 | it("Should transfer the funds to the owner", async function () { 111 | const { lock, unlockTime, lockedAmount, owner } = await loadFixture( 112 | deployOneYearLockFixture 113 | ); 114 | 115 | await time.increaseTo(unlockTime); 116 | 117 | await expect(lock.withdraw()).to.changeEtherBalances( 118 | [owner, lock], 119 | [lockedAmount, -lockedAmount] 120 | ); 121 | }); 122 | }); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true 9 | } 10 | } 11 | --------------------------------------------------------------------------------