LendingStrategy

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/strategies/LendingStrategy.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { Sickle } from "contracts/Sickle.sol";
import { SickleFactory } from "contracts/SickleFactory.sol";
import { ConnectorRegistry } from "contracts/ConnectorRegistry.sol";
import { FlashloanStrategy } from "contracts/strategies/FlashloanStrategy.sol";
import { FlashloanInitiator } from
    "contracts/strategies/lending/FlashloanInitiator.sol";
import { TransferLib } from "contracts/libraries/TransferLib.sol";
import { ZapLib, ZapIn, ZapOut } from "contracts/libraries/ZapLib.sol";
import { FeesLib } from "contracts/libraries/FeesLib.sol";
import { IFarmConnector, Farm } from "contracts/interfaces/IFarmConnector.sol";
import { LendingStrategyFees } from
    "contracts/strategies/lending/LendingStructs.sol";
import { StrategyModule } from "contracts/modules/StrategyModule.sol";

contract LendingStrategy is FlashloanInitiator, StrategyModule {
    error SwapPathNotSupported(); // 0x6b46d10f
    error InputArgumentsMismatch(); // 0xe3814450

    struct Libraries {
        TransferLib transferLib;
        ZapLib zapLib;
        FeesLib feesLib;
    }

    struct DepositParams {
        address tokenIn;
        uint256 amountIn;
        ZapIn zap;
    }

    struct WithdrawParams {
        address tokenOut;
        ZapOut zap;
    }

    struct CompoundParams {
        Farm farm;
        bytes extraData;
        ZapIn zap;
    }

    TransferLib public immutable transferLib;
    ZapLib public immutable zapLib;
    FeesLib public immutable feesLib;

    address public immutable strategyAddress;

    constructor(
        SickleFactory factory,
        ConnectorRegistry connectorRegistry,
        FlashloanStrategy flashloanStrategy,
        Libraries memory libraries
    )
        FlashloanInitiator(flashloanStrategy)
        StrategyModule(factory, connectorRegistry)
    {
        transferLib = libraries.transferLib;
        zapLib = libraries.zapLib;
        feesLib = libraries.feesLib;
        strategyAddress = address(this);
    }

    /// FLASHLOAN FUNCTIONS ///

    /// @notice Deposit using a zap
    /// Deposit asset X, swap for asset A
    /// Flashloan asset A, or flashloan asset Z and swap for asset A
    /// Supply asset A, borrow asset A + fees, repay flashloan
    function deposit(
        DepositParams calldata depositParams,
        IncreaseParams calldata increaseParams,
        FlashloanParams calldata flashloanParams,
        address[] memory sweepTokens,
        address approved,
        bytes32 referralCode
    ) public payable flashloanParamCheck(flashloanParams) {
        Sickle sickle = getOrDeploySickle(msg.sender, approved, referralCode);

        address[] memory targets = new address[](1);
        bytes[] memory data = new bytes[](1);

        targets[0] = address(transferLib);
        data[0] = abi.encodeCall(
            TransferLib.transferTokenFromUser,
            (
                depositParams.tokenIn,
                depositParams.amountIn,
                strategyAddress,
                LendingStrategyFees.Deposit
            )
        );

        sickle.multicall{ value: msg.value }(targets, data);

        targets = new address[](1);
        data = new bytes[](1);

        targets[0] = address(zapLib);
        data[0] = abi.encodeCall(ZapLib.zapIn, (depositParams.zap));

        sickle.multicall(targets, data);

        flashloan_deposit(address(sickle), increaseParams, flashloanParams);

        if (sweepTokens.length > 0) {
            targets = new address[](1);
            data = new bytes[](1);

            targets[0] = address(transferLib);
            data[0] =
                abi.encodeCall(TransferLib.transferTokensToUser, (sweepTokens));

            sickle.multicall(targets, data);
        }
    }

    /// @notice Repay asset A loan with flashloan, withdraw collateral asset A
    function withdraw(
        DecreaseParams calldata decreaseParams,
        FlashloanParams calldata flashloanParams,
        WithdrawParams calldata withdrawParams,
        address[] memory sweepTokens
    ) public {
        Sickle sickle = getSickle(msg.sender);

        flashloan_withdraw(address(sickle), decreaseParams, flashloanParams);

        address[] memory targets = new address[](3);
        bytes[] memory data = new bytes[](3);

        targets[0] = address(zapLib);
        data[0] = abi.encodeCall(ZapLib.zapOut, (withdrawParams.zap));

        targets[1] = address(feesLib);
        data[1] = abi.encodeCall(
            FeesLib.chargeFee,
            (
                strategyAddress,
                LendingStrategyFees.Withdraw,
                withdrawParams.tokenOut,
                0
            )
        );

        targets[2] = address(transferLib);
        data[2] =
            abi.encodeCall(TransferLib.transferTokensToUser, (sweepTokens));

        sickle.multicall(targets, data);
    }

    /// @notice Claim accrued rewards, sell for loan token and leverage with
    /// flashloan
    function compound(
        CompoundParams calldata compoundParams,
        IncreaseParams calldata increaseParams,
        FlashloanParams calldata flashloanParams,
        address[] memory sweepTokens
    ) public {
        Sickle sickle = getSickle(msg.sender);

        // delegatecall callback function
        address[] memory targets = new address[](3);
        bytes[] memory data = new bytes[](3);

        targets[0] =
            connectorRegistry.connectorOf(compoundParams.farm.stakingContract);
        data[0] = abi.encodeCall(
            IFarmConnector.claim,
            (compoundParams.farm, compoundParams.extraData)
        );

        targets[1] = address(zapLib);
        data[1] = abi.encodeCall(ZapLib.zapIn, (compoundParams.zap));

        address flashloanToken = flashloanParams.flashloanAssets[0]
            == address(0)
            ? flashloanParams.flashloanAssets[1]
            : flashloanParams.flashloanAssets[0];
        targets[2] = address(feesLib);
        data[2] = abi.encodeCall(
            FeesLib.chargeFee,
            (strategyAddress, LendingStrategyFees.Compound, flashloanToken, 0)
        );

        sickle.multicall(targets, data);

        flashloan_deposit(address(sickle), increaseParams, flashloanParams);

        if (sweepTokens.length > 0) {
            targets = new address[](1);
            data = new bytes[](1);

            targets[0] = address(transferLib);
            data[0] =
                abi.encodeCall(TransferLib.transferTokensToUser, (sweepTokens));

            sickle.multicall(targets, data);
        }
    }
}
"
    },
    "contracts/Sickle.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { SickleStorage } from "contracts/base/SickleStorage.sol";
import { Multicall } from "contracts/base/Multicall.sol";
import { SickleRegistry } from "contracts/SickleRegistry.sol";

/// @title Sickle contract
/// @author vfat.tools
/// @notice Sickle facilitates farming and interactions with Masterchef
/// contracts
/// @dev Base contract inheriting from all the other "manager" contracts
contract Sickle is SickleStorage, Multicall {
    /// @notice Function to receive ETH
    receive() external payable { }

    /// @param sickleRegistry_ Address of the SickleRegistry contract
    constructor(
        SickleRegistry sickleRegistry_
    ) Multicall(sickleRegistry_) {
        _disableInitializers();
    }

    /// @param sickleOwner_ Address of the Sickle owner
    function initialize(
        address sickleOwner_,
        address approved_
    ) external initializer {
        SickleStorage._initializeSickleStorage(sickleOwner_, approved_);
    }

    /// INTERNALS ///

    function onERC721Received(
        address, // operator
        address, // from
        uint256, // tokenId
        bytes calldata // data
    ) external pure returns (bytes4) {
        return this.onERC721Received.selector;
    }

    function onERC1155Received(
        address, // operator
        address, // from
        uint256, // id
        uint256, // value
        bytes calldata // data
    ) external pure returns (bytes4) {
        return this.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address, // operator
        address, // from
        uint256[] calldata, // ids
        uint256[] calldata, // values
        bytes calldata // data
    ) external pure returns (bytes4) {
        return this.onERC1155BatchReceived.selector;
    }
}
"
    },
    "contracts/SickleFactory.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";

import { Sickle } from "contracts/Sickle.sol";
import { SickleRegistry } from "contracts/SickleRegistry.sol";
import { Admin } from "contracts/base/Admin.sol";

/// @title SickleFactory contract
/// @author vfat.tools
/// @notice Factory deploying new Sickle contracts
contract SickleFactory is Admin {
    /// EVENTS ///

    /// @notice Emitted when a new Sickle contract is deployed
    /// @param admin Address receiving the admin rights of the Sickle contract
    /// @param sickle Address of the newly deployed Sickle contract
    event Deploy(address indexed admin, address sickle);

    /// @notice Thrown when the caller is not whitelisted
    /// @param caller Address of the non-whitelisted caller
    error CallerNotWhitelisted(address caller); // 0x252c8273

    /// @notice Thrown when the factory is not active and a deploy is attempted
    error NotActive(); // 0x80cb55e2

    /// @notice Thrown when a Sickle contract is already deployed for a user
    error SickleAlreadyDeployed(); //0xf6782ef1

    /// STORAGE ///

    mapping(address => address) private _sickles;
    mapping(address => address) private _admins;
    mapping(address => bytes32) public _referralCodes;

    /// @notice Address of the SickleRegistry contract
    SickleRegistry public immutable registry;

    /// @notice Address of the Sickle implementation contract
    address public immutable implementation;

    /// @notice Address of the previous SickleFactory contract (if applicable)
    SickleFactory public immutable previousFactory;

    /// @notice Whether the factory is active (can deploy new Sickle contracts)
    bool public isActive = true;

    /// WRITE FUNCTIONS ///

    /// @param admin_ Address of the admin
    /// @param sickleRegistry_ Address of the SickleRegistry contract
    /// @param sickleImplementation_ Address of the Sickle implementation
    /// contract
    /// @param previousFactory_ Address of the previous SickleFactory contract
    /// if applicable
    constructor(
        address admin_,
        address sickleRegistry_,
        address sickleImplementation_,
        address previousFactory_
    ) Admin(admin_) {
        registry = SickleRegistry(sickleRegistry_);
        implementation = sickleImplementation_;
        previousFactory = SickleFactory(previousFactory_);
    }

    function setActive(
        bool active
    ) external onlyAdmin {
        isActive = active;
    }

    function _deploy(
        address admin,
        address approved,
        bytes32 referralCode
    ) internal returns (address sickle) {
        sickle = Clones.cloneDeterministic(
            implementation, keccak256(abi.encode(admin))
        );
        Sickle(payable(sickle)).initialize(admin, approved);
        _sickles[admin] = sickle;
        _admins[sickle] = admin;
        if (referralCode != bytes32(0)) {
            _referralCodes[sickle] = referralCode;
        }
        emit Deploy(admin, sickle);
    }

    function _getSickle(
        address admin
    ) internal returns (address sickle) {
        sickle = _sickles[admin];
        if (sickle != address(0)) {
            return sickle;
        }
        if (address(previousFactory) != address(0)) {
            sickle = previousFactory.sickles(admin);
            if (sickle != address(0)) {
                _sickles[admin] = sickle;
                _admins[sickle] = admin;
                _referralCodes[sickle] = previousFactory.referralCodes(sickle);
                return sickle;
            }
        }
    }

    /// @notice Predict the address of a Sickle contract for a specific user
    /// @param admin Address receiving the admin rights of the Sickle contract
    /// @return sickle Address of the predicted Sickle contract
    function predict(
        address admin
    ) external view returns (address) {
        bytes32 salt = keccak256(abi.encode(admin));
        return Clones.predictDeterministicAddress(implementation, salt);
    }

    /// @notice Returns the Sickle contract for a specific user
    /// @param admin Address that owns the Sickle contract
    /// @return sickle Address of the Sickle contract
    function sickles(
        address admin
    ) external view returns (address sickle) {
        sickle = _sickles[admin];
        if (sickle == address(0) && address(previousFactory) != address(0)) {
            sickle = previousFactory.sickles(admin);
        }
    }

    /// @notice Returns the admin for a specific Sickle contract
    /// @param sickle Address of the Sickle contract
    /// @return admin Address that owns the Sickle contract
    function admins(
        address sickle
    ) external view returns (address admin) {
        admin = _admins[sickle];
        if (admin == address(0) && address(previousFactory) != address(0)) {
            admin = previousFactory.admins(sickle);
        }
    }

    /// @notice Returns the referral code for a specific Sickle contract
    /// @param sickle Address of the Sickle contract
    /// @return referralCode Referral code for the user
    function referralCodes(
        address sickle
    ) external view returns (bytes32 referralCode) {
        referralCode = _referralCodes[sickle];
        if (
            referralCode == bytes32(0) && address(previousFactory) != address(0)
        ) {
            referralCode = previousFactory.referralCodes(sickle);
        }
    }

    /// @notice Deploys a new Sickle contract for a specific user, or returns
    /// the existing one if it exists
    /// @param admin Address receiving the admin rights of the Sickle contract
    /// @param approved Address approved to manage automation
    /// @param referralCode Referral code for the user
    /// @return sickle Address of the deployed Sickle contract
    function getOrDeploy(
        address admin,
        address approved,
        bytes32 referralCode
    ) external returns (address sickle) {
        if (!isActive) {
            revert NotActive();
        }
        if (!registry.isWhitelistedCaller(msg.sender)) {
            revert CallerNotWhitelisted(msg.sender);
        }
        if ((sickle = _getSickle(admin)) != address(0)) {
            return sickle;
        }
        return _deploy(admin, approved, referralCode);
    }

    /// @notice Deploys a new Sickle contract for a specific user
    /// @dev Sickle contracts are deployed with create2, the address of the
    /// admin is used as a salt, so all the Sickle addresses can be pre-computed
    /// and only 1 Sickle will exist per address
    /// @param approved Address approved to manage automation
    /// @param referralCode Referral code for the user
    /// @return sickle Address of the deployed Sickle contract
    function deploy(
        address approved,
        bytes32 referralCode
    ) external returns (address sickle) {
        if (!isActive) {
            revert NotActive();
        }
        if (_getSickle(msg.sender) != address(0)) {
            revert SickleAlreadyDeployed();
        }
        return _deploy(msg.sender, approved, referralCode);
    }
}
"
    },
    "contracts/ConnectorRegistry.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { Admin } from "contracts/base/Admin.sol";
import { TimelockAdmin } from "contracts/base/TimelockAdmin.sol";

error ConnectorNotRegistered(address target);
error CustomRegistryAlreadyRegistered();

interface ICustomConnectorRegistry {
    function connectorOf(
        address target
    ) external view returns (address);
}

contract ConnectorRegistry is Admin, TimelockAdmin {
    event ConnectorChanged(address target, address connector);
    event CustomRegistryAdded(address registry);
    event CustomRegistryRemoved(address registry);

    error ConnectorAlreadySet(address target);
    error ConnectorNotSet(address target);
    error ArrayLengthMismatch();

    ICustomConnectorRegistry[] public customRegistries;

    mapping(address target => address connector) private connectors_;

    constructor(
        address admin_,
        address timelockAdmin_
    ) Admin(admin_) TimelockAdmin(timelockAdmin_) { }

    /// Admin functions

    /// @notice Update connector addresses for a batch of targets.
    /// @dev Controls which connector contracts are used for the specified
    /// targets.
    /// @custom:access Restricted to protocol admin.
    function setConnectors(
        address[] calldata targets,
        address[] calldata connectors
    ) external onlyAdmin {
        if (targets.length != connectors.length) {
            revert ArrayLengthMismatch();
        }
        for (uint256 i; i != targets.length;) {
            if (connectors_[targets[i]] != address(0)) {
                revert ConnectorAlreadySet(targets[i]);
            }
            connectors_[targets[i]] = connectors[i];
            emit ConnectorChanged(targets[i], connectors[i]);

            unchecked {
                ++i;
            }
        }
    }

    function updateConnectors(
        address[] calldata targets,
        address[] calldata connectors
    ) external onlyTimelockAdmin {
        if (targets.length != connectors.length) {
            revert ArrayLengthMismatch();
        }
        for (uint256 i; i != targets.length;) {
            if (connectors_[targets[i]] == address(0)) {
                revert ConnectorNotSet(targets[i]);
            }
            connectors_[targets[i]] = connectors[i];
            emit ConnectorChanged(targets[i], connectors[i]);

            unchecked {
                ++i;
            }
        }
    }

    /// @notice Append an address to the custom registries list.
    /// @custom:access Restricted to protocol admin.
    function addCustomRegistry(
        ICustomConnectorRegistry registry
    ) external onlyAdmin {
        if (isCustomRegistry(registry)) {
            revert CustomRegistryAlreadyRegistered();
        }

        customRegistries.push(registry);
        emit CustomRegistryAdded(address(registry));
    }

    /// @notice Replace an address in the custom registries list.
    /// @custom:access Restricted to protocol admin.
    function updateCustomRegistry(
        uint256 index,
        ICustomConnectorRegistry newRegistry
    ) external onlyTimelockAdmin {
        ICustomConnectorRegistry oldRegistry = customRegistries[index];
        emit CustomRegistryRemoved(address(oldRegistry));
        customRegistries[index] = newRegistry;
        if (address(newRegistry) != address(0)) {
            emit CustomRegistryAdded(address(newRegistry));
        }
    }

    /// Public functions

    function connectorOf(
        address target
    ) external view returns (address) {
        address connector = _getConnector(target);

        if (connector != address(0)) {
            return connector;
        }

        revert ConnectorNotRegistered(target);
    }

    function hasConnector(
        address target
    ) external view returns (bool) {
        return _getConnector(target) != address(0);
    }

    function isCustomRegistry(
        ICustomConnectorRegistry registry
    ) public view returns (bool) {
        for (uint256 i; i != customRegistries.length;) {
            if (address(customRegistries[i]) == address(registry)) {
                return true;
            }
            unchecked {
                ++i;
            }
        }
        return false;
    }

    /// Internal functions

    function _getConnector(
        address target
    ) internal view returns (address) {
        address connector = connectors_[target];
        if (connector != address(0)) {
            return connector;
        }
        uint256 length = customRegistries.length;
        for (uint256 i; i != length;) {
            if (address(customRegistries[i]) != address(0)) {
                (bool success, bytes memory data) = address(customRegistries[i])
                    .staticcall(
                    abi.encodeWithSelector(
                        ICustomConnectorRegistry.connectorOf.selector, target
                    )
                );
                if (success && data.length == 32) {
                    address _connector = abi.decode(data, (address));
                    if (_connector != address(0)) {
                        return _connector;
                    }
                }
            }

            unchecked {
                ++i;
            }
        }

        return address(0);
    }
}
"
    },
    "contracts/strategies/FlashloanStrategy.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IMorpho } from "@morpho-blue/interfaces/IMorpho.sol";
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";

import { BPS_BASIS } from "contracts/base/Constants.sol";
import { Admin } from "contracts/base/Admin.sol";
import { ILendingPoolV2 } from
    "contracts/interfaces/external/flashloans/ILendingPoolV2.sol";
import { IPoolV3 } from "contracts/interfaces/external/flashloans/IPoolV3.sol";
import {
    IBalancerVault,
    IFlashLoanRecipient
} from "contracts/interfaces/external/flashloans/IBalancerVault.sol";
import { IUniswapV3Pool } from
    "contracts/interfaces/external/uniswap/IUniswapV3Pool.sol";
import { IUniswapV2Factory } from
    "contracts/interfaces/external/uniswap/IUniswapV2Factory.sol";
import { IUniswapV2Pair } from
    "contracts/interfaces/external/uniswap/IUniswapV2Pair.sol";
import { IUniswapV3Factory } from
    "contracts/interfaces/external/uniswap/IUniswapV3Factory.sol";
import { SickleRegistry } from "contracts/SickleRegistry.sol";
import { SickleFactory } from "contracts/SickleFactory.sol";
import { Multicall } from "contracts/base/Multicall.sol";

library FlashloanStrategyEvents {
    event SelectorLinked(bytes4 selector, address strategy);
}

/// @title FlashloanStrategy contract
/// @author vfat.tools
/// @notice Manages approved flashloan providers for the sickle and calls to and
/// from flashloan providers
contract FlashloanStrategy is Admin, IFlashLoanRecipient {
    /// ENUMS ///

    enum FlashloanStatus {
        FLASHLOAN_UNLOCKED,
        FLASHLOAN_INITIATED,
        CALLBACK_INITIATED
    }

    enum FlashloanProvider {
        AAVEV2,
        AAVEV3,
        BALANCER,
        UNIV2,
        UNIV3,
        MORPHO
    }

    /// ERRORS: Strategy ///

    error NotFlashloanStrategy(); // 0x9862416d
    error InvalidFlashloanData();
    error FlashloanNotInitiated();
    error FlashloanAlreadyInitiated();
    error FlashloanNotReset();
    error NotAnAssetPair();
    error InvalidAssetOrder();
    error UnauthorizedOperation();
    error SenderIsNotStrategy();
    error SenderIsNotAaveLendingPool();
    error SenderIsNotAaveV3LendingPool();
    error SenderIsNotBalancerVault();
    error SenderIsNotUniswapPool();
    error SenderIsNorMorpho();
    error NotSingleAsset();

    /// ERRORS: Registry ///

    /// @notice Thrown when array lengths don't match when registering flashloan
    /// operation types
    error ParamsMismatch();

    /// @notice Thrown when trying to override stub for an already registered
    /// flashloan operation
    error SelectorAlreadyLinked();

    /// STORAGE ///

    // Contract addresses
    SickleFactory public immutable sickleFactory;
    address public immutable aaveV2LendingPool;
    address public immutable aaveV3LendingPool;
    address public immutable balancerVault;
    address public immutable quickswapFactoryAddr;
    address public immutable uniswapV3FactoryAddr;
    address public immutable morpho;

    // Operational variables
    bytes32 flashloanDataHash;
    FlashloanStatus currentFlashloanStatus; // added reentrancy protection

    /// @notice Returns the stub contract address corresponding to the flashloan
    /// operation's function selector
    /// @dev if address(0) is returned, the flashloan operation does not exist
    /// or is not whitelisted
    /// param: flashloanOpSelector Function selector of the flashloan operation
    /// @return Address of the corresponding stub if it exists
    mapping(bytes4 => address) public whitelistedFlashloanOpsRegistry;

    /// WRITE FUNCTIONS ///

    /// @param admin_ Address of the admin
    /// @param whitelistedOpsSelectors_ Array of flashloan operation types to
    /// whitelist
    /// @param correspondingStrategies_ Array of strategy addresses where
    /// flashloan operations will be executed
    constructor(
        address admin_,
        SickleFactory sickleFactory_,
        address aaveV2LendingPool_,
        address aaveV3LendingPool_,
        address balancerVault_,
        address quickswapFactoryAddr_,
        address uniswapV3FactoryAddr_,
        address morpho_,
        bytes4[] memory whitelistedOpsSelectors_,
        address[] memory correspondingStrategies_
    ) Admin(admin_) {
        sickleFactory = sickleFactory_;
        aaveV2LendingPool = aaveV2LendingPool_;
        aaveV3LendingPool = aaveV3LendingPool_;
        balancerVault = balancerVault_;
        quickswapFactoryAddr = quickswapFactoryAddr_;
        uniswapV3FactoryAddr = uniswapV3FactoryAddr_;
        morpho = morpho_;

        currentFlashloanStatus = FlashloanStatus.FLASHLOAN_UNLOCKED;

        _setWhitelistedFlashloanOpsSelectors(
            whitelistedOpsSelectors_, correspondingStrategies_
        );
    }

    /// @notice Sets the approval status and corresponding stubs of several
    /// flashloan operation types
    /// @param whitelistedOpsSelectors Array of flashloan operation types to
    /// whitelist
    /// @param correspondingStrategies Array of strategy addresses where
    /// flashloan operations will be executed
    /// @custom:access Restricted to protocol admin.
    function setWhitelistedFlashloanOpsSelectors(
        bytes4[] memory whitelistedOpsSelectors,
        address[] memory correspondingStrategies
    ) external onlyAdmin {
        _setWhitelistedFlashloanOpsSelectors(
            whitelistedOpsSelectors, correspondingStrategies
        );
    }

    function _setWhitelistedFlashloanOpsSelectors(
        bytes4[] memory whitelistedOpsSelectors,
        address[] memory correspondingStrategies
    ) internal {
        if (whitelistedOpsSelectors.length != correspondingStrategies.length) {
            revert ParamsMismatch();
        }

        uint256 length = whitelistedOpsSelectors.length;
        for (uint256 i; i < length;) {
            if (
                whitelistedFlashloanOpsRegistry[whitelistedOpsSelectors[i]]
                    != address(0)
            ) {
                revert SelectorAlreadyLinked();
            }

            whitelistedFlashloanOpsRegistry[whitelistedOpsSelectors[i]] =
                correspondingStrategies[i];

            emit FlashloanStrategyEvents.SelectorLinked(
                whitelistedOpsSelectors[i], correspondingStrategies[i]
            );

            unchecked {
                ++i;
            }
        }
    }

    modifier callbackSafetyCheck(
        bytes memory params
    ) {
        bytes32 hashCheck = keccak256(params);
        if (hashCheck != flashloanDataHash) {
            revert InvalidFlashloanData();
        }
        if (currentFlashloanStatus != FlashloanStatus.FLASHLOAN_INITIATED) {
            revert FlashloanNotInitiated();
        }

        _;

        // resetting currentFlashloanStatus to FLASHLOAN_UNLOCKED after all
        // operations are finished
        currentFlashloanStatus = FlashloanStatus.FLASHLOAN_UNLOCKED;
    }

    /// @notice Routing function that initiates the flashloan at the indicated
    /// provider
    /// @param flashloanProvider Bytes blob containing the name of the flashloan
    /// provider and optionally the fee tier
    /// @param assets Array of contract addresses of the tokens being borrowed
    /// @param amounts Array of amounts of tokens being borrowed
    /// @param params Bytes blob contains all necessary parameters for the
    /// post-flashloan function
    /// @dev if using a uniswap call, the assets array must contain the token0
    /// and token1 of the called pool in the correct order
    function initiateFlashloan(
        address sickleAddress,
        bytes calldata flashloanProvider,
        address[] calldata assets,
        uint256[] calldata amounts,
        bytes calldata params
    ) external {
        if (assets.length != amounts.length) {
            revert SickleRegistry.ArrayLengthMismatch();
        }
        if (currentFlashloanStatus != FlashloanStatus.FLASHLOAN_UNLOCKED) {
            revert FlashloanAlreadyInitiated();
        }
        SickleRegistry registry = sickleFactory.registry();
        if (registry.isWhitelistedCaller(msg.sender) == false) {
            revert SenderIsNotStrategy();
        }

        // setting the currentFlashloanStatus to FLASHLOAN_INITIATED to avoid
        // reentrancy and allow callbacks only
        currentFlashloanStatus = FlashloanStatus.FLASHLOAN_INITIATED;

        (FlashloanProvider providerType, uint24 providerFee) =
            abi.decode(flashloanProvider, (FlashloanProvider, uint24));

        if (providerType == FlashloanProvider.AAVEV2) {
            uint256[] memory modes = new uint256[](assets.length);
            // mode 0 = no debt incurred, everything repaid at end of flashloan
            bytes memory aaveV2params = abi.encode(sickleAddress, params);
            // storing the hash of the callback data for safety checks
            flashloanDataHash = keccak256(aaveV2params);
            ILendingPoolV2(aaveV2LendingPool).flashLoan(
                address(this), // flashloan receiver
                assets,
                amounts,
                modes,
                address(0), // onBehalfOf variable
                aaveV2params,
                0 // referral code
            );
        } else if (providerType == FlashloanProvider.AAVEV3) {
            bytes memory aaveV3params = abi.encode(sickleAddress, params);
            // storing the hash of the callback data for safety checks
            flashloanDataHash = keccak256(aaveV3params);
            if (assets.length == 1) {
                IPoolV3(aaveV3LendingPool).flashLoanSimple(
                    address(this), // flashloan receiver
                    assets[0],
                    amounts[0],
                    aaveV3params,
                    0 // referral code
                );
            } else {
                uint256[] memory modes = new uint256[](assets.length);
                // mode 0 = no debt incurred, everything repaid at end of
                // flashloan
                IPoolV3(aaveV3LendingPool).flashLoan(
                    address(this), // flashloan receiver
                    assets,
                    amounts,
                    modes,
                    address(0),
                    aaveV3params,
                    0 // referral code
                );
            }
        } else if (providerType == FlashloanProvider.BALANCER) {
            bytes memory balancerParams = abi.encode(sickleAddress, params);
            // storing the hash of the callback data for safety checks
            flashloanDataHash = keccak256(balancerParams);
            IBalancerVault(balancerVault).flashLoan(
                this, assets, amounts, balancerParams
            );
        } else if (providerType == FlashloanProvider.UNIV2) {
            if (assets.length != 2) revert NotAnAssetPair();
            if (assets[0] > assets[1]) revert InvalidAssetOrder();
            address poolAddress = IUniswapV2Factory(quickswapFactoryAddr)
                .getPair(assets[0], assets[1]);
            (,, uint256[] memory premiums,) =
                abi.decode(params[4:], (address[], uint256[], uint256[], bytes));
            bytes memory uniswapFlashParams = abi.encode(
                sickleAddress, poolAddress, assets, amounts, premiums, params
            );
            // storing the hash of the callback data for safety checks
            flashloanDataHash = keccak256(uniswapFlashParams);
            IUniswapV2Pair(poolAddress).swap(
                amounts[0], amounts[1], address(this), uniswapFlashParams
            );
        } else if (providerType == FlashloanProvider.UNIV3) {
            if (assets.length != 2) revert NotAnAssetPair();
            if (assets[0] > assets[1]) revert InvalidAssetOrder();
            address poolAddress = IUniswapV3Factory(uniswapV3FactoryAddr)
                .getPool(assets[0], assets[1], providerFee);
            (,, uint256[] memory premiums,) =
                abi.decode(params[4:], (address[], uint256[], uint256[], bytes));
            bytes memory uniswapFlashParams = abi.encode(
                sickleAddress, poolAddress, assets, amounts, premiums, params
            );
            // storing the hash of the callback data for safety checks
            flashloanDataHash = keccak256(uniswapFlashParams);
            IUniswapV3Pool(poolAddress).flash(
                address(this), amounts[0], amounts[1], uniswapFlashParams
            );
        } else {
            if (assets.length != 1) revert NotSingleAsset();
            bytes memory morphoParams =
                abi.encode(sickleAddress, assets[0], params);
            // storing the hash of the callback data for safety checks
            flashloanDataHash = keccak256(morphoParams);
            IMorpho(morpho).flashLoan(assets[0], amounts[0], morphoParams);
        }

        // resetting the flashloanDataHash variable to zero bytes at the end of
        // conversion operation
        flashloanDataHash = bytes32(0);

        if (currentFlashloanStatus != FlashloanStatus.FLASHLOAN_UNLOCKED) {
            revert FlashloanNotReset();
        }
    }

    /// @notice Callback function for flashloans coming from Aave's LendingPool
    /// contract (single-asset V3)
    /// @param asset Contract address of the token being borrowed
    /// @param amount Amount of tokens being borrowed
    /// @param premium Premium amount charged by the Aave protocol
    /// @param paramsInput Bytes blob contains all necessary parameters for the
    /// post-flashloan function
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address, // initiator
        bytes calldata paramsInput
    ) external callbackSafetyCheck(paramsInput) returns (bool) {
        if (msg.sender != aaveV3LendingPool) {
            revert SenderIsNotAaveV3LendingPool();
        }

        (address sickleAddress, bytes memory callback) =
            abi.decode(paramsInput, (address, bytes));

        address targetStrategy = _checkSelector(callback);

        // setting the currentFlashloanStatus to CALLBACK_INITIATED to avoid
        // reentrancy
        currentFlashloanStatus = FlashloanStatus.CALLBACK_INITIATED;

        // transfer borrowed assets to the sickle
        SafeTransferLib.safeTransfer(asset, sickleAddress, amount);

        // execute post-flashloan strategy function via a delegatecall from the
        // sickle
        {
            address[] memory targetStrategyArray = new address[](1);
            targetStrategyArray[0] = targetStrategy;
            bytes[] memory calldataArray = new bytes[](1);
            calldataArray[0] = callback;

            Multicall(sickleAddress).multicall(
                targetStrategyArray, calldataArray
            );
        }

        // approving borrowed funds + premiums to be repaid to the Lending Pool
        // contract
        SafeTransferLib.safeApprove(asset, aaveV3LendingPool, amount + premium);
        return true;

        // debt is automatically repaid
    }

    function executeOperation(
        address[] memory assets,
        uint256[] memory amounts,
        uint256[] memory premiums,
        address, // initiator
        bytes memory paramsInput
    ) external callbackSafetyCheck(paramsInput) returns (bool) {
        if (assets.length != amounts.length || assets.length != premiums.length)
        {
            revert SickleRegistry.ArrayLengthMismatch();
        }

        if (msg.sender != aaveV2LendingPool && msg.sender != aaveV3LendingPool)
        {
            revert SenderIsNotAaveLendingPool();
        }

        (address sickleAddress, bytes memory callback) =
            abi.decode(paramsInput, (address, bytes));

        address targetStrategy = _checkSelector(callback);

        // setting the currentFlashloanStatus to CALLBACK_INITIATED to avoid
        // reentrancy
        currentFlashloanStatus = FlashloanStatus.CALLBACK_INITIATED;

        // transfer borrowed assets to the sickle
        _transferAssets(sickleAddress, assets, amounts);

        // execute post-flashloan strategy function via a delegatecall from the
        // sickle
        {
            address[] memory targetStrategyArray = new address[](1);
            targetStrategyArray[0] = targetStrategy;
            bytes[] memory calldataArray = new bytes[](1);
            calldataArray[0] = callback;

            Multicall(sickleAddress).multicall(
                targetStrategyArray, calldataArray
            );
        }

        // approving borrowed funds + premiums to be repaid to the Lending Pool
        // contract
        _approveAssets(msg.sender, assets, amounts, premiums);

        return true;

        // debt is automatically repaid
    }

    /// @notice Callback function for flashloans coming from Balancer's Vault
    /// contract
    /// @param tokens Array of contract addresses of the tokens being borrowed
    /// @param amounts Array of amounts of tokens being borrowed
    /// @param premiums Array of premium amounts charged by the Balancer
    /// protocol
    /// @param paramsInput Bytes blob contains all necessary parameters for the
    /// post-flashloan function
    function receiveFlashLoan(
        IERC20[] calldata tokens,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        bytes memory paramsInput
    ) external callbackSafetyCheck(paramsInput) {
        if (tokens.length != amounts.length || tokens.length != premiums.length)
        {
            revert SickleRegistry.ArrayLengthMismatch();
        }

        if (msg.sender != balancerVault) {
            revert SenderIsNotBalancerVault();
        }

        (address sickleAddress, bytes memory params) =
            abi.decode(paramsInput, (address, bytes));

        address targetStrategy = _checkSelector(params);

        // setting the currentFlashloanStatus to CALLBACK_INITIATED to avoid
        // reentrancy
        currentFlashloanStatus = FlashloanStatus.CALLBACK_INITIATED;

        // convert data and transfer borrowed assets to the sickle
        _transferAssets(sickleAddress, tokens, amounts);

        // execute post-flashloan strategy function via a delegatecall from the
        // sickle
        {
            address[] memory targetStrategyArray = new address[](1);
            targetStrategyArray[0] = targetStrategy;
            bytes[] memory calldataArray = new bytes[](1);
            calldataArray[0] = params;

            Multicall(sickleAddress).multicall(
                targetStrategyArray, calldataArray
            );
        }

        // transferring borrowed funds + premiums to be repaid to the Balancer
        // Vault contract
        _transferAssets(balancerVault, tokens, amounts, premiums);
    }

    /// @notice Callback function for flashloans coming from UniswapV2 pairs
    /// @param params Bytes blob contains all necessary parameters for the
    /// post-flashloan function
    function uniswapV2Call(
        address, //sender
        uint256, // amount0
        uint256, // amount1
        bytes calldata params
    ) external callbackSafetyCheck(params) {
        (
            address sickleAddress,
            address poolAddress,
            address[] memory assets,
            uint256[] memory amounts,
            uint256[] memory premiums,
            bytes memory callback
        ) = abi.decode(
            params, (address, address, address[], uint256[], uint256[], bytes)
        );

        _uniswapCallback(
            sickleAddress, poolAddress, assets, amounts, premiums, callback
        );
    }

    /// @notice Callback function for flashloans coming from UniswapV3 pools
    /// @param params Bytes blob contains all necessary parameters for the
    /// post-flashloan function
    function uniswapV3FlashCallback(
        uint256, // fee0
        uint256, // fee1
        bytes calldata params
    ) external callbackSafetyCheck(params) {
        (
            address sickleAddress,
            address poolAddress,
            address[] memory assets,
            uint256[] memory amounts,
            uint256[] memory premiums,
            bytes memory callback
        ) = abi.decode(
            params, (address, address, address[], uint256[], uint256[], bytes)
        );

        _uniswapCallback(
            sickleAddress, poolAddress, assets, amounts, premiums, callback
        );
    }

    /// @notice Callback function for flashloans coming from PancakeV3 pools
    /// @param params Bytes blob contains all necessary parameters for the
    /// post-flashloan function
    function pancakeV3FlashCallback(
        uint256, // fee0
        uint256, // fee1
        bytes calldata params
    ) external callbackSafetyCheck(params) {
        (
            address sickleAddress,
            address poolAddress,
            address[] memory assets,
            uint256[] memory amounts,
            uint256[] memory premiums,
            bytes memory callback
        ) = abi.decode(
            params, (address, address, address[], uint256[], uint256[], bytes)
        );

        _uniswapCallback(
            sickleAddress, poolAddress, assets, amounts, premiums, callback
        );
    }

    function onMorphoFlashLoan(
        uint256 assets,
        bytes calldata data
    ) external callbackSafetyCheck(data) {
        (address sickleAddress, address token, bytes memory callback) =
            abi.decode(data, (address, address, bytes));

        // Check that we're being called from Morpho
        if (msg.sender != morpho) {
            revert SenderIsNorMorpho();
        }

        address targetStrategy = _checkSelector(callback);

        // Set the currentFlashloanStatus to CALLBACK_INITIATED to avoid
        // reentrancy
        currentFlashloanStatus = FlashloanStatus.CALLBACK_INITIATED;

        // Transfer borrowed assets to the sickle
        SafeTransferLib.safeTransfer(token, sickleAddress, assets);

        // Execute post-flashloan strategy function via a delegatecall from the
        // sickle
        {
            address[] memory targetStrategyArray = new address[](1);
            targetStrategyArray[0] = targetStrategy;
            bytes[] memory calldataArray = new bytes[](1);
            calldataArray[0] = callback;

            Multicall(sickleAddress).multicall(
                targetStrategyArray, calldataArray
            );
        }

        SafeTransferLib.safeApprove(token, morpho, assets);
    }

    /// VIEW FUNCTIONS ///

    /// @notice Returns the premium amounts on borrowed amounts based on the
    /// flashloan provider
    /// @param flashloanProvider Bytes blob indicating the selected flashloan
    /// provider and optionally the fee tier
    /// @param amounts Array of amounts of tokens being borrowed
    /// @return Array of premium amounts corresponding to each borrowed amount
    function calculatePremiums(
        bytes calldata flashloanProvider,
        uint256[] calldata amounts
    ) public view returns (uint256[] memory) {
        uint256[] memory premiums = new uint256[](amounts.length);

        (FlashloanProvider providerType, uint24 providerFee) =
            abi.decode(flashloanProvider, (FlashloanProvider, uint24));

        if (providerType == FlashloanProvider.AAVEV2) {
            uint256 aaveV2FlashloanPremiumInBasisPoints =
                ILendingPoolV2(aaveV2LendingPool).FLASHLOAN_PREMIUM_TOTAL();
            uint256 length = amounts.length;
            for (uint256 i; i < length;) {
                if (amounts[i] > 0 && aaveV2FlashloanPremiumInBasisPoints > 0) {
                    premiums[i] = // Rounding down
                    (
                        (amounts[i] * aaveV2FlashloanPremiumInBasisPoints)
                            / BPS_BASIS
                    );
                }

                unchecked {
                    ++i;
                }
            }
        } else if (providerType == FlashloanProvider.AAVEV3) {
            uint256 aaveV3FlashloanPremiumInBasisPoints =
                IPoolV3(aaveV3LendingPool).FLASHLOAN_PREMIUM_TOTAL();
            uint256 length = amounts.length;
            for (uint256 i; i < length;) {
                if (amounts[i] > 0 && aaveV3FlashloanPremiumInBasisPoints > 0) {
                    premiums[i] = // Rounding to nearest neighbor
                    (
                        (
                            (
                                amounts[i] * aaveV3FlashloanPremiumInBasisPoints
                                    + (BPS_BASIS / 2)
                            ) / BPS_BASIS
                        )
                    );
                }

                unchecked {
                    ++i;
                }
            }
        } else if (providerType == FlashloanProvider.BALANCER) {
            uint256 balancerFlashLoanFeePercentage = IBalancerVault(
                balancerVault
            ).getProtocolFeesCollector().getFlashLoanFeePercentage();
            uint256 length = amounts.length;
            for (uint256 i; i < length;) {
                // reproducing the mulUp() function from Balancer's FixedPoint
                // helper library
                if (balancerFlashLoanFeePercentage != 0 && amounts[i] != 0) {
                    premiums[i] = (
                        (amounts[i] * balancerFlashLoanFeePercentage - 1) / 1e18
                    ) + 1;
                }

                unchecked {
                    ++i;
                }
            }
        } else if (providerType == FlashloanProvider.UNIV2) {
            uint256 length = amounts.length;
            for (uint256 i; i < length;) {
                if (amounts[i] > 0) {
                    premiums[i] = ((amounts[i] * 3 - 1) / 997) + 1;
                }

                unchecked {
                    ++i;
                }
            }
        } else if (providerType == FlashloanProvider.UNIV3) {
            uint256 length = amounts.length;
            for (uint256 i; i < length;) {
                if (amounts[i] > 0 && providerFee > 0) {
                    premiums[i] =
                        ((amounts[i] * providerFee - 1) / BPS_BASIS / 100) + 1;
                    // hundredths of basis points
                }

                unchecked {
                    ++i;
                }
            }
        }

        return premiums;
    }

    /// @notice Returns the first four bytes of a given bytes blob
    /// @param params Bytes blob from which we want to extract the first four
    /// bytes corresponding to the function selector
    /// @return selector Bytes4 containing the function selector
    /// @dev helper function for uniswapV2Call and uniswapV3Call functions where
    /// the function selector is not at the beginning of the bytes parameter in
    /// the callback
    function extractSelector(
        bytes memory params
    ) public pure returns (bytes4 selector) {
        selector = bytes4(params);
    }

    /// INTERNALS ///

    function _transferAssets(
        address to,
        address[] memory assets,
        uint256[] memory amounts
    ) internal {
        uint256[] memory premiums = new uint256[](assets.length);
        _transferAssets(to, assets, amounts, premiums);
    }

    function _transferAssets(
        address to,
        IERC20[] memory assets,
        uint256[] memory amounts
    ) internal {
        uint256[] memory premiums = new uint256[](assets.length);
        _transferAssets(to, assets, amounts, premiums);
    }

    function _transferAssets(
        address to,
        address[] memory assets,
        uint256[] memory amounts,
        uint256[] memory premiums
    ) internal {
        uint256 length = assets.length;
        for (uint256 i; i < length;) {
            uint256 total = amounts[i] + premiums[i];
            if (total > 0) {
                SafeTransferLib.safeTransfer(assets[i], to, total);
            }

            unchecked {
                ++i;
            }
        }
    }

    function _transferAssets(
        address to,
        IERC20[] memory assets,
        uint256[] memory amounts,
        uint256[] memory premiums
    ) internal {
        uint256 length = assets.length;
        for (uint256 i; i < length;) {
            uint256 total = amounts[i] + premiums[i];
            if (total > 0) {
                SafeTransferLib.safeTransfer(address(assets[i]), to, total);
            }

            unchecked {
                ++i;
            }
        }
    }

    function _approveAssets(
        address to,
        address[] memory assets,
        uint256[] memory amounts,
        uint256[] memory premiums
    ) internal {
        uint256 length = assets.length;
        for (uint256 i; i < length;) {
            uint256 total = amounts[i] + premiums[i];
            if (total > 0) {
                SafeTransferLib.safeApprove(assets[i], to, total);
            }

            unchecked {
                ++i;
            }
        }
    }

    function _checkSelector(
        bytes memory params
    ) internal view returns (address) {
        // extracting function selector from the callback parameters
        bytes4 flashloanOpSelector = extractSelector(params);

        // fetching the corresponding strategy from the registry
        address targetStrategy =
            whitelistedFlashloanOpsRegistry[flashloanOpSelector];

        if (targetStrategy == address(0)) {
            revert UnauthorizedOperation();
        }

        return targetStrategy;
    }

    function _uniswapCallback(
        address sickleAddress,
        address poolAddress,
        address[] memory assets,
        uint256[] memory amounts,
        uint256[] memory premiums,
        bytes memory callback
    ) internal {
        if (assets.length != amounts.length || assets.length != premiums.length)
        {
            revert SickleRegistry.ArrayLengthMismatch();
        }

        // Check that we're being called from a Uniswap pool
        if (msg.sender != poolAddress) {
            revert SenderIsNotUniswapPool();
        }

        address targetStrategy = _checkSelector(callback);

        // Set the currentFlashloanStatus to CALLBACK_INITIATED to avoid
        // reentrancy
        currentFlashloanStatus = FlashloanStatus.CALLBACK_INITIATED;

        // Transfer borrowed assets to the sickle
        _transferAssets(sickleAddress, assets, amounts);

        // Execute post-flashloan strategy function via a delegatecall from the
        // sickle
        {
            address[] memory targetStrategyArray = new address[](1);
            targetStrategyArray[0] = targetStrategy;
            bytes[] memory calldataArray = new bytes[](1);
            calldataArray[0] = callback;

            Multicall(sickleAddress).multicall(
                targetStrategyArray, calldataArray
            );
        }

        // Transfer borrowed funds + premiums to be repaid to the Uniswap pool
        // contract
        _transferAssets(poolAddress, assets, amounts, premiums);
    }
}
"
    },
    "contracts/strategies/lending/FlashloanInitiator.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { LendingStructs } from "contracts/strategies/lending/LendingStructs.sol";
import { IFlashloanCallback } from "contracts/interfaces/IFlashloanCallback.sol";
import { FlashloanStrategy } from "contracts/strategies/FlashloanStrategy.sol";

abstract contract FlashloanInitiator is LendingStructs {
    error ArrayLengthMismatch();

    modifier flashloanParamCheck(
        FlashloanParams calldata flashloanParams
    ) {
        if (
            flashloanParams.flashloanAssets.length
                != flashloanParams.flashloanAmounts.length
        ) {
            revert ArrayLengthMismatch();
        }
        _;
    }

    FlashloanStrategy public immutable flashloanStrategy;

    constructor(
        FlashloanStrategy flashloanStrategy_
    ) {
        flashloanStrategy = flashloanStrategy_;
    }

    /// Internal functions ///

    function flashloan_deposit(
        address sickleAddress,
        IncreaseParams calldata increaseParams,
        FlashloanParams calldata flashloanParams
    ) internal {
        uint256[] memory premiums = flashloanStrategy.calculatePremiums(
            flashloanParams.flashloanProvider, flashloanParams.flashloanAmounts
        );

        bytes memory callback = abi.encodeCall(
            IFlashloanCallback.flashloanDepositCallback,
            (
                flashloanParams.flashloanAssets,
                flashloanParams.flashloanAmounts,
                premiums,
                abi.encode(increaseParams)
            )
        );
        flashloanStrategy.initiateFlashloan(
            sickleAddress,
            flashloanParams.flashloanProvider,
            flashloanParams.flashloanAssets,
            flashloanParams.flashloanAmounts,
            callback
        );
    }

    function flashloan_withdraw(
        address sickleAddress,
        DecreaseParams calldata decreaseParams,
        FlashloanParams calldata flashLoanParams
    ) internal {
        // calculate expected premiums on flashloan amount
        uint256[] memory premiums = flashloanStrategy.calculatePremiums(
            flashLoanParams.flashloanProvider, flashLoanParams.flashloanAmounts
        );

        bytes memory encoded = abi.encodeCall(
            IFlashloanCallback.flashloanWithdrawCallback,
            (
                flashLoanParams.flashloanAssets,
                flashLoanParams.flashloanAmounts,
                premiums,
                abi.encode(decreaseParams)
            )
        );

        flashloanStrategy.initiateFlashloan(
            sickleAddress,
            flashLoanParams.flashloanProvider,
            flashLoanParams.flashloanAssets,
            flashLoanParams.flashloanAmounts,
            encoded
        );
    }
}
"
    },
    "contracts/libraries/TransferLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { MsgValueModule } from "contracts/modules/MsgValueModule.sol";
import { WETH } from "lib/solmate/src/tokens/WETH.sol";
import { Sickle } from "contracts/Sickle.sol";
import { SafeTransferLib } from "lib/solmate/src/utils/SafeTransferLib.sol";
import { IERC20 } from
    "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import { IFeesLib } from "contracts/interfaces/libraries/IFeesLib.sol";
import { DelegateModule } from "contracts/modules/DelegateModule.sol";
import { ITransferLib } from "contracts/interfaces/libraries/ITransferLib.sol";

contract TransferLib is MsgValueModule, DelegateModule, ITransferLib {
    address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address public constant UNISWAP_ETH =
        0x0000000000000000000000000000000000000000;
    WETH public immutable weth;

    IFeesLib public immutable feesLib;

    constructor(IFeesLib feesLib_, WETH weth_) {
        feesLib = feesLib_;
        weth = weth_;
    }

    /// @dev Transfers the balance of {token} from the contract to the
    /// sickle owner
    /// @param token Address of the token to transfer
    function transferTokenToUser(
        address token
    ) public payable {
        address recipient = Sickle(payable(address(this))).owner();
        if (token == ETH || token == UNISWAP_ETH) {
            uint256 wethBalance = weth.balanceOf(address(this));
            if (wethBalance > 0) {
                weth.withdraw(wethBalance);
            }

            if (address(this).balance > 0) {
                SafeTransferLib.safeTransferETH(
                    recipient, address(this).balance
                );
            }
        } else {
            uint256 balance = IERC20(token).balanceOf(address(this));
            if (balance > 0) {
                SafeTransferLib.safeTransfer(token, recipient, balance);
            }
        }
    }

    /// @dev Transfers all balances of {tokens} and/or ETH from the contract
    /// to the sickle owner
    /// @param tokens An array of token addresses
    function transferTokensToUser(
        address[] memory tokens
    ) external payable checkTransfersTo(tokens) {
        for (uint256 i; i != tokens.length;) {
            transferTokenToUser(tokens[i]);

            unchecked {
                i++;
            }
        }
    }

    /// @dev Transfers {amountIn} of {tokenIn} from the user to the Sickle
    /// contract, charging the fees and converting the amount to WETH if
    /// necessary
    /// @param tokenIn Address of the token to transfer
    /// @param amountIn Amount of the token to transfer
    /// @param strategy Address of the caller strategy
    /// @param feeSelector Selector of the caller function
    function transferTokenFromUser(
        address tokenIn,
        uint256 amountIn,
        address strategy,
        bytes4 feeSelector
    ) public payable checkTransferFrom(tokenIn, amountIn) {
        _checkMsgValue(amountIn, tokenIn == ETH || tokenIn == UNISWAP_ETH);

        _transferTokenFromUser(tokenIn, amountIn, strategy, feeSelector);
    }

    /// @dev Transfers {amountIn} of {tokenIn} from the user to the Sickle
    /// contract, charging the fees and converting the amount to WETH if
    /// necessary
    /// @param tokensIn Addresses of the tokens to transfer
    /// @param amountsIn Amounts of the tokens to transfer
    /// @param strategy Address of the caller strategy
    /// @param feeSelector Selector of the caller function
    function transferTokensFromUser(
        address[] memory tokensIn,
        uint256[] memory amountsIn,
        address strategy,
        bytes4 feeSelector
    ) external payable checkTransfersFrom(tokensIn, amountsIn) {
        bool hasEth = false;

        for (uint256 i; i < tokensIn.length; i++) {
            if (tokensIn[i] == ETH || tokensIn[i] == UNISWAP_ETH) {
                _checkMsgValue(amountsIn[i], true);
                hasEth = true;
            }
            _transferTokenFromUser(
                tokensIn[i], amountsIn[i], strategy, feeSelector
            );
        }

        if (!hasEth) {
            // Revert if ETH was sent but not used
            _checkMsgValue(0, false);
        }
    }

    /* Internal functions */

    function _transferTokenFromUser(
        address tokenIn,
        uint256 amountIn,
        address strategy,
        bytes4 feeSelector
    ) internal {
        if (tokenIn != ETH && tokenIn != UNISWAP_ETH) {
            SafeTransferLib.safeTransferFrom(
                tokenIn,
                Sickle(payable(address(this))).owner(),
                address(this),
                amountIn
            );
        }

        bytes memory result = _delegateTo(
            address(feesLib),
            abi.encodeCall(
                IFeesLib.chargeFee, (strategy, feeSelector, tokenIn, amountIn)
            )
        );
        uint256 remainder = abi.decode(result, (uint256));

        if (tokenIn == ETH) {
            weth.deposit{ value: remainder }();
        }
    }

    modifier checkTransferFrom(address tokenIn, uint256 amountIn) {
        if (amountIn == 0) {
            revert AmountInRequired();
        }
        _;
    }

    modifier checkTransfersFrom(
        address[] memory tokensIn,
        uint256[] memory amountsIn
    ) {
        uint256 tokenLength = tokensIn.length;
        if (tokenLength != amountsIn.length) {
            revert ArrayLengthMismatch();
        }
        if (tokenLength == 0) {
            revert TokenInRequired();
        }
        for (uint256 i; i < tokenLength; i++) {
            if (amountsIn[i] == 0) {
                revert AmountInRequired();
            }
        }
        bool hasETH = false;
        bool hasUniswapETH = false;
        for (uint256 i; i < tokenLength; i++) {
            if (tokensIn[i] == ETH) {
                hasETH = true;
            }
            if (tokensIn[i] == UNISWAP_ETH) {
                hasUniswapETH = true;
            }
            if (hasETH && hasUniswapETH) {
                revert IncompatibleEthTokens();
            }
            for (uint256 j = i + 1; j < tokenLength; j++) {
                if (tokensIn[i] == tokensIn[j]) {
                    revert DuplicateTokenIn();
                }
            }
        }
        _;
    }

    modifier checkTransfersTo(
        address[] memory tokensOut
    ) {
        uint256 tokenLength = tokensOut.length;
        if (tokenLength == 0) {
            revert TokenOutRequired();
        }
        _;
    }
}
"
    },
    "contracts/libraries/ZapLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
import { AddLiquidityParams } from "contracts/structs/LiquidityStructs.sol";
import { ILiquidityConnector } from
    "contracts/interfaces/ILiquidityConnector.sol";
import { ConnectorRegistry } from "contracts/ConnectorRegistry.sol";
import { DelegateModule } from "contracts/modules/DelegateModule.sol";
import { ZapIn, ZapOut } from "contracts/structs/ZapStructs.sol";
import { IZapLib } from "contracts/interfaces/libraries/IZapLib.sol";
import { ISwapLib } from "contracts/interfaces/libraries/ISwapLib.sol";

contract ZapLib is DelegateModule, IZapLib {
    error LiquidityAmountError(); // 0x4d0ab6b4

    ISwapLib public immutable swapLib;
    ConnectorRegistry public immutable connectorRegistry;

    constructor(ConnectorRegistry connectorRegistry_, ISwapLib swapLib_) {
        connectorRegistry = connectorRegistry_;
        swapLib = swapLib_;
    }

    function zapIn(
        ZapIn memory zap
    ) external payable {
        uint256 swapDataLength = zap.swaps.length;
        for (uint256 i; i < swapDataLength;) {
            _delegateTo(
                address(swapLib), abi.encodeCall(ISwapLib.swap, (zap.swaps[i]))
            );
            unchecked {
                i++;
            }
        }

        if (zap.addLiquidityParams.lpToken == address(0)) {
            return;
        }

        bool atLeastOneNonZero = false;

        AddLiquidi

Tags:
ERC20, Multisig, Mintable, Burnable, Swap, Liquidity, Staking, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x31d747b5771272a4be23b2229dda3d912182d353|verified:true|block:23382554|tx:0xed4847f3ed5307b37ee138cf04a2fdb843781f3283e434d09f12a8d4f3f4d78f|first_check:1758109135

Submitted on: 2025-09-17 13:38:57

Comments

Log in to comment.

No comments yet.