From a192d964c2fc9a54b662b45cd68436adcedce91a Mon Sep 17 00:00:00 2001 From: Shehu-Fatiudeen Lawal Date: Wed, 19 Feb 2025 12:37:15 +0100 Subject: [PATCH 1/6] feat: counter contract --- assignments/fatiudeen/Counter.sol | 79 +++++++++++++++++++ .../Assignment1/fatiudeen/assignment-1.md | 12 +++ 2 files changed, 91 insertions(+) create mode 100644 assignments/fatiudeen/Counter.sol create mode 100644 submissions/Assignment1/fatiudeen/assignment-1.md diff --git a/assignments/fatiudeen/Counter.sol b/assignments/fatiudeen/Counter.sol new file mode 100644 index 00000000..86742549 --- /dev/null +++ b/assignments/fatiudeen/Counter.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +/// @title Counter Contract +/// @notice A simple contract that implements a counter with increase, decrease and reset functionality +contract Counter { + /// @notice The current value of the counter + uint public count; + + /// @notice Emitted when the counter value is increased + /// @param amount The new value of the counter + /// @param when The timestamp when the increase occurred + event CountIncreased(uint amount, uint when); + + /// @notice Emitted when the counter value is decreased + /// @param amount The new value of the counter + /// @param when The timestamp when the decrease occurred + event CountDecreased(uint amount, uint when); + + /// @notice Increases the counter by one + /// @dev Reverts if the operation would cause an overflow + function increaseByOne() public { + require(count < type(uint).max, "cannot increase beyond max uint"); + count += 1; + emit CountIncreased(count, block.timestamp); + } + + /// @notice Increases the counter by a specified value + /// @param _value The amount to increase the counter by + /// @dev Reverts if the operation would cause an overflow + function increaseByValue(uint _value) public { + require(count + _value <= type(uint).max, "cannot increase beyond max uint"); + count += _value; + emit CountIncreased(count, block.timestamp); + } + + /// @notice Decreases the counter by one + /// @dev Reverts if the operation would cause an underflow + function decreaseByOne() public { + require(count > 0, "cannot decrease below 0"); + count -= 1; + emit CountDecreased(count, block.timestamp); + } + + /// @notice Decreases the counter by a specified value + /// @param _value The amount to decrease the counter by + /// @dev Reverts if the operation would cause an underflow + function decreaseByValue(uint _value) public { + require(count >= _value, "cannot decrease below 0"); + count -= _value; + emit CountDecreased(count, block.timestamp); + } + + /// @notice Resets the counter to zero + function resetCount() public { + uint oldCount = count; + if (oldCount != 0) { + count = 0; + emit CountDecreased(count, block.timestamp); + } + } + + /// @notice Returns the current value of the counter + /// @return The current counter value + function getCount() public view returns (uint) { + return count; + } + + /// @notice Returns the maximum value of uint256 + /// @return The maximum value that can be stored in a uint256 + /// @dev Uses unchecked arithmetic to intentionally underflow and get max uint + function getMaxUint256() public pure returns (uint) { + uint max; + unchecked { + max = 0 - 1; + } + return max; + } +} diff --git a/submissions/Assignment1/fatiudeen/assignment-1.md b/submissions/Assignment1/fatiudeen/assignment-1.md new file mode 100644 index 00000000..11dbdfbd --- /dev/null +++ b/submissions/Assignment1/fatiudeen/assignment-1.md @@ -0,0 +1,12 @@ +# Assignment 1 Submission + +The implementation of the Counter Contract can be found at: +[Counter.sol](../../../assignments/fatiudeen/Counter.sol) + +This implementation includes: +- Complete counter functionality with increase/decrease operations +- Overflow and underflow protection +- Event emission for all counter changes +- Comprehensive error handling +- Clear NatSpec documentation +- Pure function for max uint256 calculation From 3c71142b4b87d3a7d37ffab89a5208f84271ea0e Mon Sep 17 00:00:00 2001 From: Shehu-Fatiudeen Lawal Date: Wed, 19 Feb 2025 12:38:36 +0100 Subject: [PATCH 2/6] feat: student registry --- assignments/fatiudeen/StudentRegistry.sol | 179 ++++++++++++++++++ .../Assignment2/fatiudeen/assignment-2.md | 12 ++ 2 files changed, 191 insertions(+) create mode 100644 assignments/fatiudeen/StudentRegistry.sol create mode 100644 submissions/Assignment2/fatiudeen/assignment-2.md diff --git a/assignments/fatiudeen/StudentRegistry.sol b/assignments/fatiudeen/StudentRegistry.sol new file mode 100644 index 00000000..fbe2ee90 --- /dev/null +++ b/assignments/fatiudeen/StudentRegistry.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +/// @title Student Registry Contract +/// @notice A contract for managing student information, attendance, and interests +contract StudentRegistry { + /// @notice Possible attendance statuses + enum Attendance { Present, Absent } + + /// @notice Structure to store student information + struct Student { + string name; + Attendance attendance; + string[] interests; + } + + /// @notice Maps student addresses to their information + mapping(address => Student) public students; + + /// @notice Contract owner address + address public owner; + + /// @notice Emitted when a new student is registered + event StudentCreated(address _studentAddress, string _name); + /// @notice Emitted when attendance is updated + event AttendanceStatus(address _studentAddress, Attendance _attendance); + /// @notice Emitted when an interest is added + event InterestAdded(address _studentAddress, string _interest); + /// @notice Emitted when an interest is removed + event InterestRemoved(address _studentAddress, string _interest); + + /// @notice Ensures only the owner can call certain functions + modifier onlyOwner() { + require(msg.sender == owner, "Only owner can call this function"); + _; + } + + /// @notice Ensures the student exists + modifier studentExists(address _address) { + require(bytes(students[_address].name).length > 0, "Student does not exist"); + _; + } + + /// @notice Ensures the student does not exist + modifier studentDoesNotExist(address _address) { + require(bytes(students[_address].name).length == 0, "Student already exists"); + _; + } + + /// @notice Sets the contract deployer as the owner + constructor() { + owner = msg.sender; + } + + /// @notice Registers a new student with full information + function registerStudent( + string memory _name, + Attendance _attendance, + string[] memory _interests + ) public studentDoesNotExist(msg.sender) { + require(bytes(_name).length > 0, "Name cannot be empty"); + require(_interests.length <= 5, "Maximum 5 interests allowed"); + + students[msg.sender] = Student({ + name: _name, + attendance: _attendance, + interests: _interests + }); + + emit StudentCreated(msg.sender, _name); + } + + /// @notice Registers a new student with just a name + function registerNewStudent(string memory _name) public studentDoesNotExist(msg.sender) { + require(bytes(_name).length > 0, "Name cannot be empty"); + + students[msg.sender] = Student({ + name: _name, + attendance: Attendance.Absent, + interests: new string[](0) + }); + + emit StudentCreated(msg.sender, _name); + } + + /// @notice Updates a student's attendance + function markAttendance(address _address, Attendance _attendance) + public + onlyOwner + studentExists(_address) + { + students[_address].attendance = _attendance; + emit AttendanceStatus(_address, _attendance); + } + + /// @notice Adds an interest to a student's profile + function addInterest(address _address, string memory _interest) + public + studentExists(_address) + { + require(bytes(_interest).length > 0, "Interest cannot be empty"); + require(students[_address].interests.length < 5, "Maximum 5 interests allowed"); + + // Check for duplicates + for(uint i = 0; i < students[_address].interests.length; i++) { + require( + keccak256(bytes(students[_address].interests[i])) != keccak256(bytes(_interest)), + "Interest already exists" + ); + } + + students[_address].interests.push(_interest); + emit InterestAdded(_address, _interest); + } + + /// @notice Removes an interest from a student's profile + function removeInterest(address _address, string memory _interest) + public + studentExists(_address) + { + string[] storage interests = students[_address].interests; + bool found = false; + uint indexToRemove; + + for(uint i = 0; i < interests.length; i++) { + if(keccak256(bytes(interests[i])) == keccak256(bytes(_interest))) { + indexToRemove = i; + found = true; + break; + } + } + + require(found, "Interest not found"); + + // Move the last element to the position of the element to remove + if(indexToRemove != interests.length - 1) { + interests[indexToRemove] = interests[interests.length - 1]; + } + interests.pop(); + + emit InterestRemoved(_address, _interest); + } + + /// @notice Gets a student's name + function getStudentName(address _address) + public + view + studentExists(_address) + returns (string memory) + { + return students[_address].name; + } + + /// @notice Gets a student's attendance + function getStudentAttendance(address _address) + public + view + studentExists(_address) + returns (Attendance) + { + return students[_address].attendance; + } + + /// @notice Gets a student's interests + function getStudentInterests(address _address) + public + view + studentExists(_address) + returns (string[] memory) + { + return students[_address].interests; + } + + /// @notice Transfers ownership of the contract + function transferOwnership(address _newOwner) public onlyOwner { + require(_newOwner != address(0), "New owner cannot be zero address"); + owner = _newOwner; + } +} \ No newline at end of file diff --git a/submissions/Assignment2/fatiudeen/assignment-2.md b/submissions/Assignment2/fatiudeen/assignment-2.md new file mode 100644 index 00000000..5470e632 --- /dev/null +++ b/submissions/Assignment2/fatiudeen/assignment-2.md @@ -0,0 +1,12 @@ +# Assignment 2 Submission + +The implementation of the Student Registry can be found at: +[StudentRegistry.sol](../../../assignments/fatiudeen/StudentRegistry.sol) + +This implementation includes: +- Full student registration system +- Attendance tracking +- Interest management +- Comprehensive access controls +- Event emission for all major actions +- Security considerations and input validation From 102d9a446452d445e4a0f7064236b6a13ae289f2 Mon Sep 17 00:00:00 2001 From: Shehu-Fatiudeen Lawal Date: Wed, 19 Feb 2025 12:39:20 +0100 Subject: [PATCH 3/6] feat: funding contract --- assignments/fatiudeen/CrowdNFT.sol | 25 +++++++ assignments/fatiudeen/CrowdToken.sol | 19 +++++ assignments/fatiudeen/Crowdfunding.sol | 73 +++++++++++++++++++ .../Assignment3/fatiudeen/assignment-3.md | 14 ++++ 4 files changed, 131 insertions(+) create mode 100644 assignments/fatiudeen/CrowdNFT.sol create mode 100644 assignments/fatiudeen/CrowdToken.sol create mode 100644 assignments/fatiudeen/Crowdfunding.sol create mode 100644 submissions/Assignment3/fatiudeen/assignment-3.md diff --git a/assignments/fatiudeen/CrowdNFT.sol b/assignments/fatiudeen/CrowdNFT.sol new file mode 100644 index 00000000..ab6eec15 --- /dev/null +++ b/assignments/fatiudeen/CrowdNFT.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/Counters.sol"; + +/// @title Crowd NFT +/// @notice ERC721 token for top funders +contract CrowdNFT is ERC721, Ownable { + using Counters for Counters.Counter; + Counters.Counter private _tokenIds; + + constructor() ERC721("Crowd NFT", "CNFT") Ownable(msg.sender) {} + + /// @notice Mints a new NFT to the specified address + /// @param to Address to receive the NFT + /// @return The ID of the newly minted NFT + function mintNFT(address to) public onlyOwner returns (uint256) { + _tokenIds.increment(); + uint256 newTokenId = _tokenIds.current(); + _safeMint(to, newTokenId); + return newTokenId; + } +} \ No newline at end of file diff --git a/assignments/fatiudeen/CrowdToken.sol b/assignments/fatiudeen/CrowdToken.sol new file mode 100644 index 00000000..bdd23d20 --- /dev/null +++ b/assignments/fatiudeen/CrowdToken.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +/// @title Crowd Token +/// @notice ERC20 token for crowdfunding rewards +contract CrowdToken is ERC20, Ownable { + /// @notice Constructor sets the token name and symbol + constructor() ERC20("Crowd Token", "CROWD") Ownable(msg.sender) {} + + /// @notice Mints new tokens to an address + /// @param to Address to receive the tokens + /// @param amount Amount of tokens to mint + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } +} \ No newline at end of file diff --git a/assignments/fatiudeen/Crowdfunding.sol b/assignments/fatiudeen/Crowdfunding.sol new file mode 100644 index 00000000..e37981e8 --- /dev/null +++ b/assignments/fatiudeen/Crowdfunding.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "./CrowdToken.sol"; +import "./CrowdNFT.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +/// @title Crowdfunding Platform +/// @notice Manages crowdfunding campaigns with token and NFT rewards +contract Crowdfunding is Ownable { + CrowdToken public token; + CrowdNFT public nft; + + uint256 public fundingGoal; + uint256 public totalFunds; + uint256 public constant NFT_THRESHOLD = 1 ether; + uint256 public constant TOKEN_RATE = 100; // tokens per ether + + mapping(address => uint256) public contributions; + + event ContributionReceived(address contributor, uint256 amount); + event GoalReached(uint256 totalAmount); + event FundsWithdrawn(uint256 amount); + + constructor( + uint256 _goal, + address _tokenAddress, + address _nftAddress + ) Ownable(msg.sender) { + fundingGoal = _goal; + token = CrowdToken(_tokenAddress); + nft = CrowdNFT(_nftAddress); + } + + /// @notice Allows users to contribute ETH to the campaign + function contribute() public payable { + require(msg.value > 0, "Contribution must be greater than 0"); + + contributions[msg.sender] += msg.value; + totalFunds += msg.value; + + // Mint reward tokens + uint256 tokenReward = (msg.value * TOKEN_RATE) / 1 ether; + token.mint(msg.sender, tokenReward); + + // Mint NFT if contribution exceeds threshold + if (contributions[msg.sender] >= NFT_THRESHOLD) { + nft.mintNFT(msg.sender); + } + + emit ContributionReceived(msg.sender, msg.value); + + if (totalFunds >= fundingGoal) { + emit GoalReached(totalFunds); + } + } + + /// @notice Allows owner to withdraw funds if goal is reached + function withdrawFunds() public onlyOwner { + require(totalFunds >= fundingGoal, "Funding goal not reached"); + + uint256 amount = address(this).balance; + (bool sent, ) = payable(owner()).call{value: amount}(""); + require(sent, "Failed to send Ether"); + + emit FundsWithdrawn(amount); + } + + /// @notice Returns the contribution amount for an address + function getContribution(address contributor) public view returns (uint256) { + return contributions[contributor]; + } +} \ No newline at end of file diff --git a/submissions/Assignment3/fatiudeen/assignment-3.md b/submissions/Assignment3/fatiudeen/assignment-3.md new file mode 100644 index 00000000..11b1c616 --- /dev/null +++ b/submissions/Assignment3/fatiudeen/assignment-3.md @@ -0,0 +1,14 @@ +# Assignment 3 Submission + +The Tokenized Crowdfunding Platform implementation consists of three contracts: + +1. [CrowdToken.sol](../../../assignments/fatiudeen/CrowdToken.sol) - ERC20 token for crowdfunding rewards +2. [CrowdNFT.sol](../../../assignments/fatiudeen/CrowdNFT.sol) - ERC721 token for top contributors +3. [Crowdfunding.sol](../../../assignments/fatiudeen/Crowdfunding.sol) - Main crowdfunding platform + +The implementation includes: +- ERC20 token rewards based on contribution amount +- ERC721 NFT rewards for contributions over 1 ETH +- Secure fund management +- Owner withdrawal functionality +- Event emission for tracking \ No newline at end of file From a1fbe6ecc5238afa0edb3c7ce1f00dc951ee95d5 Mon Sep 17 00:00:00 2001 From: Shehu-Fatiudeen Lawal Date: Wed, 19 Feb 2025 12:57:00 +0100 Subject: [PATCH 4/6] chore: restructure --- submissions/Assignment3/README.md | 3 --- submissions/{Assignment3/fatiudeen => }/assignment-3.md | 0 2 files changed, 3 deletions(-) delete mode 100644 submissions/Assignment3/README.md rename submissions/{Assignment3/fatiudeen => }/assignment-3.md (100%) diff --git a/submissions/Assignment3/README.md b/submissions/Assignment3/README.md deleted file mode 100644 index 6f3a09c7..00000000 --- a/submissions/Assignment3/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Submissions for Assignment 3 ---- ---- \ No newline at end of file diff --git a/submissions/Assignment3/fatiudeen/assignment-3.md b/submissions/assignment-3.md similarity index 100% rename from submissions/Assignment3/fatiudeen/assignment-3.md rename to submissions/assignment-3.md From 6024d51f7a25ed84904a40c232651b385345a216 Mon Sep 17 00:00:00 2001 From: Shehu-Fatiudeen Lawal Date: Wed, 19 Feb 2025 12:57:32 +0100 Subject: [PATCH 5/6] chore: restructure --- submissions/Assignment3/README.md | 3 --- submissions/{Assignment2/fatiudeen => }/assignment-2.md | 0 2 files changed, 3 deletions(-) delete mode 100644 submissions/Assignment3/README.md rename submissions/{Assignment2/fatiudeen => }/assignment-2.md (100%) diff --git a/submissions/Assignment3/README.md b/submissions/Assignment3/README.md deleted file mode 100644 index 6f3a09c7..00000000 --- a/submissions/Assignment3/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Submissions for Assignment 3 ---- ---- \ No newline at end of file diff --git a/submissions/Assignment2/fatiudeen/assignment-2.md b/submissions/assignment-2.md similarity index 100% rename from submissions/Assignment2/fatiudeen/assignment-2.md rename to submissions/assignment-2.md From 893ec2f91257d18ce56fc5ba2584dac0be06aa98 Mon Sep 17 00:00:00 2001 From: Shehu-Fatiudeen Lawal Date: Wed, 19 Feb 2025 12:58:05 +0100 Subject: [PATCH 6/6] chore: restructure --- submissions/Assignment3/README.md | 3 --- submissions/{Assignment1/fatiudeen => }/assignment-1.md | 0 2 files changed, 3 deletions(-) delete mode 100644 submissions/Assignment3/README.md rename submissions/{Assignment1/fatiudeen => }/assignment-1.md (100%) diff --git a/submissions/Assignment3/README.md b/submissions/Assignment3/README.md deleted file mode 100644 index 6f3a09c7..00000000 --- a/submissions/Assignment3/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Submissions for Assignment 3 ---- ---- \ No newline at end of file diff --git a/submissions/Assignment1/fatiudeen/assignment-1.md b/submissions/assignment-1.md similarity index 100% rename from submissions/Assignment1/fatiudeen/assignment-1.md rename to submissions/assignment-1.md