// File: src/Morphous.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.20;

import {Registry} from "src/Registry.sol";
import {Owned} from "solmate/auth/Owned.sol";
import {IDSProxy} from "src/interfaces/IDSProxy.sol";
import {Constants} from "src/libraries/Constants.sol";
import {IRegistry} from "src/interfaces/IRegistry.sol";

/// @title Morphous
/// @notice Allows interaction with the Morpho protocol for DSProxy or any delegateCall type contract.
/// @author @Mutative_
contract Morphous is Registry, Owned {
    /// @notice Address of this contract.
    IRegistry internal immutable _Registry;

    /// @notice Checks if timestamp is not expired
    /// @param deadline Timestamp to not be expired.
    modifier checkDeadline(uint256 deadline) {
        if (block.timestamp > deadline) revert Constants.DEADLINE_EXCEEDED();
        _;
    }

    constructor(address _owner) Owned(_owner) {
        _Registry = IRegistry(address(this));
    }

    ////////////////////////////////////////////////////////////////
    /// --- REGISTRY FUNCTIONS
    ///////////////////////////////////////////////////////////////

    /// @notice Sees {Registry-_getModule}.
    function getModule(bytes1 identifier) external view returns (address) {
        return _getModule(identifier);
    }

    /// @notice Sees {Registry-_setModule}.
    function setModule(bytes1 identifier, address module) external onlyOwner {
        _setModule(identifier, module);
    }

    /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
    /// @param deadline The time by which this function must be called before failing
    /// @param data The encoded function data for each of the calls to make to this contract
    /// @param argPos The position of the argument that should be updated with the previous call's return data
    /// @return results The results from each of the calls passed in via data
    function multicall(uint256 deadline, bytes[] calldata data, uint256[] calldata argPos)
        public
        payable
        checkDeadline(deadline)
        returns (bytes32[] memory results)
    {
        if (data.length != argPos.length) revert Constants.INVALID_LENGTH();

        results = new bytes32[](data.length);

        uint256 _argPos;
        uint256 _length = data.length;

        for (uint256 i = 0; i < _length;) {
            // Decode the first item of the array into a module identifier and the associated function data
            (bytes1 _moduleIdentifier, bytes memory _moduleData) = abi.decode(data[i], (bytes1, bytes));

            _argPos = argPos[i];

            if (i > 0 && _argPos > 0) {
                uint256 _argToUpdate = _argPos;
                bytes memory _updatedData = _moduleData;
                uint256 _previousCallResult = uint256(results[i - 1]);

                assembly {
                    mstore(add(_updatedData, add(_argToUpdate, 0x20)), _previousCallResult)
                }

                results[i] = IDSProxy(address(this)).execute(_Registry.getModule(_moduleIdentifier), _updatedData);
            } else {
                results[i] = IDSProxy(address(this)).execute(_Registry.getModule(_moduleIdentifier), _moduleData);
            }

            unchecked {
                ++i;
            }
        }
    }

    function version() external pure returns (string memory) {
        return "2.0.0";
    }

    receive() external payable {}
}

// File: src/Registry.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.20;

import {IRegistry} from "src/interfaces/IRegistry.sol";

/// @title Registry
/// @notice Registry for all the contracts in the system.
/// @dev This contract is used for the Multicall to know which module call.
abstract contract Registry is IRegistry {
    /// @notice Mapping to store the contract modules in the system.
    /// @dev The key is a bytes1 identifier and the value is the contract address.
    //// Can have up to 256 modules.
    mapping(bytes1 => address) internal modules;

    event ModuleSet(bytes1 indexed identifier, address indexed module);

    /// @notice Set a module for a given identifier.
    /// @param identifier The identifier of the module.
    /// @param module The address of the module.
    /// @dev This function can only be called by the owner of the contract.
    /// If the module is already set for the identifier, it will overwrite it.
    function _setModule(bytes1 identifier, address module) internal {
        modules[identifier] = module;

        // Emit the ModuleSet event after successfully setting the module.
        emit ModuleSet(identifier, module);
    }

    /// @notice Get the module address for a given identifier.
    /// @param identifier The identifier of the module.
    /// @return The address of the module.
    /// @dev This is a view function, meaning it only reads data and does not modify the contract state.
    function _getModule(bytes1 identifier) internal view returns (address) {
        return modules[identifier];
    }
}

// File: lib/solmate/src/auth/Owned.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event OwnershipTransferred(address indexed user, address indexed newOwner);

    /*//////////////////////////////////////////////////////////////
                            OWNERSHIP STORAGE
    //////////////////////////////////////////////////////////////*/

    address public owner;

    modifier onlyOwner() virtual {
        require(msg.sender == owner, "UNAUTHORIZED");

        _;
    }

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(address _owner) {
        owner = _owner;

        emit OwnershipTransferred(address(0), _owner);
    }

    /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferOwnership(address newOwner) public virtual onlyOwner {
        owner = newOwner;

        emit OwnershipTransferred(msg.sender, newOwner);
    }
}

// File: src/interfaces/IDSProxy.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.20;

interface IDSProxy {
    function owner() external view returns (address);
    function setCache(address _cacheAddr) external payable returns (bool);
    function execute(address _target, bytes memory _data) external payable returns (bytes32);
}

// File: src/libraries/Constants.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.20;

/// @notice Constants used in Morphous.
library Constants {
    /// @notice ETH address.
    address internal constant _ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    /// @notice sETH address.
    address internal constant _stETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;

    /// @notice wstETH address.
    address internal constant _wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;

    /// @notice cETH address.
    address internal constant _cETHER = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5;

    /// @notice WETH address.
    address internal constant _WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    /// @notice DAI address.
    address internal constant _DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;

    /// @notice The address of Morpho Aave markets.
    address public constant _MORPHO_AAVE = 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0;

    /// @notice The address of Morpho Aave markets.
    /// TODO: Replace this address with the correct one.
    address public constant _MORPHO_AAVE_V3 = 0x33333aea097c193e66081E930c33020272b33333;

    /// @notice Address of Aave Lending Pool contract.
    address internal constant _AAVE_LENDING_POOL = 0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9;

    /// @notice Address of Balancer contract.
    address internal constant _BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;

    /// @notice The address of Morpho Compound markets.
    address public constant _MORPHO_COMPOUND = 0x8888882f8f843896699869179fB6E4f7e3B58888;

    /// @notice Address of Factory Guard contract.
    address internal constant _FACTORY_GUARD_ADDRESS = 0x5a15566417e6C1c9546523066500bDDBc53F88C7;

    /////////////////////////////////////////////////////////////////
    /// --- ERRORS
    ////////////////////////////////////////////////////////////////

    /// @dev Error message when the caller is not allowed to call the function.
    error NOT_ALLOWED();

    /// @dev Error message when array length is invalid.
    error INVALID_LENGTH();

    /// @dev Error message when the caller is not allowed to call the function.
    error INVALID_LENDER();

    /// @dev Error message when the caller is not allowed to call the function.
    error INVALID_INITIATOR();

    /// @dev Error message when the market is invalid.
    error INVALID_MARKET();

    /// @dev Error message when the market is invalid.
    error INVALID_AGGREGATOR();

    /// @dev Error message when the deadline has passed.
    error DEADLINE_EXCEEDED();

    /// @dev Error message for when the amount of received tokens is less than the minimum amount.
    error NOT_ENOUGH_RECEIVED();
}

// File: src/interfaces/IRegistry.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.20;

interface IRegistry {
    function getModule(bytes1 identifier) external view returns (address);
    function setModule(bytes1 identifier, address module) external;
}