diff --git a/contracts/StudentRegistry.sol b/contracts/StudentRegistry.sol new file mode 100644 index 00000000..fc69fb77 --- /dev/null +++ b/contracts/StudentRegistry.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +contract StudentRegistry { + // Attendance status. + enum Attendance { Present, Absent } + + // Student data structure. + struct Student { + string name; + Attendance attendance; + string[] interests; + } + + mapping(address => Student) public students; + address public owner; + + event StudentCreated(address studentAddress, string name); + event AttendanceStatus(address studentAddress, Attendance attendance); + event InterestAdded(address studentAddress, string interest); + event InterestRemoved(address studentAddress, string interest); + + modifier onlyOwner() { + require(msg.sender == owner, "Only owner"); + _; + } + + modifier studentExists(address studentAddress) { + require(bytes(students[studentAddress].name).length != 0, "Student does not exist"); + _; + } + + modifier studentDoesNotExist(address studentAddress) { + require(bytes(students[studentAddress].name).length == 0, "Student already exists"); + _; + } + + constructor() { + owner = msg.sender; + } + + // Register a student with full details. + function registerStudent( + string memory name, + Attendance attendance, + string[] memory interests + ) public studentDoesNotExist(msg.sender) { + require(bytes(name).length > 0, "Name cannot be empty"); + students[msg.sender] = Student(name, attendance, interests); + emit StudentCreated(msg.sender, name); + } + + // Register a student with a name and default attendance (Absent). + function registerNewStudent(string memory name) + public + studentDoesNotExist(msg.sender) + { + require(bytes(name).length > 0, "Name cannot be empty"); + students[msg.sender] = Student(name, Attendance.Absent, new string[](0)); + emit StudentCreated(msg.sender, name); + } + + // Update a student's attendance. + function markAttendance(address studentAddress, Attendance attendance) + public + studentExists(studentAddress) + { + students[studentAddress].attendance = attendance; + emit AttendanceStatus(studentAddress, attendance); + } + + // Add an interest to a student's profile. + function addInterest(address studentAddress, string memory interest) + public + studentExists(studentAddress) + { + require(bytes(interest).length > 0, "Interest cannot be empty"); + Student storage student = students[studentAddress]; + require(student.interests.length < 5, "Max interests reached"); + + for (uint i = 0; i < student.interests.length; i++) { + require(keccak256(bytes(student.interests[i])) != keccak256(bytes(interest)), "Duplicate interest"); + } + student.interests.push(interest); + emit InterestAdded(studentAddress, interest); + } + + // Remove an interest from a student's profile. + function removeInterest(address studentAddress, string memory interest) + public + studentExists(studentAddress) + { + Student storage student = students[studentAddress]; + uint length = student.interests.length; + bool found = false; + for (uint i = 0; i < length; i++) { + if (keccak256(bytes(student.interests[i])) == keccak256(bytes(interest))) { + student.interests[i] = student.interests[length - 1]; + student.interests.pop(); + found = true; + emit InterestRemoved(studentAddress, interest); + break; + } + } + require(found, "Interest not found"); + } + + // Get the student's name. + function getStudentName(address studentAddress) + public + view + studentExists(studentAddress) + returns (string memory) + { + return students[studentAddress].name; + } + + // Get the student's attendance. + function getStudentAttendance(address studentAddress) + public + view + studentExists(studentAddress) + returns (Attendance) + { + return students[studentAddress].attendance; + } + + // Get the student's interests. + function getStudentInterests(address studentAddress) + public + view + studentExists(studentAddress) + returns (string[] memory) + { + return students[studentAddress].interests; + } + + // Transfer contract ownership. + function transferOwnership(address newOwner) public onlyOwner { + require(newOwner != address(0), "Invalid address"); + owner = newOwner; + } + + // (Bonus) Update the caller's name. + function updateStudentName(string memory newName) + public + studentExists(msg.sender) + { + require(bytes(newName).length > 0, "New name empty"); + students[msg.sender].name = newName; + emit StudentCreated(msg.sender, newName); + } +} diff --git a/submissions/assignment-2.md b/submissions/assignment-2.md new file mode 100644 index 00000000..f5bb8d9e --- /dev/null +++ b/submissions/assignment-2.md @@ -0,0 +1,2 @@ +# Assignment 2 +Here's a link to [assignment 2](../contracts/studentRegistry.sol) \ No newline at end of file diff --git a/test/StudentRegistry.js b/test/StudentRegistry.js new file mode 100644 index 00000000..d2c6ce72 --- /dev/null +++ b/test/StudentRegistry.js @@ -0,0 +1,69 @@ +const { expect } = require("chai"); +const { ethers } = require("hardhat"); + +describe("StudentRegistry", function () { + let StudentRegistry, studentRegistry, owner, addr1, addr2; + + beforeEach(async function () { + [owner, addr1, addr2] = await ethers.getSigners(); + StudentRegistry = await ethers.getContractFactory("StudentRegistry"); + studentRegistry = await StudentRegistry.deploy(); + }); + + it("should deploy with the correct owner", async function () { + expect(await studentRegistry.owner()).to.equal(owner.address); + }); + + it("should allow a student to register", async function () { + await studentRegistry.connect(addr1).registerNewStudent("Alice"); + const studentName = await studentRegistry.getStudentName(addr1.address); + expect(studentName).to.equal("Alice"); + }); + + it("should prevent duplicate registration", async function () { + await studentRegistry.connect(addr1).registerNewStudent("Alice"); + await expect( + studentRegistry.connect(addr1).registerNewStudent("Alice") + ).to.be.revertedWith("Student already exists"); + }); + + it("should allow marking attendance", async function () { + await studentRegistry.connect(addr1).registerNewStudent("Alice"); + await studentRegistry.markAttendance(addr1.address, 1); // 1 = Present + expect(await studentRegistry.getStudentAttendance(addr1.address)).to.equal(1); + }); + + it("should allow adding interests", async function () { + await studentRegistry.connect(addr1).registerNewStudent("Alice"); + await studentRegistry.addInterest(addr1.address, "Blockchain"); + const interests = await studentRegistry.getStudentInterests(addr1.address); + expect(interests).to.include("Blockchain"); + }); + + it("should prevent duplicate interests", async function () { + await studentRegistry.connect(addr1).registerNewStudent("Alice"); + await studentRegistry.addInterest(addr1.address, "Blockchain"); + await expect( + studentRegistry.addInterest(addr1.address, "Blockchain") + ).to.be.revertedWith("Duplicate interest"); + }); + + it("should allow removing interests", async function () { + await studentRegistry.connect(addr1).registerNewStudent("Alice"); + await studentRegistry.addInterest(addr1.address, "Blockchain"); + await studentRegistry.removeInterest(addr1.address, "Blockchain"); + const interests = await studentRegistry.getStudentInterests(addr1.address); + expect(interests).to.not.include("Blockchain"); + }); + + it("should allow owner to transfer ownership", async function () { + await studentRegistry.transferOwnership(addr1.address); + expect(await studentRegistry.owner()).to.equal(addr1.address); + }); + + it("should prevent non-owners from transferring ownership", async function () { + await expect( + studentRegistry.connect(addr1).transferOwnership(addr2.address) + ).to.be.revertedWith("Only owner"); + }); +});