Skip to content

Commit

Permalink
add smart contract testing
Browse files Browse the repository at this point in the history
  • Loading branch information
chris017 committed Mar 14, 2023
1 parent 570b886 commit f18c40f
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types

# Hardhat files
cache
artifacts

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Smart Contract Unit Testing 🧪

This repository contains a Solidity smart contract that represents a Faucet. The Faucet contract is designed to allow users to withdraw a small amount of ETH at a time. The contract can be tested using the following tests:

1. Deployment and Ownership: The first test checks whether the contract is deployed correctly and the owner is set to the correct address.
2. Withdrawal Limit: The second test ensures that the contract does not allow withdrawals above 0.1 ETH at a time. This test checks the functionality of the contract's withdrawal feature.
3. Faucet Destruction: The third test checks the faucet's ability to destroy the Faucet contract when the destroyFaucet function is called by the owner.
4. Owner Withdrawal: The fourth and final test checks whether the contract only allows the owner to withdraw all funds. This test checks the security of the contract and ensures that only the owner can access the funds.

## Getting Started 🚀

To run the tests for the Faucet contract, you will need to set up a local environment with the following:

1. Solidity Compiler: Solidity is a contract-oriented programming language for writing smart contracts. You will need a Solidity compiler to compile the smart contract.
2. Chai: Chai is a JavaScript assertion library that allows you to write expressive and readable tests.

Once you have installed these dependencies, you can run the tests using the following steps:

1. Clone the repository.
2. Change into the project directory: cd...
3. Install the dependencies: npm install.
4. Finally, run the tests: npx hardhat test.

## Conclusion 📚

The tests outlined above cover the essential functionalities of the Faucet smart contract, including deployment, withdrawal limits, destruction, and owner withdrawal. By running these tests, you can ensure that the contract behaves as expected and that there are no critical security vulnerabilities. Using Chai, you can write expressive and readable tests to verify the contract's functionality.

There are many more cases that you can test for to create really iron-clad and comprehensive unit tests - and thus create iron-clad smart contracts! 💪 The testing rabbit hole is particularly great for anyone looking to get a solid foundation in smart contract security, lots of testing there for sure! Good luck, smart contract tester! 🫡
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract Faucet {
address payable public owner;

constructor() payable {
owner = payable(msg.sender);
}

function withdraw(uint _amount) public payable {
// users can only withdraw .1 ETH at a time, feel free to change this!
require(_amount <= 100000000000000000);
(bool sent, ) = payable(msg.sender).call{value: _amount}("");
require(sent, "Failed to send Ether");
}

function withdrawAll() public onlyOwner {
(bool sent, ) = owner.call{value: address(this).balance}("");
require(sent, "Failed to send Ether");
}

function destroyFaucet() public onlyOwner {
selfdestruct(owner);
}

modifier onlyOwner() {
require(msg.sender == owner);
_;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require("@nomicfoundation/hardhat-toolbox");

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.19",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "unit-test-a-smart-contract",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^2.0.2",
"hardhat": "^2.13.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const hre = require("hardhat");

async function main() {
const currentTimestampInSeconds = Math.round(Date.now() / 1000);
const unlockTime = currentTimestampInSeconds + 60;

const lockedAmount = hre.ethers.utils.parseEther("0.001");

const Lock = await hre.ethers.getContractFactory("Lock");
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });

await lock.deployed();

console.log(
`Lock with ${ethers.utils.formatEther(
lockedAmount
)}ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}`
);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { expect } = require('chai');

describe('Faucet', 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 deployContractAndSetVariables() {
const Faucet = await ethers.getContractFactory('Faucet');
const faucet = await Faucet.deploy();

const [owner, alice] = await ethers.getSigners();

let withdrawAmount = ethers.utils.parseUnits('1', 'ether');

return { faucet, owner, alice, withdrawAmount };
}

it('should deploy and set the owner correctly', async function () {
const { faucet, owner } = await loadFixture(deployContractAndSetVariables);

expect(await faucet.owner()).to.equal(owner.address);
});

it('should not allow withdrawals above .1 ETH at a time', async function () {
const { faucet, withdrawAmount } = await loadFixture(
deployContractAndSetVariables
);
await expect(faucet.withdraw(withdrawAmount)).to.be.reverted;
});

it('should destroy the faucet when the destroyFaucet function is called by the owner', async function () {
const { faucet, owner } = await loadFixture(deployContractAndSetVariables);
await faucet.destroyFaucet();
expect(await ethers.provider.getCode(faucet.address)).to.equal('0x');
});

it('should only allow the owner to withdraw all funds', async function () {
const { faucet, owner, alice } = await loadFixture(deployContractAndSetVariables);
await expect(faucet.connect(alice).withdrawAll()).to.be.reverted;
await expect(faucet.connect(owner).withdrawAll()).to.be.ok;
});
});

0 comments on commit f18c40f

Please sign in to comment.