IN THIS CONTRACT // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1
// This contract is licensed under the SYMM Core Business Source License 1.1
// Copyright (c) 2023 Symmetry Labs AG
// For more information, see https://docs.symm.io/legal-disclaimer/license
pragma solidity ^0.8.20;

import "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./SolverVaultToken.sol";
import "./interfaces/ISymmio.sol";

contract SolverVault is
    Initializable,
    AccessControlEnumerableUpgradeable,
    PausableUpgradeable
{
    // Use SafeERC20 for safer token transfers
    using SafeERC20 for IERC20;

    bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE");
    bytes32 public constant BALANCER_ROLE = keccak256("BALANCER_ROLE");
    bytes32 public constant SETTER_ROLE = keccak256("SETTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE");

    struct WithdrawRequest {
        address receiver;
        uint256 amount;
        RequestStatus status;
        uint256 acceptedRatio;
    }

    enum RequestStatus {
        Pending,
        Ready,
        Done
    }

    event Deposit(address indexed depositor, uint256 amount);
    event DepositToSymmio(
        address indexed depositor,
        address indexed solver,
        uint256 amount
    );
    event WithdrawRequestEvent(
        uint256 indexed requestId,
        address indexed receiver,
        uint256 amount
    );
    event WithdrawRequestAcceptedEvent(
        uint256 providedAmount,
        uint256[] acceptedRequestIds,
        uint256 paybackRatio
    );
    event WithdrawClaimedEvent(
        uint256 indexed requestId,
        address indexed receiver
    );
    event SymmioAddressUpdatedEvent(address indexed newSymmioAddress);
    event SolverUpdatedEvent(address indexed solver);
    event DepositLimitUpdatedEvent(uint256 value);

    ISymmio public symmio;
    address public collateralTokenAddress;
    address public solverVaultTokenAddress;
    address public solver;

    WithdrawRequest[] public withdrawRequests;
    uint256 public lockedBalance;
    uint256 public minimumPaybackRatio;
    uint256 public depositLimit;
    uint256 public currentDeposit;

    uint256 public collateralTokenDecimals;
    uint256 public solverVaultTokenDecimals;

    function initialize(
        address _symmioAddress,
        address _symmioVaultTokenAddress,
        address _solver,
        uint256 _minimumPaybackRatio,
        uint256 _depositLimit
    ) public initializer {
        __AccessControl_init();
        __Pausable_init();
        
        require(_minimumPaybackRatio <= 1e18, "SolverVault: Invalid ratio");
        
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(DEPOSITOR_ROLE, msg.sender);
        _grantRole(BALANCER_ROLE, msg.sender);
        _grantRole(SETTER_ROLE, msg.sender);
        _grantRole(PAUSER_ROLE, msg.sender);
        _grantRole(UNPAUSER_ROLE, msg.sender);
        setSymmioAddress(_symmioAddress);
        setSymmioVaultTokenAddress(_symmioVaultTokenAddress);
        setDepositLimit(_depositLimit);
        setSolver(_solver);
        lockedBalance = 0;
        currentDeposit = 0;
        minimumPaybackRatio = _minimumPaybackRatio;
    }

    function setSymmioAddress(
        address _symmioAddress
    ) public onlyRole(SETTER_ROLE) {
        require(_symmioAddress != address(0), "SolverVault: Zero address");
        symmio = ISymmio(_symmioAddress);
        address beforeCollateral = collateralTokenAddress;
        updateCollateral();
        require(
            beforeCollateral == collateralTokenAddress ||
                beforeCollateral == address(0),
            "SolverVault: Collateral can not be changed"
        );
        emit SymmioAddressUpdatedEvent(_symmioAddress);
    }

    function setSolver(address _solver) public onlyRole(SETTER_ROLE) {
        require(_solver != address(0), "SolverVault: Zero address");
        solver = _solver;
        emit SolverUpdatedEvent(_solver);
    }

    function updateCollateral() internal {
        collateralTokenAddress = symmio.getCollateral();
        collateralTokenDecimals = IERC20Metadata(collateralTokenAddress)
            .decimals();
        require(
            collateralTokenDecimals <= 18,
            "SolverVault: Collateral decimals should be lower than 18"
        );
    }

    function setSymmioVaultTokenAddress(
        address _symmioVaultTokenAddress
    ) internal {
        require(_symmioVaultTokenAddress != address(0), "SolverVault: Zero address");
        solverVaultTokenAddress = _symmioVaultTokenAddress;
        solverVaultTokenDecimals = SolverVaultToken(_symmioVaultTokenAddress)
            .decimals();
        require(
            solverVaultTokenDecimals <= 18,
            "SolverVault: SolverVaultToken decimals should be lower than 18"
        );
    }

    function setDepositLimit(
        uint256 _depositLimit
    ) public onlyRole(SETTER_ROLE) {
        depositLimit = _depositLimit;
        emit DepositLimitUpdatedEvent(_depositLimit);
    }

    function deposit(uint256 amount) external whenNotPaused {
        require(
            currentDeposit + amount <= depositLimit,
            "SolverVault: Deposit limit reached"
        );
        IERC20(collateralTokenAddress).safeTransferFrom(
            msg.sender,
            address(this),
            amount
        );
        uint256 amountInSolverVaultTokenDecimals = solverVaultTokenDecimals >=
            collateralTokenDecimals
            ? amount *
                (10 ** (solverVaultTokenDecimals - collateralTokenDecimals))
            : amount /
                (10 ** (collateralTokenDecimals - solverVaultTokenDecimals));

        SolverVaultToken(solverVaultTokenAddress).mint(
            msg.sender,
            amountInSolverVaultTokenDecimals
        );
        currentDeposit += amount;
        emit Deposit(msg.sender, amount);
    }

    function depositToSymmio(
        uint256 amount
    ) external onlyRole(DEPOSITOR_ROLE) whenNotPaused {
        uint256 contractBalance = IERC20(collateralTokenAddress).balanceOf(
            address(this)
        );
        require(
            contractBalance - lockedBalance >= amount,
            "SolverVault: Insufficient contract balance"
        );
        require(
            IERC20(collateralTokenAddress).approve(address(symmio), amount),
            "SolverVault: Approve failed"
        );
        symmio.depositFor(solver, amount);
        emit DepositToSymmio(msg.sender, solver, amount);
    }

    function requestWithdraw(
        uint256 amount,
        address receiver
    ) external whenNotPaused {
        require(
            SolverVaultToken(solverVaultTokenAddress).balanceOf(msg.sender) >=
                amount,
            "SolverVault: Insufficient token balance"
        );
        SolverVaultToken(solverVaultTokenAddress).burnFrom(msg.sender, amount);

        uint256 amountInCollateralDecimals = collateralTokenDecimals >=
            solverVaultTokenDecimals
            ? amount *
                (10 ** (collateralTokenDecimals - solverVaultTokenDecimals))
            : amount /
                (10 ** (solverVaultTokenDecimals - collateralTokenDecimals));

        currentDeposit -= amountInCollateralDecimals;

        withdrawRequests.push(
            WithdrawRequest({
                receiver: receiver,
                amount: amountInCollateralDecimals,
                status: RequestStatus.Pending,
                acceptedRatio: 0
            })
        );
        emit WithdrawRequestEvent(
            withdrawRequests.length - 1,
            receiver,
            amountInCollateralDecimals
        );
    }

    function acceptWithdrawRequest(
        uint256 providedAmount,
        uint256[] memory _acceptedRequestIds,
        uint256 _paybackRatio
    ) external onlyRole(BALANCER_ROLE) whenNotPaused {
        IERC20(collateralTokenAddress).safeTransferFrom(
            msg.sender,
            address(this),
            providedAmount
        );
        require(
            _paybackRatio >= minimumPaybackRatio,
            "SolverVault: Payback ratio is too low"
        );
        uint256 totalRequiredBalance = lockedBalance;

        for (uint256 i = 0; i < _acceptedRequestIds.length; i++) {
            uint256 id = _acceptedRequestIds[i];
            require(
                id < withdrawRequests.length,
                "SolverVault: Invalid request ID"
            );
            require(
                withdrawRequests[id].status == RequestStatus.Pending,
                "SolverVault: Invalid accepted request"
            );
            totalRequiredBalance +=
                (withdrawRequests[id].amount * _paybackRatio) /
                1e18;
            withdrawRequests[id].status = RequestStatus.Ready;
            withdrawRequests[id].acceptedRatio = _paybackRatio;
        }

        require(
            IERC20(collateralTokenAddress).balanceOf(address(this)) >=
                totalRequiredBalance,
            "SolverVault: Insufficient contract balance"
        );
        lockedBalance = totalRequiredBalance;
        emit WithdrawRequestAcceptedEvent(
            providedAmount,
            _acceptedRequestIds,
            _paybackRatio
        );
    }

    function claimForWithdrawRequest(uint256 requestId) external whenNotPaused {
        require(
            requestId < withdrawRequests.length,
            "SolverVault: Invalid request ID"
        );
        WithdrawRequest storage request = withdrawRequests[requestId];

        require(
            request.status == RequestStatus.Ready,
            "SolverVault: Request not ready for withdrawal"
        );

        request.status = RequestStatus.Done;
        uint256 amount = (request.amount * request.acceptedRatio) / 1e18;
        lockedBalance -= amount;
        IERC20(collateralTokenAddress).safeTransfer(request.receiver, amount);
        emit WithdrawClaimedEvent(requestId, request.receiver);
    }

    function pause() external onlyRole(PAUSER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(UNPAUSER_ROLE) {
        _unpause();
    }
}  review this vulnerabilities and see which one of them are valid and correct and exist in the contract and prove that are correct with evidence     Implement input validation for the `acceptWithdrawRequest` function to ensure the `_paybackRatio` is not greater than 1e18.
    medium severity, medium likelihood

    The acceptWithdrawRequest function currently checks that the _paybackRatio provided is greater than or equal to the minimumPaybackRatio. However, there is no check to ensure that the _paybackRatio is not greater than 1e18, which represents 100% in the contract's ratio representation. Allowing a _paybackRatio greater than 1e18 could lead to incorrect calculations and potentially allow users to claim more than their entitled amount.

    Remediation Steps:
        Add a require statement in the acceptWithdrawRequest function to validate that _paybackRatio is less than or equal to 1e18.

    Pseudocode Example:

    function acceptWithdrawRequest(
        uint256 providedAmount,
        uint256[] memory _acceptedRequestIds,
        uint256 _paybackRatio
    ) external onlyRole(BALANCER_ROLE) whenNotPaused {
        // Existing code...
        require(
            _paybackRatio <= 1e18,
            "SolverVault: Payback ratio cannot exceed 100%"
        );
        // Rest of the function...
    }

    Why This Remediation Is Necessary: Ensuring that the _paybackRatio does not exceed 100% is critical to maintain the integrity of the withdrawal process and to prevent the contract from releasing more funds than it should. This validation protects against potential errors or exploits that could arise from an incorrect payback ratio.
    Ensure consistent use of SafeERC20 for all ERC20 interactions.
    medium severity, medium likelihood

    The contract inconsistently uses the SafeERC20 library from OpenZeppelin for ERC20 token interactions. While most functions use safeTransferFrom and safeTransfer, the depositToSymmio function uses the standard approve function, which does not include the same safety checks.

    Remediation Steps:
        Replace the approve function call in the depositToSymmio function with safeApprove from the SafeERC20 library.

    Pseudocode Example:

    function depositToSymmio(uint256 amount) external onlyRole(DEPOSITOR_ROLE) whenNotPaused {
        // Existing code...
        IERC20(collateralTokenAddress).safeApprove(address(symmio), amount);
        // Rest of the function...
    }

    Why This Remediation Is Necessary: Using the SafeERC20 library's safeApprove ensures that the approval is handled safely, accounting for potential issues like the ERC20 approve/transferFrom race condition. Consistent use of SafeERC20 functions across all ERC20 interactions enhances the contract's security and reduces the risk of unexpected behavior due to token contract quirks.
    Update the `setSymmioVaultTokenAddress` function to be public with role-based access control.
    medium severity, medium likelihood

    The setSymmioVaultTokenAddress function is currently marked as internal, which means it can only be called from within the contract itself. However, the contract may require the ability to update the address of the SymmioVaultToken in the future by an administrator or through a governance process. To allow for this, the function should be made public and protected with the onlyRole modifier to ensure that only authorized users can update it.

    Remediation Steps:
        Change the visibility of the setSymmioVaultTokenAddress function from internal to public.
        Add the onlyRole modifier to the function, with a role such as SETTER_ROLE to restrict access.

    Pseudocode Example:

    function setSymmioVaultTokenAddress(
        address _symmioVaultTokenAddress
    ) public onlyRole(SETTER_ROLE) {
        require(_symmioVaultTokenAddress != address(0), "SolverVault: Zero address");
        solverVaultTokenAddress = _symmioVaultTokenAddress;
        solverVaultTokenDecimals = SolverVaultToken(_symmioVaultTokenAddress)
            .decimals();
        require(
            solverVaultTokenDecimals <= 18,
            "SolverVault: SolverVaultToken decimals should be lower than 18"
        );
    }

    Why This Remediation Is Necessary: This change is necessary to provide flexibility for the contract administrators to update the SymmioVaultToken address when needed, while still maintaining strict access control to prevent unauthorized changes.
    Add event logging for role assignments and revocations.
    low severity, medium likelihood

    The contract uses the AccessControlEnumerableUpgradeable contract from OpenZeppelin, which provides role-based access control. However, there are no events emitted when roles are granted or revoked. Adding events for these actions would improve transparency and allow for better tracking and auditing of changes to role assignments.

    Remediation Steps:
        Define two new events, RoleGranted and RoleRevoked, to log these actions.
        Emit the RoleGranted event in the _grantRole function after the role is successfully granted.
        Emit the RoleRevoked event in the _revokeRole function after the role is successfully revoked.

    Pseudocode Example:

    // Define events
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    // Modify _grantRole function
    function _grantRole(bytes32 role, address account) internal override {
        super._grantRole(role, account);
        emit RoleGranted(role, account, msg.sender);
    }

    // Modify _revokeRole function
    function _revokeRole(bytes32 role, address account) internal override {
        super._revokeRole(role, account);
        emit RoleRevoked(role, account, msg.sender);
    }

    Why This Remediation Is Necessary: Logging role assignments and revocations is a best practice for smart contract security and governance. It provides a clear audit trail of who has been given access to critical functions within the contract and when access has been removed. This transparency is essential for maintaining trust and security in the contract's operations.