Lack of double step Transfer ownership pattern
transferOwnership is a single-step EOA-targeted call, so a mistyped or unreachable address permanently locks every onlyOwner function.
Description
The current ownership transfer process on the project involves the current owner calling transferOwnership() function.
function transferOwnership(address newOwner) public virtual onlyOwner {require(newOwner != address(0),"Ownable: new owner is the zero address");_setOwner(newOwner);}
Impact
If the nominated EOA account is not a valid one, it is entirely possible that the owner may accidentally transfer ownership to an uncontrolled account, losing the access to all functions with the onlyOwner modifier.
Recommendation
It is recommended to implement a two-step process where the owner nominates an account and the nominated account needs to call an acceptOwnership() function for the transfer of the ownership to fully succeed.
This ensures the nominated EOA account is a valid and active account. This can be easily achieved by using OpenZeppelin's Ownable2Step contract instead of Ownable:
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;import {Ownable} from "./Ownable.sol";abstract contract Ownable2Step is Ownable {address private _pendingOwner;event OwnershipTransferStarted(address indexed previousOwner,address indexed newOwner);function pendingOwner() public view virtual returns (address) {return _pendingOwner;}function transferOwnership(address newOwner) public virtual override onlyOwner {_pendingOwner = newOwner;emit OwnershipTransferStarted(owner(), newOwner);}function _transferOwnership(address newOwner) internal virtual override {delete _pendingOwner;super._transferOwnership(newOwner);}function acceptOwnership() public virtual {address sender = _msgSender();if (pendingOwner() != sender) {revert OwnableUnauthorizedAccount(sender);}_transferOwnership(sender);}}

