Skip to content

Commit

Permalink
chore: merge PR #49 from BlockheaderWeb3-Community/refac-payFee
Browse files Browse the repository at this point in the history
Refac pay fee
  • Loading branch information
sprtd authored Sep 2, 2024
2 parents 8da88f3 + d213b58 commit 5040e4f
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 66 deletions.
18 changes: 18 additions & 0 deletions contracts/IStudentRegistry.so
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "./Student.sol";
interface IStudentRegistry {

function register( address _studentAddr, string memory _name, uint8 _age) external payable;

function addStudent( address _studentAddr ) external payable ;

function authorizeStudentRegistration (address _studentAddr) external;

function getStudent(uint8 _studentID) external view returns (Student memory);

function getStudentFromMapping(address _studentAddr) external view returns (Student memory);

function deleteStudent(address _studentAddr) external;

}
6 changes: 5 additions & 1 deletion contracts/Ownable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ contract Ownable {
}

// Function to get the owner of the contract

function getOwner() public view virtual returns(address) {
return owner;
}



// Function to change the owner using an address as a parmeter
function changeOwner(address payable _newOwner) public onlyOwner {
Expand Down
67 changes: 20 additions & 47 deletions contracts/StudentRegistryV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ contract StudentRegistryV2 is Ownable {
error NameIsEmpty();
error UnderAge(uint8 age, uint8 expectedAge);

uint constant public FEE = 1 ether;
//dynamic array of students
Student[] public students;


// Mappings for my Student Registery Contract
mapping(address => Student) public studentsMapping;
mapping(address => Student) public tempstudentsMapping;
mapping(address => bool) public hasPaidMapping;


// Events for my student registry Contract
event registerStudent(
Expand All @@ -24,55 +25,39 @@ contract StudentRegistryV2 is Ownable {
);
event authorizeStudentReg(address _studentAddress);
event addStud(address _studentAddr);
event PaidFee(address indexed payer, uint256 amount);

// Function For Paying
function payFee() public payable {
require(msg.sender != owner, "Owner is excluded");
require(msg.value == 1, "You must pay fee");
require(msg.value == FEE, "You must pay fee");
Student storage student = studentsMapping[msg.sender];
require(student.hasPaid == false, "You have paid already");
(bool success, ) = address(this).call{value: msg.value}("");
require(success, "failed to send ETH");
hasPaidMapping[msg.sender] = true;
student.studentAddr = msg.sender;
student.hasPaid = true;

emit PaidFee(msg.sender, FEE);
}

// Function for Registration
function register(
address _studentAddr,
string memory _name,
uint8 _age
) public payable {
require(
tempstudentsMapping[_studentAddr].studentAddr == address(0),
"You're already registered"
);
require(hasPaidMapping[msg.sender], "You must pay first");
require(bytes(_name).length > 0, "No name has been inputed");
require(_age >= 18, "name should be 18 or more");

uint256 _studentId = students.length + 1;
// A variable is defined to match the array format and datatype
Student memory tempstudent = Student({
studentAddr: _studentAddr,
name: _name,
age: _age,
studentId: _studentId,
hasPaid: true,
isAuthorized: false
});
students.push(tempstudent);

// add student to Studentmapping
tempstudentsMapping[_studentAddr] = tempstudent;
emit registerStudent(_studentAddr, _name, _age);
Student storage student = studentsMapping[msg.sender];
student.name = _name;
student.age = _age;
emit registerStudent(msg.sender, _name, _age);
}

// Function for authorizing registered Student
function authorizeStudentRegistration(
address _studentAddr
) public onlyOwner {
require(
tempstudentsMapping[_studentAddr].studentAddr != address(0),
"Invalid Address"
);
require(
studentsMapping[_studentAddr].studentAddr == address(0),
"You're already registered"
Expand All @@ -84,20 +69,7 @@ contract StudentRegistryV2 is Ownable {
// Function for Adding student, this function is called in the authorizeStudentRegistration() function
function addStudent(address _studentAddr) private {
uint256 _studentId = students.length + 1;
// A variable is defined to match the array format and datatype
Student memory student = Student({
studentAddr: tempstudentsMapping[_studentAddr].studentAddr,
name: tempstudentsMapping[_studentAddr].name,
age: tempstudentsMapping[_studentAddr].age,
studentId: _studentId,
hasPaid: true,
isAuthorized: false
});
students.push(student);

// add student to Studentmapping
studentsMapping[_studentAddr] = student;
emit addStud(_studentAddr);

}

// Function to get student by call the ID
Expand Down Expand Up @@ -155,14 +127,15 @@ contract StudentRegistryV2 is Ownable {
}


function getAllStudents() public view returns (Student[] memory students) {
function getAllStudents() public view returns (Student[] memory students) {
return students;
}

function getOwner() public view returns (address) {
return owner;
function getOwner() public view override returns (address) {
return super.getOwner();
}


receive() external payable {}


Expand Down
9 changes: 9 additions & 0 deletions test/Ownable.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,13 @@ describe("Ownable Test Suite", () => {
});
});
});

describe("Events", () => {
it("should emit ChangeOwner event when owner is change", async () => {
const { deployedOwnable, owner, addr1, addr2, ZERO_ADDRESS } = await loadFixture(deployUtil);
await expect(deployedOwnable.connect(owner).changeOwner(addr1.address))
.to.emit(deployedOwnable, "ChangeOwner")
.withArgs(owner.address, addr1.address);
});
});
});
124 changes: 124 additions & 0 deletions test/StudentRegistry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
const { time, loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
const { expect } = require("chai");
const { ethers } = require("hardhat");

const convertEther = (amount) => {
return ethers.parseEther(amount);
};

// Get balance util function
const getBalance = async (account) => {
const balance = await ethers.provider.getBalance(account);
return balance;
};

describe("StudentRegistryV2 Test Suite", () => {
// deploy util function
const deployUtil = async () => {
const StudentRegistryV2 = await ethers.getContractFactory("StudentRegistryV2"); // instance of StudentRegistryV2 contract in Contracts folder
const deployedStudentRegistryV2 = await StudentRegistryV2.deploy(); // the deployed version of the StudentRegistryV2 contract in the network
const [owner, addr1, addr2] = await ethers.getSigners();
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
return {
deployedStudentRegistryV2,
owner,
addr1,
addr2,
ZERO_ADDRESS,
deployedStudentRegistryV2Address: deployedStudentRegistryV2.target,
};
};

describe("Deployment", () => {
it("should deploy succesfuly and return default values of count", async () => {
const { deployedStudentRegistryV2, owner, ZERO_ADDRESS, addr1 } = await loadFixture(deployUtil);
// check the owner of the StudentRegistry contract which auto inherits from Ownable
let studentRegistryV2Owner = await deployedStudentRegistryV2.getOwner();
expect(studentRegistryV2Owner).to.eq(owner);

// check that students array of Student struct is empty
let allStudents = await deployedStudentRegistryV2.getAllStudents();
expect(allStudents.length).to.eq(0);

// check that studentsMapping has default values
// meaning no values pushed to studentsMapping upon successful deployment
let studentsMapping = await deployedStudentRegistryV2.studentsMapping(ZERO_ADDRESS);
let addr1Mapping = await deployedStudentRegistryV2.studentsMapping(addr1);
const emptyStudentStruct = ["0x0000000000000000000000000000000000000000", "", "0", "0", false, false];
expect(...addr1Mapping).to.eq(...emptyStudentStruct);
expect(...studentsMapping).to.eq(...emptyStudentStruct);

// check that tempstudentsMapping has default values
const tempStudentsMapping = await deployedStudentRegistryV2.tempstudentsMapping(addr1);
expect(...tempStudentsMapping).to.eq(...emptyStudentStruct);

// check if hasPaidMapping has default value of false
const hasPaidMapping = await deployedStudentRegistryV2.hasPaidMapping(addr1);
expect(hasPaidMapping).to.eq(false);
});
});

describe("Transactions", () => {
describe("PayFee Transaction", () => {
describe("Validations", () => {
it("should revert owner attempt to paying 1ETH as fee", async () => {
const { deployedStudentRegistryV2, owner } = await loadFixture(deployUtil);
await expect(
deployedStudentRegistryV2.connect(owner).payFee({ value: convertEther("1") })
).to.be.revertedWith("Owner is excluded");
});

it("should revert attempt to proceed without paying 1ETH as fee", async () => {
const { deployedStudentRegistryV2, addr1 } = await loadFixture(deployUtil);
await expect(
deployedStudentRegistryV2.connect(addr1).payFee({ value: convertEther("0") })
).to.be.revertedWith("You must pay fee");
});
});

describe("Success", () => {
it("should successfully process payment of 1 ETH fee", async () => {
const { deployedStudentRegistryV2, addr1, deployedStudentRegistryV2Address, owner } = await loadFixture(
deployUtil
);

// Fetch contract balance before payFee txn
const initialContractBalance = await getBalance(deployedStudentRegistryV2Address);

// Fetch addr1 balance of before payFee txn
const initialPayerBalance = await getBalance(addr1.address);

// Perform the payment
const tx = await deployedStudentRegistryV2.connect(addr1).payFee({ value: ethers.parseEther("1") });
await tx.wait(); // Wait for the transaction to be mined

// Fetch updated balances
const finalPayerBalance = await getBalance(addr1.address);
const finalContractBalance = await getBalance(deployedStudentRegistryV2Address);

// Assert the balance changes
const initialPayerBalanceNum = parseFloat(ethers.formatEther(initialPayerBalance));
const finalPayerBalanceNum = parseFloat(ethers.formatEther(finalPayerBalance));
const initialContractBalanceNum = parseFloat(ethers.formatEther(initialContractBalance));
const finalContractBalanceNum = parseFloat(ethers.formatEther(finalContractBalance));

// Check that the payer's balance decreased by 1 ETH
expect(finalPayerBalanceNum).to.be.closeTo(initialPayerBalanceNum - 1, 0.01); // Use a tolerance for floating point comparison

// Check that the contract's balance increased by 1 ETH
expect(finalContractBalanceNum).to.be.closeTo(initialContractBalanceNum + 1, 0.01); // Use a tolerance for floating point comparison
});
});
describe("Event", () => {
it("should emit PaidFee", async () => {
const { deployedStudentRegistryV2, addr1 } = await loadFixture(deployUtil);

await expect(deployedStudentRegistryV2.connect(addr1).payFee({ value: ethers.parseEther("1") }))
.to.emit(deployedStudentRegistryV2, "PaidFee")
.withArgs(addr1.address, ethers.parseEther("1"));
});
});
});
});
});
Loading

0 comments on commit 5040e4f

Please sign in to comment.