diff --git a/README.md b/README.md index 1933d03..4a9515a 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,4 @@ This repository contains a collection of Solidity examples that students can use to learn about smart contracts and blockchain development. -## Examples -This repository contains the following Solidity examples: - -- [Storage.sol](./001_Storage.sol) - A simple smart contract that demonstrates various Solidity data types and modifiers. - -- [Mapping.sol](./002_Mapping.sol) - A simple smart contract that demonstrates the use of the mapping data structure. \ No newline at end of file diff --git a/contracts/CohortBank.sol b/contracts/CohortBank.sol new file mode 100644 index 0000000..dbfe900 --- /dev/null +++ b/contracts/CohortBank.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.9; + +// Uncomment this line to use console.log +// import "hardhat/console.sol"; + +contract CohortBank { + // The keyword "public" makes variables + // accessible from other contracts + uint256 public unlockTime; + mapping(address => uint256) public balances; + uint256 public totalCohortBalance; + + address payable public owner; + // Events allow clients to react to specific + // contract changes you declare + event Deposit(uint256 amount, uint256 when, address caller); + event Withdrawal(uint256 amount, uint256 when); + + // Constructor code is only run when the contract + // is created + constructor(uint256 _unlockTime) payable { + require( + block.timestamp < _unlockTime, + "Unlock time should be in the future" + ); + + unlockTime = _unlockTime; + owner = payable(msg.sender); + } + + + // spot the error here + function deposit(uint256 amount) public payable { + // 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(amount > 0, "cannot deposit 0 amount"); + balances[msg.sender] += amount; + totalCohortBalance += amount; + + emit Deposit(amount, block.timestamp, msg.sender); + } + + // buggy withdraw function + // spot this bug + // add your findings to findings.md + 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"); + balances[msg.sender] = 0; + + emit Withdrawal(address(this).balance, block.timestamp); + owner.transfer(address(this).balance); + } +} \ No newline at end of file diff --git a/findings.md b/findings.md new file mode 100644 index 0000000..35155aa --- /dev/null +++ b/findings.md @@ -0,0 +1,4 @@ +# My Findings + +- I added a uint amount to the deposit function +- I changed the withdraw logic to set thee balancee of msg.sender to equal 0. \ No newline at end of file diff --git a/test/CohortBank.js b/test/CohortBank.js new file mode 100644 index 0000000..1f625aa --- /dev/null +++ b/test/CohortBank.js @@ -0,0 +1,144 @@ +const { + time, + loadFixture, + } = require("@nomicfoundation/hardhat-network-helpers"); + const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); + const { expect } = require("chai"); + const {ethers} = require("hardhat"); + const { parseEther } = ethers.utils + + const TEST_ETH = parseEther("2"); + + describe("CohortBank Test Suite", function(){ + async function runEveryTime(){ + const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; + const ONE_GWEI = 1000000000; + + const lockedAmount = ONE_GWEI; + const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; + + const [owner, addr1] = await ethers.getSigners(); + + const CohortBank = await ethers.getContractFactory("CohortBank"); + const cohortBank = await CohortBank.deploy(unlockTime, {value: lockedAmount}); + + return {cohortBank, unlockTime, lockedAmount, owner, addr1}; + + } + // Deployment + describe("Deployment", function(){ + it("Should check unlock time", async function(){ + const {cohortBank, unlockTime} = await loadFixture(runEveryTime); + + expect(await cohortBank.unlockTime()).to.eq(unlockTime) + }); + + it("Should set the right owner", async function(){ + const {cohortBank, owner} = await loadFixture(runEveryTime); + + expect(await cohortBank.owner()).to.eq(owner.address); + }); + + it("should correctly retrieve deployed ETH to CohortBank", async function(){ + const { cohortBank, lockedAmount} = await loadFixture(runEveryTime); + + expect(await ethers.provider.getBalance(cohortBank.address)).to.eq(lockedAmount) + + }); + + it("should fail if unlock time is not in the future", async function(){ + const latestTime = await time.latest(); + + const CohortBank = await ethers.getContractFactory("CohortBank"); + + await expect(CohortBank.deploy(latestTime, {value: 1})).to.be.revertedWith("Unlock time should be in the future") + }); + }); + + // Deposit function + describe("Deposit", async()=>{ + it("should deposit an amount that is greater than 0", async () =>{ + const {cohortBank, owner} = await loadFixture(runEveryTime); + + const ownerBalanceBeforeDeposit = await cohortBank.balances(owner.address); + expect (ownerBalanceBeforeDeposit).to.eq(0); + expect(cohortBank.connect(owner).deposit(0)).to.be.revertedWith("cannot deposit 0 amount") + }); + }); + + + // Deposit Event + describe("Deposit events", async ()=>{ + it("should emit event on deposit", async()=>{ + + const{cohortBank, owner} = await loadFixture(runEveryTime) + await cohortBank.connect(owner).deposit(8); + + const ownerBalanceAfterDeposit = await cohortBank.balances(owner.address); + expect(ownerBalanceAfterDeposit).to.eq(8); + + const ownerDeposit = await cohortBank.connect(owner).deposit(8); + await expect(ownerDeposit).to.emit(cohortBank, "Deposit").withArgs(8, anyValue, owner.address); + }); + }); + + // Withdrawal Function + describe("Withdrawal", function(){ + describe("Validations", function(){ + it("should not be able to withdraw if called too soon", async function(){ + const {cohortBank} = await loadFixture(runEveryTime); + + await expect(cohortBank.withdraw()).to.be.revertedWith("You can't withdraw yet"); + }); + + it("should revert the message with right owner", async function(){ + const {cohortBank, unlockTime, addr1} = await loadFixture( + runEveryTime + ); + + await time.increaseTo(unlockTime); + await expect(cohortBank.connect(addr1).withdraw()).to.be.revertedWith("You aren't the owner"); + }); + + it("should not fail if unlock time has arrived and is called by the owner", async function(){ + const {cohortBank, unlockTime} = await loadFixture(runEveryTime); + + await time.increaseTo(unlockTime); + await expect(cohortBank.withdraw()).not.to.be.reverted; + }); + }); + }); + + // Witdrawal Event + describe("Withdrawal events", function(){ + it("should emit the event on withdrawals", async function (){ + const { cohortBank, unlockTime, lockedAmount} = await loadFixture( + runEveryTime + ); + await time.increaseTo(unlockTime); + await expect(cohortBank.withdraw()).to.emit(cohortBank, "Withdrawal").withArgs(lockedAmount,anyValue); + }); + + }); + + // Transfer + describe("Transfer", function () { + it("Should transfer the funds to the owner", async () => { + const { cohortBank, unlockTime, owner } = await loadFixture(runEveryTime); + + await cohortBank.deposit(3, {value: TEST_ETH}); + + const ownerBalanceBeforeWithdrawal = await cohortBank.balances(owner.address); + await time.increaseTo(unlockTime); + + + //withdraw balance + await cohortBank.withdraw(); + const ownerBalanceAfterWithdrawal = await cohortBank.balances(owner.address); + expect(ownerBalanceAfterWithdrawal).to.be.lt( + ownerBalanceBeforeWithdrawal + ); + }); + }); + runEveryTime() +}); diff --git a/test/Lock.js b/test/Lock.js index 6a161e6..49e46f1 100644 --- a/test/Lock.js +++ b/test/Lock.js @@ -1,126 +1,126 @@ -const { - time, - loadFixture, -} = require("@nomicfoundation/hardhat-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.address)).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] - ); - }); - }); - }); -}); +// const { +// time, +// loadFixture, +// } = require("@nomicfoundation/hardhat-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.address)).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] +// ); +// }); +// }); +// }); +// });