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;
 }
 
}
다음 매개변수에 따라 솔리디티 코드의 등급을 매깁니다: 일반적인 코딩 관행, 기능적 정확성, 보안, 가스 최적화 및 코드 가독성. 오버플로는 확인하지 마세요.1은 매개변수의 최저 점수이고 5는 매개변수의 최고 점수입니다. 각 파라미터에 대한 가이드라인은 아래와 같습니다. 각 파라미터의 점수에 대한 자세한 이유를 제시해 주세요.기능적 오류- withdrawStream에서 이더를 전송하는 동안 전송 성공 여부를 확인하기 위해 호출의 반환값을 확인하지 않음 = 1점- createStream에서 startTime < stopTime = 1점 확인을 위한 요구사항 누락 추가- balanceOf 함수 = 1 포인트의 구현 수정- withdrawFromStream 함수 수정, 모든 출금 시 타임스탬프를 저장하여 나중에 해당 타임스탬프를 기준으로 사용자 잔액을 계산할 수 있도록 = 1포인트 수정- 나머지 오류 해결 = 1점기능 추가- 잘못된 스트림아이디 처리 = 2점- 취소 스트림의 올바른 기능, 즉 수신자와 발신자에게 정확한 금액을 다시 보내는 기능 = 3점보안 문제- 올바른 기능 가시성 = 2점- createStream 함수에서 msg.value가 입금과 동일한지 확인 = 1점- 재입금 버그 없음 = 2점가스 최적화- 변수 패킹, streamIdCounter와 소유자를 함께 패킹할 수 있음 = 1점- 필요할 때마다 공용 함수를 외부 함수로 변환 = 1점- 스토리지 액세스 문제, 변수를 메모리에 먼저 저장하여 가스를 절약할 수 있음 = 3점테스트- 추가된 기능의 적용 범위 = 3점- 보안 및 기능 문제 발견 및 수정에 대한 테스트 = 2점코드 가독성- 필요한 경우 수정자 사용 = 2점- 필요한 경우 이벤트 발생 = 5점// SPDX-라이선스 식별자: MIT프라그마 솔리디티 ^0.8.4;"hardhat/console.sol" 임포트;컨트랙트 스트리밍 { 주소 공개 소유자;  맵핑(uint256 => Stream) 비공개 스트림;  uint64 공개 streamIdCounter; 수정자 onlyRecipient(uint256 streamId) { require (msg.sender == streams[streamId].recipient, "발신자가 스트림의 수신자가 아님"); \_; } 구조체 스트림 { 주소 수신자 주소 발신자; uint256 입금; uint256 startTime; uint256 stopTime; uint256 rate; uint256 잔액; }  이벤트 CreateStream( uint256 인덱싱된 streamId, 주소 인덱싱된 발신자, 주소 인덱싱된 수신자 uint256 입금, uint256 startTime, uint256 stopTime ); 이벤트 WithdrawFromStream(uint256 인덱싱된 스트림Id, 주소 인덱싱된 수신자); event CancelStream(uint256 인덱싱된 streamId, 주소 발신자, 주소 수신자, uint256 입금, uint256 금액); // .withArgs(1, 발신자.주소, 수신자1.주소, 입금, 0);  constructor() { 소유자 = msg.발신자; } 함수 createStream( 주소 수신자, uint256 입금, uint256 startTime, uint256 stopTime ) public returns (uint256 streamId) {  require(recipient != address(0x00), "제로 주소로 스트림"); require(recipient != address(this), "컨트랙트 자체로 스트림"); require(recipient != msg.sender, "발신자에게 스트림"); require(deposit > 0, "예치금은 0과 같습니다"); require(startTime >= block.timestamp, "블록 타임스탬프 이전의 시작 시간");  uint256 duration = stopTime - startTime;  require(deposit >= duration, "기간보다 작은 입금"); require(deposit % duration == 0, "예치금은 시간 델타의 배수가 아닙니다");  streamIdCounter += 1; uint256 currentStreamId = streamIdCounter;  // 초당 요율 uint256 rate = 입금 / 기간;  streams[currentStreamId] = Stream({ balance: 예금, 예금: 예금, rate: rate, 수신자: 수신자 발신자: msg.발신자, startTime: 시작시간, stopTime: stopTime });  emit CreateStream(currentStreamId, msg.sender, recipient, deposit, startTime, stopTime); return currentStreamId; }  함수 balanceOf( uint256 streamId, 주소 누구) 비공개 뷰 반환 (uint256 balance) {  스트림 메모리 stream = streams[streamId]; require(stream.rate > 0, "스트림이 존재하지 않습니다"); uint256 elapsedTime = elapsedTimeFor(streamId); uint256 due = elapsedTime \* stream.rate;  if (who == stream.recipient) { 기한을 반환합니다; } else if (who == stream.sender) { stream.balance - 기한을 반환합니다; } else { 반환 0; } }   함수 elapsedTimeFor(uint256 streamId) 비공개 뷰 반환 (uint256 델타) { 스트림 메모리 stream = streams[streamId];  // 스트림 시작 전 if (block.timestamp <= stream.startTime) return 0;  // 스트림 중 if (block.timestamp < stream.stopTime) return block.timestamp - stream.startTime;  // 스트림 종료 후 반환 stream.stopTime - stream.startTime; }  함수 withdrawFromStream(uint256 streamId) public onlyRecipient(streamId) { uint256 balance = balanceOf(streamId, streams[streamId].recipient); require(balance > 0, "사용 가능한 잔액이 0입니다");  payable(streams[streamId].recipient).call{value: balance}(""); 스트림[스트림아이디].잔액 = 스트림[스트림아이디].입금 - 잔액;  emit WithdrawFromStream(streamId, streams[streamId].recipient); 