Can you grade the solidity code on the following parameters: General Coding Practices, Functional Correctness, Security, Gas Optimisations, and Code Readability. Don't check for overflows.
1 is the lowest no. of points for a parameter and 5 is the highest no. of points for a parameter. The guidelines for each parameter are below. Please give detailed reasoning on the score for each parameter.

Functional Errors
- Checking the return value of call to confirm success of transfer while sending ether in withdrawStream = 1 point
- Add missing require in createStream to check startTime < stopTime	= 1 point
- Correcting implementation of balanceOf function = 1 point
- Fixing withdrawFromStream function, every withdrawl should save a timestamp so that user balance can later be calculated based on that timestamp	= 1 point
- Rest any errors solved = 1 point
Feature Addition
- Handling incorrect streamId	= 2 points
- Correct functioning of cancelStream i.e., sending back correct amounts to receiver and sender	= 3 points
Security Issues
- Correct function visibility	= 2 points
- Checks to make sure msg.value is equal to deposit in createStream function = 1 point
- No re-entrancy bug = 2 points
Gas Optimisations
- Variable Packing, streamIdCounter and owner can be packed together	= 1 point
- Converting public functions to external functions wherever necessary = 1 point
- Storage access issues, variable can be stored in memory first to save gas = 3 points
Tests
- Coverage of added functionalities = 3 points
- Tests for security and functional issues found and fixed	= 2 points
Code Readability
- Use of modifiers wherever necessary = 2 points
- Emitting events wherever necessary = 5 points

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "hardhat/console.sol";

contract Streaming {

    address public owner;
    
    mapping(uint256 => Stream) private streams;
    
    uint64 public streamIdCounter;

    modifier onlyRecipient(uint256 streamId) {
        require (msg.sender == streams[streamId].recipient, "caller is not the recipient of the stream");
        _;
    }

    struct Stream {
        address recipient;
        address sender;
        uint256 deposit;
        uint256 startTime;
        uint256 stopTime;
        uint256 rate;
        uint256 balance;
    }
    
    event CreateStream(
        uint256 indexed streamId,
        address indexed sender,
        address indexed recipient,
        uint256 deposit,
        uint256 startTime,
        uint256 stopTime
    );

    event WithdrawFromStream(uint256 indexed streamId, address indexed recipient);
    event CancelStream(uint256 indexed streamId, address sender, address recipient, uint256 deposit, uint256 amount); // .withArgs(1, sender.address, recipient1.address, deposit, 0);
    
    constructor() {
        owner  = msg.sender;
    }

    function createStream(
            address recipient,
            uint256 deposit,
            uint256 startTime,
            uint256 stopTime
    ) public returns (uint256 streamId) {
        
        require(recipient != address(0x00), "Stream to the zero address");
        require(recipient != address(this), "Stream to the contract itself");
        require(recipient != msg.sender, "Stream to the caller");
        require(deposit > 0, "Deposit is equal to zero");
        require(startTime >= block.timestamp, "Start time before block timestamp");
        
        uint256 duration = stopTime - startTime;
        
        require(deposit >= duration, "Deposit smaller than duration");
        require(deposit % duration == 0, "Deposit is not a multiple of time delta");
        
        streamIdCounter += 1;
        uint256 currentStreamId = streamIdCounter;
        
        // Rate Per second
        uint256 rate = deposit / duration;
        
        streams[currentStreamId] = Stream({
           balance: deposit,
           deposit: deposit,
           rate: rate,
           recipient: recipient,
           sender: msg.sender,
           startTime: startTime,
           stopTime: stopTime
        });
        
        emit CreateStream(currentStreamId, msg.sender, recipient, deposit, startTime, stopTime);
        return currentStreamId;
    }
    
    function balanceOf( uint256 streamId, address who)  private view returns (uint256 balance) {
        
        Stream memory stream = streams[streamId];
        require(stream.rate > 0, "stream does not exist");
        uint256 elapsedTime = elapsedTimeFor(streamId);
        uint256 due = elapsedTime * stream.rate;
        
        if (who == stream.recipient) {
            return due;
        } else if (who == stream.sender) {
            return stream.balance - due;
        } else {
            return 0;
        }
    }
        
        
    function elapsedTimeFor(uint256 streamId) private view returns (uint256 delta) {
        Stream memory stream = streams[streamId];
        
        // Before the start of the stream
        if (block.timestamp <= stream.startTime) return 0;
        
        // During the stream
        if (block.timestamp < stream.stopTime) return block.timestamp - stream.startTime;
        
        // After the end of the stream
        return stream.stopTime - stream.startTime;
    }
    
    function withdrawFromStream(uint256 streamId) public onlyRecipient(streamId) {
        uint256 balance = balanceOf(streamId, streams[streamId].recipient);
        require(balance > 0, "Available balance is 0");
        
        payable(streams[streamId].recipient).call{value: balance}("");
        streams[streamId].balance = streams[streamId].deposit - balance;
        
        emit WithdrawFromStream(streamId, streams[streamId].recipient);
    }

    function getStream(uint256 streamId) external view returns (
            address sender,
            address recipient,
            uint256 deposit,
            uint256 startTime,
            uint256 stopTime,
            uint256 rate
        )
    {
        sender = streams[streamId].sender;
        recipient = streams[streamId].recipient;
        deposit = streams[streamId].deposit;
        startTime = streams[streamId].startTime;
        stopTime = streams[streamId].stopTime;
        rate = streams[streamId].rate;
    }

    function cancelStream(uint streamId) public payable returns (uint256 recipientAmountEarned) {
        Stream memory stream = streams[streamId];
        require(stream.rate > 0, "stream does not exist"); 

        uint recipientAmountEarned = balanceOf(streamId, stream.recipient); 
        uint senderAmountReturned = balanceOf(streamId, stream.sender);

        payable(streams[streamId].recipient).call{value: recipientAmountEarned}("");
        payable(stream.sender).call{value: senderAmountReturned}("");

        emit WithdrawFromStream(streamId, streams[streamId].recipient);
        emit CancelStream(streamId, streams[streamId].sender, streams[streamId].recipient, streams[streamId].deposit, recipientAmountEarned);
        return recipientAmountEarned;
    }
    
}