// File: contracts/Autolend/Manager/LendManagerInterface.sol



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

/**
 * @title MetaLend's manager error interface
 * @author MetaLend
 * @notice defines the errors for reporting during reverts
 * @dev use this with proxy and implementation to report errors
 */
interface LendManagerErrorInterface {
    /**
     * @notice Thrown when guarded function is called by non admin address
     * @param caller address of the invalid caller
     */
    error ErrCallerNotAdmin(address caller);

    /**
     * @notice Thrown when trying to set implementation of mediator to address which is not mediator
     * @param implementation the address of the invalid implementation
     */
    error ErrImplementationNotLendMediator(address implementation);

    /**
     * @notice Thrown when input param is an invalid number (e.g. does not fit constraints)
     * @param num the invalid number
     */
    error ErrInvalidNumber(uint256 num);

    /**
     * @notice Thrown when input param is an invalid address (such as address(0))
     * @param addr the invalid address
     */
    error ErrInvalidAddress(address addr);

    /**
     * @notice Thrown when user tries to create a mediator and it already exists
     * @param user the user with existing mediator
     * @param mediator the existing mediator address
     */
    error ErrMediatorExists(address user, address mediator);
}

/**
 * @title MetaLend's manager event interface
 * @author MetaLend
 * @notice defines the events emitted during interaction
 * @dev use this with proxy and implementation to emit events
 */
interface LendManagerEventInterface {
    /**
     * @notice emitted when a new mediator is created
     * @param user address of the account which creates the new mediator contract
     * @param mediator address of the mediator contract
     */
    event NewLendMediator(address indexed user, address indexed mediator);
    /**
     * @notice emitted when a global mediator implementation changes
     * @param previous the previous implementation address
     * @param next the new implementation address
     */
    event NewLendMediatorImplementation(address indexed previous, address indexed next);

    /**
     * @notice emitted when a royalties percentage changes
     * @param previous the previous percentage value
     * @param next the new percentage value
     */
    event NewRoyaltiesPercentage(uint256 indexed previous, uint256 indexed next);

    /**
     * @notice emitted when a new royalties receiver is set
     * @param previous the previous receiver
     * @param next the new receiver
     */
    event NewRoyaltiesReceiver(address indexed previous, address indexed next);

    /**
     * @notice emitted when a new offer signer is set
     * @param previous the previous offer signer
     * @param next the new offer signer
     */
    event NewOfferSigner(address indexed previous, address indexed next);
}

/**
 * @title MetaLend's manager function interface
 * @author MetaLend
 * @notice defines the functions usable in manager contracts
 * @dev use this with implementation contract to override functions
 */
interface LendManagerFunctionInterface {
    /**
     * @notice denominator to calculate percentage
     * @dev use this to calculate percentages
     * @return uint256 10000
     */
    function feeDenominator() external pure returns (uint256);

    /**
     * @notice creates a new mediator for user
     * @dev reverts if mediator already exists for given address
     */
    function createLendMediator() external;

    /**
     * @notice sets the new value for royalties percentage
     * @dev called only by MetaLend admin
     *  100 = 1%, based on {feeDenominator}
     * @param newPercentage the new percentage for royalties
     */
    function setRoyaltiesPercentage(uint256 newPercentage) external;

    /**
     * @notice sets the new royalties receiver
     * @dev called only by MetaLend admin
     * @param newReceiver address of the new receiver
     */
    function setRoyaltiesReceiver(address payable newReceiver) external;

    /**
     * @notice sets the new implementation for all mediator contracts
     * @dev called only by MetaLend admin
     * @param newImplementation address of the new implementation contract
     */
    function setLendMediatorImplementation(address newImplementation) external;

    /**
     * @notice sets the new address of the signer of offers
     * @dev called only by MetaLend admin
     * @param newSigner address of the new signer of offers
     */
    function setOfferSigner(address newSigner) external;

    /**
     * @notice returns a value modified by royalties precentage
     * @param value the uint256 value to modify
     * @return uint256 the result
     */
    function getValueByRoyaltiesPercentage(uint256 value) external view returns (uint256);
}

/**
 * @title MetaLend's manager proxy interface
 * @author MetaLend
 * @notice defines setImplementation proxy function and event and error
 * @dev use this interface with manager proxy
 */
interface LendManagerProxyInterface {
    /**
     * @notice Thrown when guarded function is called by non admin address
     * @param caller address of the invalid caller
     */
    error ErrCallerNotAdmin(address caller);

    /**
     * @notice Thrown when input param is an invalid address (such as address(0))
     * @param addr the invalid address
     */
    error ErrInvalidAddress(address addr);

    /**
     * @notice Thrown when trying to set implementation of manager to address which is not manager
     * @param implementation the address of the invalid implementation
     */
    error ErrImplementationNotLendManager(address implementation);

    /**
     * @notice Emitted when implementation is changed
     * @param previous address of the old implementation contract
     * @param next address of the new implementation contract
     */
    event NewImplementation(address indexed previous, address indexed next);

    /**
     * @notice emitted when an admin is changed
     * @param previous the previous admin
     * @param next the new admin
     */
    event NewAdmin(address indexed previous, address indexed next);

    /**
     * @notice sets the new implementation for the proxy contract
     * @dev should be available to be called only under specific cirmustances (such as an admin account)
     * @param newImplementation the address of the new implementation contract
     */
    function setImplementation(address newImplementation) external;

    /**
     * @notice sets the new admin
     * @dev called only by MetaLend admin
     * @param newAdmin the new admin of the protocol
     */
    function setAdmin(address newAdmin) external;
}

// File: contracts/Autolend/Manager/LendManagerProxy.sol



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

import {Proxy} from "../../lib/Proxy/Proxy.sol";
import {LendManagerProxyInterface} from "./LendManagerInterface.sol";
import {LendManagerProxyStorage} from "./LendManagerStorage.sol";
import {DetectContract} from "../../lib/utils/DetectContract.sol";

/**
 * @title MetaLend's LendManagerProxy Contract
 * @author MetaLend
 * @notice The main manager contract
 * @dev Proxies to implementation contract
 */
contract LendManagerProxy is Proxy, LendManagerProxyInterface, LendManagerProxyStorage {
    /**
     * @notice Construct a new staking manager
     * @param newImplementation The address of the implementation the contract delegates to
     */
    constructor(address newImplementation) {
        if (!DetectContract.isExistingContract(newImplementation)) {
            revert ErrImplementationNotLendManager(newImplementation);
        }
        try LendManagerProxyStorage(newImplementation).IS_LEND_MANAGER() returns (bool isLendManager) {
            if (!isLendManager) {
                revert ErrImplementationNotLendManager(newImplementation);
            }
        } catch {
            revert ErrImplementationNotLendManager(newImplementation);
        }
        implementation = newImplementation;
        admin = msg.sender;
        emit NewImplementation(address(0), newImplementation);
        emit NewAdmin(address(0), msg.sender);
    }

    /**
     * @inheritdoc LendManagerProxyInterface
     */
    function setImplementation(address newImplementation) external override {
        if (msg.sender != admin) revert ErrCallerNotAdmin(msg.sender);
        if (!DetectContract.isExistingContract(newImplementation)) {
            revert ErrImplementationNotLendManager(newImplementation);
        }
        try LendManagerProxyStorage(newImplementation).IS_LEND_MANAGER() returns (bool isLendManager) {
            if (!isLendManager) {
                revert ErrImplementationNotLendManager(newImplementation);
            }
        } catch {
            revert ErrImplementationNotLendManager(newImplementation);
        }
        emit NewImplementation(implementation, newImplementation);
        implementation = newImplementation;
    }

    /**
     * @inheritdoc LendManagerProxyInterface
     */
    function setAdmin(address newAdmin) external override {
        if (msg.sender != admin) revert ErrCallerNotAdmin(msg.sender);
        if (newAdmin == address(0)) revert ErrInvalidAddress(newAdmin);
        emit NewAdmin(admin, newAdmin);
        admin = newAdmin;
    }

    /**
     * @dev This function returns the current implementation contract
     * @return address of the implementation contract
     */
    function _implementation() internal view override returns (address) {
        return implementation;
    }
}

// File: contracts/Autolend/Manager/LendManagerStorage.sol



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

/**
 * @title MetaLend's Lend Manager proxy storage contract
 * @author MetaLend
 * @notice defines storage layout for proxy contract
 * @dev this should be inherited by `LendManager` so that implementation and proxy have same layouts
 */
abstract contract LendManagerProxyStorage {
    /// @notice Indicator that this is a LendManager contract (for inspection)
    /// @return bool true this is lend manager
    bool public constant IS_LEND_MANAGER = true;

    /// @notice implementaiton for proxy
    /// @return address of the implementation manager contract
    address public implementation;

    /// @notice MetaLend admin
    /// @return address of the MetaLend admin for this manager
    address public admin;
}

/**
 * @title MetaLend's Lend Manager storage contract
 * @author MetaLend
 * @notice defines storage layout for manager contracts
 * @dev use for both proxy and implementation contracts
 */
abstract contract LendManagerStorage {
    /// @notice global implementation for all mediator contracts
    /// @return address of the mediator implementation contract
    address public lendMediatorImplementation;

    /// @notice signer of offers
    /// @return address of the offer signer
    address public offerSigner;

    /// @notice if royalties are implemented, this is the receiver
    /// @return address of the royalties receiver
    address payable public royaltiesReceiver;

    /// @notice scale 0 <=> 10000, 1% = 100
    /// @return uint256 the percentage
    uint256 public royaltiesPercentage;

    /// @notice mapping from user address to own userLendMediator
    /// @return address of the mediator contract for given address of the user
    mapping(address => address) public userLendMediator;
}

// File: contracts/lib/Proxy/Proxy.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)

pragma solidity ^0.8.0;

/**
 * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
 * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
 * be specified by overriding the virtual {_implementation} function.
 *
 * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
 * different contract through the {_delegate} function.
 *
 * The success and return data of the delegated call will be returned back to the caller of the proxy.
 */
abstract contract Proxy {
    /**
     * @dev Delegates the current call to `implementation`.
     *
     * This function does not return to its internal call site, it will return directly to the external caller.
     */
    function _delegate(address implementation) internal virtual {
        assembly {
            // Copy msg.data. We take full control of memory in this inline assembly
            // block because it will not return to Solidity code. We overwrite the
            // Solidity scratch pad at memory position 0.
            calldatacopy(0, 0, calldatasize())

            // Call the implementation.
            // out and outsize are 0 because we don't know the size yet.
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

            // Copy the returned data.
            returndatacopy(0, 0, returndatasize())

            switch result
            // delegatecall returns 0 on error.
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }

    /**
     * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
     * and {_fallback} should delegate.
     */
    function _implementation() internal view virtual returns (address);

    /**
     * @dev Delegates the current call to the address returned by `_implementation()`.
     *
     * This function does not return to its internal call site, it will return directly to the external caller.
     */
    function _fallback() internal virtual {
        _beforeFallback();
        _delegate(_implementation());
    }

    /**
     * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
     * function in the contract matches the call data.
     */
    fallback() external payable virtual {
        _fallback();
    }

    /**
     * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
     * is empty.
     */
    receive() external payable virtual {
        _fallback();
    }

    /**
     * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
     * call, or as part of the Solidity `fallback` or `receive` functions.
     *
     * If overridden should call `super._beforeFallback()`.
     */
    function _beforeFallback() internal virtual {}
}

// File: contracts/lib/utils/DetectContract.sol



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

/**
 * @title MetaLend's DetectContract utility
 * @author MetaLend
 * @notice Contains function to detect if address is a contract
 * @dev Only works for already deployed contracts
 */
library DetectContract {
    /**
     * @notice function to detect if address is existing contract (already created)
     * @dev used for checking if caller is contract
     * @param addr the address of the account to check
     * @return bool if address is contract
     */
    function isExistingContract(address addr) internal view returns (bool) {
        uint256 contractSize;
        assembly {
            contractSize := extcodesize(addr)
        }
        if (contractSize == 0) {
            return false;
        } else {
            return true;
        }
    }
}