diff --git a/.vscode/settings.json b/.vscode/settings.json index bc1c7197..19035be5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "solidity.compileUsingRemoteVersion": "v0.8.14+commit.80d49f37" + "solidity.compileUsingRemoteVersion": "v0.8.24+commit.e11b9ed9" } \ No newline at end of file diff --git a/README.md b/README.md index e9100817..9fc1c82f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # BlockHeaderWeb3 Cohort 5 -Introduction to the fundamentals of EVM-based smart contract development \ No newline at end of file +Introduction to the fundamentals of EVM-based smart contract development diff --git a/contracts/ERC20.sol b/contracts/ERC20.sol new file mode 100644 index 00000000..f39eb901 --- /dev/null +++ b/contracts/ERC20.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "./IERC20.sol"; +import "./Ownable.sol"; +import "./ReentrancyGuard.sol"; + +contract ERC20 is IERC20, Ownable, ReentrancyGuard { + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval( + address indexed owner, address indexed spender, uint256 value + ); + + // Proper Events for burn and mint functions + event Mint(address indexed to, uint256 value); + event Burn(address indexed from, uint256 value); + + uint256 public totalSupply; + string public name; + string public symbol; + + // decimal state was missing + uint8 public decimals; + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + constructor(string memory _name, string memory _symbol, uint8 _decimals) { + name = _name; + symbol = _symbol; + decimals = _decimals; + } + + function transfer(address recipient, uint256 amount) + external + nonReentrant // Added Reentrancy Guard modifier + returns (bool) + { + require(balanceOf[msg.sender] >= amount, "Insufficient balance"); // Check if there is enough balance to go through + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(msg.sender, recipient, amount); + + return true; + } + + function approve(address spender, uint256 amount) external onlyOwner returns (bool) { // Only owner can approve spender + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) + external + nonReentrant + returns (bool) + { + require(balanceOf[sender] >= amount, "Insufficient balance"); // Check if sender has enough balance to go through + require(allowance[sender][msg.sender] >= amount, "Allowance exceeded"); // Check if address has enough allocation + + allowance[sender][msg.sender] -= amount; + balanceOf[sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(sender, recipient, amount); + return true; + } + + function _mint(address to, uint256 amount) internal onlyOwner { // Only owner can mint tokens + balanceOf[to] += amount; + totalSupply += amount; + emit Mint(to, amount); // proper log mint action + } + + function _burn(address from, uint256 amount) internal onlyOwner { // Only owner can burn tokens + balanceOf[from] -= amount; + totalSupply -= amount; + emit Burn(from, amount); // proper log burn action + } + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } + + function burn(address from, uint256 amount) external { + _burn(from, amount); + } +} \ No newline at end of file diff --git a/contracts/ERC721.sol b/contracts/ERC721.sol new file mode 100644 index 00000000..da0c2ccf --- /dev/null +++ b/contracts/ERC721.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "./Ownable.sol"; +import "./ReentrancyGuard.sol"; + +interface IERC165 { + function supportsInterface(bytes4 interfaceID) + external + view + returns (bool); +} + +interface IERC721 is IERC165 { + function balanceOf(address owner) external view returns (uint256 balance); + function ownerOf(uint256 tokenId) external view returns (address owner); + function safeTransferFrom(address from, address to, uint256 tokenId) + external; + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; + function transferFrom(address from, address to, uint256 tokenId) external; + function approve(address to, uint256 tokenId) external; + function getApproved(uint256 tokenId) + external + view + returns (address operator); + function setApprovalForAll(address operator, bool _approved) external; + function isApprovedForAll(address owner, address operator) + external + view + returns (bool); +} + +interface IERC721Receiver { + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} + +contract ERC721 is IERC721, ReentrancyGuard, Ownable { + event Transfer( + address indexed from, address indexed to, uint256 indexed id + ); + event Approval( + address indexed owner, address indexed spender, uint256 indexed id + ); + event ApprovalForAll( + address indexed owner, address indexed operator, bool approved + ); + + // Proper event for MINT/BURN + event Mint( + address indexed to, uint256 indexed id + ); + + event Burn( + address indexed owner, uint256 indexed id + ); + + // Mapping from token ID to owner address + mapping(uint256 => address) internal _ownerOf; + + // Mapping owner address to token count + mapping(address => uint256) internal _balanceOf; + + // Mapping from token ID to approved address + mapping(uint256 => address) internal _approvals; + + // Mapping from owner to operator approvals + mapping(address => mapping(address => bool)) public isApprovedForAll; + + function supportsInterface(bytes4 interfaceId) + external + pure + returns (bool) + { + return interfaceId == type(IERC721).interfaceId + || interfaceId == type(IERC165).interfaceId; + } + + function ownerOf(uint256 id) external view returns (address owner) { + owner = _ownerOf[id]; + require(owner != address(0), "token doesn't exist"); + } + + function balanceOf(address owner) external view returns (uint256) { + require(owner != address(0), "owner = zero address"); + return _balanceOf[owner]; + } + + function setApprovalForAll(address operator, bool approved) external { + isApprovedForAll[msg.sender][operator] = approved; + emit ApprovalForAll(msg.sender, operator, approved); + } + + function approve(address spender, uint256 id) external onlyOwner { + address owner = _ownerOf[id]; + require( + msg.sender == owner || isApprovedForAll[owner][msg.sender], + "not authorized" + ); + + _approvals[id] = spender; + + emit Approval(owner, spender, id); + } + + function getApproved(uint256 id) external view returns (address) { + require(_ownerOf[id] != address(0), "token doesn't exist"); + return _approvals[id]; + } + + function _isApprovedOrOwner(address owner, address spender, uint256 id) + internal + view + returns (bool) + { + return ( + spender == owner || isApprovedForAll[owner][spender] + || spender == _approvals[id] + ); + } + + function transferFrom(address from, address to, uint256 id) public { + require(from == _ownerOf[id], "from != owner"); + require(to != address(0), "transfer to zero address"); + + require(_isApprovedOrOwner(from, msg.sender, id), "not authorized"); + + _balanceOf[from]--; + _balanceOf[to]++; + _ownerOf[id] = to; + + delete _approvals[id]; + + emit Transfer(from, to, id); + } + + function safeTransferFrom(address from, address to, uint256 id) external nonReentrant { // Reentrancy Guard added + transferFrom(from, to, id); + + require( + to.code.length == 0 + || IERC721Receiver(to).onERC721Received(msg.sender, from, id, "") + == IERC721Receiver.onERC721Received.selector, + "unsafe recipient" + ); + } + + function safeTransferFrom( + address from, + address to, + uint256 id, + bytes calldata data + ) external nonReentrant { // Reentrancy Guard added + transferFrom(from, to, id); + + require( + to.code.length == 0 + || IERC721Receiver(to).onERC721Received(msg.sender, from, id, data) + == IERC721Receiver.onERC721Received.selector, + "unsafe recipient" + ); + } + + function _mint(address to, uint256 id) internal nonReentrant { // Reentrancy Guard added + require(to != address(0), "mint to zero address"); + require(_ownerOf[id] == address(0), "already minted"); + + _balanceOf[to]++; + _ownerOf[id] = to; + + emit Mint(to, id); // Proper event for Mint + } + + function _burn(uint256 id) internal nonReentrant { // Reentrancy Guard added + address owner = _ownerOf[id]; + require(owner != address(0), "not minted"); + + _balanceOf[owner] -= 1; + + delete _ownerOf[id]; + delete _approvals[id]; + + emit Burn(owner, id); + } +} + +contract MyNFT is ERC721 { + function mint(address to, uint256 id) external onlyOwner { // Can only be called by contract owner + _mint(to, id); + } + + function burn(uint256 id) external onlyOwner { // Can only be called by contract owner + require(msg.sender == _ownerOf[id], "not owner"); + _burn(id); + } +} \ No newline at end of file diff --git a/contracts/IERC20.sol b/contracts/IERC20.sol new file mode 100644 index 00000000..d3a2c35c --- /dev/null +++ b/contracts/IERC20.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) + external + returns (bool); + function allowance(address owner, address spender) + external + view + returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) + external + returns (bool); +} \ No newline at end of file diff --git a/contracts/IStudentRegistry.sol b/contracts/IStudentRegistry.sol deleted file mode 100644 index b87e8bbe..00000000 --- a/contracts/IStudentRegistry.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; -import "./Student.sol"; -interface IStudentRegistry { - - - function addStudent( - address _studentAddr, - string memory _name, - uint8 _age - ) external; - - function getStudent(uint8 _studentID) external view returns (Student memory); - - function getStudentFromMapping(address _studentAddr) external view returns (Student memory); - -} diff --git a/contracts/MyStudentRegistry.sol b/contracts/MyStudentRegistry.sol deleted file mode 100644 index 10562f12..00000000 --- a/contracts/MyStudentRegistry.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.7.0 <0.9.0; -import "./IStudentRegistry.sol"; -import "./Student.sol"; -import "./Ownable.sol"; - -contract MyStudentRegistry is Ownable { - - address private StudentRegistryContractAddress; - - constructor(address _studentRgistry){ - StudentRegistryContractAddress = _studentRgistry; - } - - function registerStudent( - address _studentAddr, - string memory _name, - uint8 _age - ) public onlyOwner { - - IStudentRegistry(StudentRegistryContractAddress).addStudent(_studentAddr, _name, _age); - } - - - function getStudent2( - uint8 _studentId - ) public view returns (Student memory) { - - return IStudentRegistry(StudentRegistryContractAddress).getStudent(_studentId); - } -} diff --git a/contracts/Ownable.sol b/contracts/Ownable.sol index 624bf251..d12386cd 100644 --- a/contracts/Ownable.sol +++ b/contracts/Ownable.sol @@ -8,16 +8,15 @@ contract Ownable { event ChangeOwner(address indexed oldOwner, address indexed newOwner); - constructor(){ - owner = msg.sender; - } - - - modifier onlyOwner { + modifier onlyOwner { require(owner == msg.sender, "Caller not owner"); _; } + constructor() payable { + owner = payable (msg.sender); + } + function getOwner() public view returns (address){ return owner; @@ -30,4 +29,6 @@ contract Ownable { emit ChangeOwner(owner, _newOwner); owner = _newOwner; } + + } \ No newline at end of file diff --git a/contracts/ReentrancyGuard.sol b/contracts/ReentrancyGuard.sol new file mode 100644 index 00000000..38e716f0 --- /dev/null +++ b/contracts/ReentrancyGuard.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +contract ReentrancyGuard { + uint256 private constant UNLOCKED = 1; + uint256 private constant LOCKED = 2; + uint256 private _status; + + constructor() { + _status = UNLOCKED; + } + + modifier nonReentrant() { + require(_status != LOCKED, "Kindly wait a bit!"); + _status = LOCKED; + _; + _status = UNLOCKED; + } + +} \ No newline at end of file diff --git a/contracts/Student.sol b/contracts/Student.sol deleted file mode 100644 index f7246ea0..00000000 --- a/contracts/Student.sol +++ /dev/null @@ -1,8 +0,0 @@ - // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - struct Student { - address studentAddr; - string name; - uint256 studentId; - uint8 age; - } diff --git a/contracts/StudentRegistry.sol b/contracts/StudentRegistry.sol deleted file mode 100644 index 591618e1..00000000 --- a/contracts/StudentRegistry.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; -import "./Ownable.sol"; -import "./Student.sol"; - - -contract StudentRegistry is Ownable { - //custom erros - error NameIsEmpty(); - error UnderAge(uint8 age, uint8 expectedAge); - - //custom data type - - - //dynamic array of students - Student[] private students; - - mapping(address => Student) public studentsMapping; - - - modifier isNotAddressZero() { - require(msg.sender != address(0), "Invalid Address"); - _; - } - - function addStudent( - address _studentAddr, - string memory _name, - uint8 _age - ) public isNotAddressZero { - if (bytes(_name).length == 0) { - revert NameIsEmpty(); - } - - if (_age < 18) { - revert UnderAge({age: _age, expectedAge: 18}); - } - - uint256 _studentId = students.length + 1; - Student memory student = Student({ - studentAddr: _studentAddr, - name: _name, - age: _age, - studentId: _studentId - }); - - students.push(student); - // add student to studentsMapping - studentsMapping[_studentAddr] = student; - } - - function getStudent(uint8 _studentId) - public - view - isNotAddressZero - returns (Student memory) - { - return students[_studentId - 1]; - } - - function getStudentFromMapping(address _studentAddr) - public - view - isNotAddressZero - returns (Student memory) - { - return studentsMapping[_studentAddr]; - } - - function deleteStudent(address _studentAddr) - public - onlyOwner - isNotAddressZero - { - require( - studentsMapping[_studentAddr].studentAddr != address(0), - "Student does not exist" - ); - - // delete studentsMapping[_studentAddr]; - - Student memory student = Student({ - studentAddr: address(0), - name: "", - age: 0, - studentId: 0 - }); - - studentsMapping[_studentAddr] = student; - } - - - function modifyOwner(address _newOwner) public { - changeOwner(_newOwner); - } -}