FarmStrategy

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

import {
    StrategyModule,
    SickleFactory,
    Sickle,
    ConnectorRegistry
} from "contracts/modules/StrategyModule.sol";
import { IFarmConnector, Farm } from "contracts/interfaces/IFarmConnector.sol";
import {
    Farm,
    DepositParams,
    HarvestParams,
    WithdrawParams,
    CompoundParams,
    SimpleDepositParams,
    SimpleWithdrawParams,
    SimpleHarvestParams
} from "contracts/structs/FarmStrategyStructs.sol";
import { IFeesLib } from "contracts/interfaces/libraries/IFeesLib.sol";
import { ISwapLib } from "contracts/interfaces/libraries/ISwapLib.sol";
import { ITransferLib } from "contracts/interfaces/libraries/ITransferLib.sol";
import { IPositionSettingsLib } from
    "contracts/interfaces/libraries/IPositionSettingsLib.sol";
import { IZapLib } from "contracts/interfaces/libraries/IZapLib.sol";
import { IAutomation } from "contracts/interfaces/IAutomation.sol";
import { IPositionSettingsRegistry } from
    "contracts/interfaces/IPositionSettingsRegistry.sol";
import {
    PositionKey,
    PositionSettings
} from "contracts/structs/PositionSettingsStructs.sol";
import { FarmStrategyEvents } from "contracts/events/FarmStrategyEvents.sol";

library FarmStrategyFees {
    bytes4 constant Deposit = bytes4(keccak256("FarmDepositFee"));
    bytes4 constant Harvest = bytes4(keccak256("FarmHarvestFee"));
    bytes4 constant Compound = bytes4(keccak256("FarmCompoundFee"));
    bytes4 constant Withdraw = bytes4(keccak256("FarmWithdrawFee"));
    bytes4 constant HarvestFor = bytes4(keccak256("FarmHarvestForFee"));
    bytes4 constant CompoundFor = bytes4(keccak256("FarmCompoundForFee"));
}

contract FarmStrategy is StrategyModule, IAutomation, FarmStrategyEvents {
    struct Libraries {
        ITransferLib transferLib;
        ISwapLib swapLib;
        IFeesLib feesLib;
        IZapLib zapLib;
        IPositionSettingsLib positionSettingsLib;
    }

    IZapLib public immutable zapLib;
    ISwapLib public immutable swapLib;
    ITransferLib public immutable transferLib;
    IFeesLib public immutable feesLib;
    IPositionSettingsLib public immutable positionSettingsLib;

    IPositionSettingsRegistry public immutable positionSettingsRegistry;

    address public immutable strategyAddress;

    constructor(
        SickleFactory factory,
        ConnectorRegistry connectorRegistry,
        Libraries memory libraries,
        IPositionSettingsRegistry _positionSettingsRegistry
    ) StrategyModule(factory, connectorRegistry) {
        zapLib = libraries.zapLib;
        swapLib = libraries.swapLib;
        transferLib = libraries.transferLib;
        feesLib = libraries.feesLib;
        positionSettingsLib = libraries.positionSettingsLib;
        positionSettingsRegistry = _positionSettingsRegistry;
        strategyAddress = address(this);
    }

    /**
     * @notice Deposits tokens into the specified farm and sets position
     * settings.
     * @param params The parameters for the deposit, including farm details and
     * token amounts.
     * @param positionSettings The settings for the position, including reward
     * configurations and exit configurations.
     * @param sweepTokens The list of tokens to be swept.
     * @param approved The address approved to manage the position (used on
     * first deposit only).
     * @param referralCode The referral code for tracking referrals (used on
     * first deposit only).
     */
    function deposit(
        DepositParams calldata params,
        PositionSettings calldata positionSettings,
        address[] calldata sweepTokens,
        address approved,
        bytes32 referralCode
    ) public payable {
        Sickle sickle = getOrDeploySickle(msg.sender, approved, referralCode);

        bytes4 fee =
            params.zap.swaps.length > 0 ? FarmStrategyFees.Deposit : bytes4(0);

        _increase(sickle, params, sweepTokens, fee);

        _setPositionSettings(sickle, params.farm, positionSettings);

        emit SickleDeposited(
            sickle, params.farm.stakingContract, params.farm.poolIndex
        );
    }

    /**
     * @notice Increases the position by depositing additional tokens into the
     * specified farm.
     * @param params The parameters for the deposit, including farm details and
     * token amounts.
     * @param sweepTokens The list of tokens to be swept.
     */
    function increase(
        DepositParams calldata params,
        address[] calldata sweepTokens
    ) public payable {
        Sickle sickle = getSickle(msg.sender);

        bytes4 fee =
            params.zap.swaps.length > 0 ? FarmStrategyFees.Deposit : bytes4(0);

        _increase(sickle, params, sweepTokens, fee);

        emit SickleDeposited(
            sickle, params.farm.stakingContract, params.farm.poolIndex
        );
    }

    /**
     * @notice Compounds the position by claiming rewards and reinvesting them
     * into the specified farm.
     * @param params The parameters for the compound, including farm details and
     * reward tokens.
     * @param sweepTokens The list of tokens to be swept.
     */
    function compound(
        CompoundParams calldata params,
        address[] calldata sweepTokens
    ) public {
        Sickle sickle = getSickle(msg.sender);

        _compound(sickle, params, sweepTokens, FarmStrategyFees.Compound);

        emit SickleCompounded(
            sickle,
            params.claimFarm.stakingContract,
            params.claimFarm.poolIndex,
            params.depositFarm.stakingContract,
            params.depositFarm.poolIndex
        );
    }

    /**
     * @notice Withdraws tokens from the specified farm.
     * @param farm The farm details.
     * @param params The parameters for the withdrawal, including zap details.
     * @param sweepTokens The list of tokens to be swept.
     */
    function withdraw(
        Farm calldata farm,
        WithdrawParams calldata params,
        address[] calldata sweepTokens
    ) public {
        Sickle sickle = getSickle(msg.sender);

        bytes4 fee =
            params.zap.swaps.length > 0 ? FarmStrategyFees.Withdraw : bytes4(0);

        _withdraw(sickle, farm, params, sweepTokens, fee);

        emit SickleWithdrawn(sickle, farm.stakingContract, farm.poolIndex);
    }

    /**
     * @notice Claims rewards from the specified farm.
     * @param farm The farm details.
     * @param params The parameters for the harvest, including zap details.
     * @param sweepTokens The list of tokens to be swept.
     */
    function harvest(
        Farm calldata farm,
        HarvestParams calldata params,
        address[] calldata sweepTokens
    ) public {
        Sickle sickle = getSickle(msg.sender);

        _harvest(sickle, farm, params, sweepTokens, FarmStrategyFees.Harvest);

        emit SickleHarvested(sickle, farm.stakingContract, farm.poolIndex);
    }

    /**
     * @notice Exits the position by claiming rewards and withdrawing tokens
     * from the specified farm.
     * @param farm The farm details.
     * @param harvestParams The parameters for the harvest, including zap
     * details.
     * @param harvestSweepTokens The list of tokens to be swept from the
     * harvest.
     * @param withdrawParams The parameters for the withdrawal, including zap
     * details.
     * @param withdrawSweepTokens The list of tokens to be swept from the
     * withdrawal.
     */
    function exit(
        Farm calldata farm,
        HarvestParams calldata harvestParams,
        address[] calldata harvestSweepTokens,
        WithdrawParams calldata withdrawParams,
        address[] calldata withdrawSweepTokens
    ) public {
        Sickle sickle = getSickle(msg.sender);

        _exit(
            sickle,
            farm,
            harvestParams,
            harvestSweepTokens,
            withdrawParams,
            withdrawSweepTokens,
            FarmStrategyFees.Harvest
        );

        emit SickleExited(sickle, farm.stakingContract, farm.poolIndex);
    }

    /* Simple (non-zap) */

    /**
     * @notice Deposits tokens into the specified farm and sets position
     * settings.
     * @param params The parameters for the deposit, including farm details and
     * token amounts.
     * @param positionSettings The settings for the position, including reward
     * configurations and exit configurations.
     * @param approved The address approved to manage the position (used on
     * first deposit only).
     * @param referralCode The referral code for tracking referrals (used on
     * first deposit only).
     */
    function simpleDeposit(
        SimpleDepositParams calldata params,
        PositionSettings calldata positionSettings,
        address approved,
        bytes32 referralCode
    ) public payable {
        Sickle sickle = getOrDeploySickle(msg.sender, approved, referralCode);

        _simpleIncrease(sickle, params);

        _setPositionSettings(sickle, params.farm, positionSettings);

        emit SickleDeposited(
            sickle, params.farm.stakingContract, params.farm.poolIndex
        );
    }

    /**
     * @notice Increases the position by depositing additional tokens into the
     * specified farm.
     * @param params The parameters for the deposit, including farm details and
     * token amounts.
     */
    function simpleIncrease(
        SimpleDepositParams calldata params
    ) public {
        Sickle sickle = getSickle(msg.sender);

        _simpleIncrease(sickle, params);

        emit SickleDeposited(
            sickle, params.farm.stakingContract, params.farm.poolIndex
        );
    }

    /**
     * @notice Claims rewards from the specified farm.
     * @param farm The farm details.
     * @param params The parameters for the harvest, including zap details.
     */
    function simpleHarvest(
        Farm calldata farm,
        SimpleHarvestParams calldata params
    ) external {
        Sickle sickle = getSickle(msg.sender);

        _simpleHarvest(sickle, farm, params);

        emit SickleHarvested(sickle, farm.stakingContract, farm.poolIndex);
    }

    /**
     * @notice Withdraws tokens from the specified farm.
     * @param farm The farm details.
     * @param params The parameters for the withdrawal, including zap details.
     */
    function simpleWithdraw(
        Farm calldata farm,
        SimpleWithdrawParams calldata params
    ) public {
        Sickle sickle = getSickle(msg.sender);

        _simpleWithdraw(sickle, farm, params);

        emit SickleWithdrawn(sickle, farm.stakingContract, farm.poolIndex);
    }

    /**
     * @notice Exits the position by claiming rewards and withdrawing tokens
     * from the specified farm.
     * @param farm The farm details.
     * @param harvestParams The parameters for the harvest, including zap
     * details.
     * @param withdrawParams The parameters for the withdrawal, including zap
     * details.
     */
    function simpleExit(
        Farm calldata farm,
        SimpleHarvestParams calldata harvestParams,
        SimpleWithdrawParams calldata withdrawParams
    ) external {
        Sickle sickle = getSickle(msg.sender);

        _simpleHarvest(sickle, farm, harvestParams);
        _simpleWithdraw(sickle, farm, withdrawParams);

        emit SickleExited(sickle, farm.stakingContract, farm.poolIndex);
    }

    /* Automation */

    /**
     * @notice Claims rewards from the specified farm.
     * Used by Automation contract only.
     * @param farm The farm details.
     * @param params The parameters for the harvest, including zap details.
     * @param sweepTokens The list of tokens to be swept.
     */
    function harvestFor(
        Sickle sickle,
        Farm calldata farm,
        HarvestParams calldata params,
        address[] calldata sweepTokens
    ) external override onlyApproved(sickle) {
        positionSettingsRegistry.validateHarvestFor(
            PositionKey({
                sickle: sickle,
                stakingContract: farm.stakingContract,
                poolIndex: farm.poolIndex
            })
        );
        _harvest(sickle, farm, params, sweepTokens, FarmStrategyFees.HarvestFor);
    }

    /**
     * @notice Compounds the position by claiming rewards and reinvesting them
     * into the specified farm.
     * @param params The parameters for the compound, including farm details and
     * reward tokens.
     * @param sweepTokens The list of tokens to be swept.
     */
    function compoundFor(
        Sickle sickle,
        CompoundParams calldata params,
        address[] calldata sweepTokens
    ) external override onlyApproved(sickle) {
        positionSettingsRegistry.validateCompoundFor(
            PositionKey({
                sickle: sickle,
                stakingContract: params.claimFarm.stakingContract,
                poolIndex: params.claimFarm.poolIndex
            })
        );
        _compound(sickle, params, sweepTokens, FarmStrategyFees.CompoundFor);
    }

    /**
     * @notice Exits the position by claiming rewards and withdrawing tokens
     * from the specified farm.
     * @param farm The farm details.
     * @param harvestParams The parameters for the harvest, including zap
     * details.
     * @param harvestSweepTokens The list of tokens to be swept from the
     * harvest.
     * @param withdrawParams The parameters for the withdrawal, including zap
     * details.
     * @param withdrawSweepTokens The list of tokens to be swept from the
     * withdrawal.
     */
    function exitFor(
        Sickle sickle,
        Farm calldata farm,
        HarvestParams calldata harvestParams,
        address[] calldata harvestSweepTokens,
        WithdrawParams calldata withdrawParams,
        address[] calldata withdrawSweepTokens
    ) external override onlyApproved(sickle) {
        positionSettingsRegistry.validateExitFor(
            PositionKey({
                sickle: sickle,
                stakingContract: farm.stakingContract,
                poolIndex: farm.poolIndex
            })
        );
        _exit(
            sickle,
            farm,
            harvestParams,
            harvestSweepTokens,
            withdrawParams,
            withdrawSweepTokens,
            FarmStrategyFees.HarvestFor
        );
    }

    /* Simple Private */

    function _simpleIncrease(
        Sickle sickle,
        SimpleDepositParams calldata params
    ) private {
        address[] memory targets = new address[](2);
        bytes[] memory data = new bytes[](2);

        targets[0] = address(transferLib);
        data[0] = abi.encodeCall(
            ITransferLib.transferTokenFromUser,
            (params.lpToken, params.amountIn, strategyAddress, bytes4(0))
        );

        targets[1] = connectorRegistry.connectorOf(params.farm.stakingContract);
        data[1] = abi.encodeCall(
            IFarmConnector.deposit,
            (params.farm, params.lpToken, params.extraData)
        );

        sickle.multicall(targets, data);
    }

    function _simpleWithdraw(
        Sickle sickle,
        Farm calldata farm,
        SimpleWithdrawParams calldata params
    ) private {
        address[] memory targets = new address[](2);
        bytes[] memory data = new bytes[](2);

        address farmConnector =
            connectorRegistry.connectorOf(farm.stakingContract);
        targets[0] = farmConnector;
        data[0] = abi.encodeCall(
            IFarmConnector.withdraw, (farm, params.amountOut, params.extraData)
        );

        targets[1] = address(transferLib);
        data[1] =
            abi.encodeCall(ITransferLib.transferTokenToUser, (params.lpToken));

        sickle.multicall(targets, data);
    }

    function _simpleHarvest(
        Sickle sickle,
        Farm calldata farm,
        SimpleHarvestParams calldata params
    ) private {
        address[] memory targets = new address[](3);
        bytes[] memory data = new bytes[](3);

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

        targets[1] = address(feesLib);
        data[1] = abi.encodeCall(
            IFeesLib.chargeFees,
            (strategyAddress, FarmStrategyFees.Harvest, params.rewardTokens)
        );

        targets[2] = address(transferLib);
        data[2] = abi.encodeCall(
            ITransferLib.transferTokensToUser, (params.rewardTokens)
        );

        sickle.multicall(targets, data);
    }

    /* Private */

    function _setPositionSettings(
        Sickle sickle,
        Farm calldata farm,
        PositionSettings calldata settings
    ) private {
        address[] memory targets = new address[](1);
        bytes[] memory data = new bytes[](1);

        targets[0] = address(positionSettingsLib);
        data[0] = abi.encodeCall(
            positionSettingsLib.setPositionSettings,
            (positionSettingsRegistry, farm, settings)
        );

        sickle.multicall(targets, data);
    }

    function _increase(
        Sickle sickle,
        DepositParams calldata params,
        address[] calldata sweepTokens,
        bytes4 fee
    ) private {
        address[] memory targets = new address[](1);
        bytes[] memory data = new bytes[](1);

        targets[0] = address(transferLib);
        data[0] = abi.encodeCall(
            ITransferLib.transferTokensFromUser,
            (params.tokensIn, params.amountsIn, strategyAddress, fee)
        );

        // Transfer first to keep further actions non-payable
        sickle.multicall{ value: msg.value }(targets, data);

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

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

        targets[1] = connectorRegistry.connectorOf(params.farm.stakingContract);
        data[1] = abi.encodeCall(
            IFarmConnector.deposit,
            (
                params.farm,
                params.zap.addLiquidityParams.lpToken,
                params.extraData
            )
        );

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

        sickle.multicall(targets, data);
    }

    function _harvest(
        Sickle sickle,
        Farm calldata farm,
        HarvestParams calldata params,
        address[] calldata sweepTokens,
        bytes4 fee
    ) private {
        address[] memory targets = new address[](4);
        bytes[] memory data = new bytes[](4);

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

        targets[1] = address(swapLib);
        data[1] = abi.encodeCall(ISwapLib.swapMultiple, (params.swaps));

        targets[2] = address(feesLib);
        data[2] = abi.encodeCall(
            IFeesLib.chargeFees, (strategyAddress, fee, params.tokensOut)
        );

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

        sickle.multicall(targets, data);
    }

    function _withdraw(
        Sickle sickle,
        Farm calldata farm,
        WithdrawParams calldata params,
        address[] calldata sweepTokens,
        bytes4 fee
    ) private {
        address[] memory targets = new address[](4);
        bytes[] memory data = new bytes[](4);

        address farmConnector =
            connectorRegistry.connectorOf(farm.stakingContract);
        targets[0] = farmConnector;
        data[0] = abi.encodeCall(
            IFarmConnector.withdraw,
            (
                farm,
                params.zap.removeLiquidityParams.lpAmountIn,
                params.extraData
            )
        );

        targets[1] = address(zapLib);
        data[1] = abi.encodeCall(IZapLib.zapOut, (params.zap));

        targets[2] = address(feesLib);
        data[2] = abi.encodeCall(
            IFeesLib.chargeFees, (strategyAddress, fee, params.tokensOut)
        );

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

        sickle.multicall(targets, data);
    }

    function _exit(
        Sickle sickle,
        Farm calldata farm,
        HarvestParams calldata harvestParams,
        address[] calldata harvestSweepTokens,
        WithdrawParams calldata withdrawParams,
        address[] calldata withdrawSweepTokens,
        bytes4 harvestFee
    ) private {
        _harvest(sickle, farm, harvestParams, harvestSweepTokens, harvestFee);
        _withdraw(
            sickle,
            farm,
            withdrawParams,
            withdrawSweepTokens,
            FarmStrategyFees.Withdraw
        );
    }

    function _compound(
        Sickle sickle,
        CompoundParams calldata params,
        address[] calldata sweepTokens,
        bytes4 fee
    ) private {
        address[] memory targets = new address[](5);
        bytes[] memory data = new bytes[](5);

        address farmConnector =
            connectorRegistry.connectorOf(params.claimFarm.stakingContract);

        targets[0] = farmConnector;
        data[0] = abi.encodeCall(
            IFarmConnector.claim, (params.claimFarm, params.claimExtraData)
        );

        targets[1] = address(feesLib);
        data[1] = abi.encodeCall(
            IFeesLib.chargeFees, (strategyAddress, fee, params.rewardTokens)
        );

        targets[2] = address(zapLib);
        data[2] = abi.encodeCall(IZapLib.zapIn, (params.zap));

        address depositConnector =
            connectorRegistry.connectorOf(params.depositFarm.stakingContract);

        targets[3] = depositConnector;
        data[3] = abi.encodeCall(
            IFarmConnector.deposit,
            (
                params.depositFarm,
                params.zap.addLiquidityParams.lpToken,
                params.depositExtraData
            )
        );

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

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

import { SickleFactory, Sickle } from "contracts/SickleFactory.sol";
import { ConnectorRegistry } from "contracts/ConnectorRegistry.sol";
import { AccessControlModule } from "contracts/modules/AccessControlModule.sol";

contract StrategyModule is AccessControlModule {
    ConnectorRegistry public immutable connectorRegistry;

    constructor(
        SickleFactory factory,
        ConnectorRegistry connectorRegistry_
    ) AccessControlModule(factory) {
        connectorRegistry = connectorRegistry_;
    }

    function getSickle(
        address owner
    ) public view returns (Sickle) {
        Sickle sickle = Sickle(payable(factory.sickles(owner)));
        if (address(sickle) == address(0)) {
            revert SickleNotDeployed();
        }
        return sickle;
    }

    function getOrDeploySickle(
        address owner,
        address approved,
        bytes32 referralCode
    ) public returns (Sickle) {
        return
            Sickle(payable(factory.getOrDeploy(owner, approved, referralCode)));
    }
}
"
    },
    "contracts/interfaces/IFarmConnector.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Farm } from "contracts/structs/FarmStrategyStructs.sol";

interface IFarmConnector {
    function deposit(
        Farm calldata farm,
        address token,
        bytes memory extraData
    ) external;

    function withdraw(
        Farm calldata farm,
        uint256 amount,
        bytes memory extraData
    ) external;

    function claim(Farm calldata farm, bytes memory extraData) external;

    function balanceOf(
        Farm calldata farm,
        address user
    ) external view returns (uint256);

    function earned(
        Farm calldata farm,
        address user,
        address[] calldata rewardTokens
    ) external view returns (uint256[] memory);

    function isStaked(
        Farm calldata farm,
        address user
    ) external view returns (bool);
}
"
    },
    "contracts/structs/FarmStrategyStructs.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { ZapIn, ZapOut } from "contracts/structs/ZapStructs.sol";
import { SwapParams } from "contracts/structs/SwapStructs.sol";

struct Farm {
    address stakingContract;
    uint256 poolIndex;
}

struct DepositParams {
    Farm farm;
    address[] tokensIn;
    uint256[] amountsIn;
    ZapIn zap;
    bytes extraData;
}

struct WithdrawParams {
    bytes extraData;
    ZapOut zap;
    address[] tokensOut;
}

struct HarvestParams {
    SwapParams[] swaps;
    bytes extraData;
    address[] tokensOut;
}

struct CompoundParams {
    Farm claimFarm;
    bytes claimExtraData;
    address[] rewardTokens;
    ZapIn zap;
    Farm depositFarm;
    bytes depositExtraData;
}

struct SimpleDepositParams {
    Farm farm;
    address lpToken;
    uint256 amountIn;
    bytes extraData;
}

struct SimpleHarvestParams {
    address[] rewardTokens;
    bytes extraData;
}

struct SimpleWithdrawParams {
    address lpToken;
    uint256 amountOut;
    bytes extraData;
}
"
    },
    "contracts/interfaces/libraries/IFeesLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { Sickle } from "contracts/Sickle.sol";

interface IFeesLib {
    event FeeCharged(
        address strategy, bytes4 feeDescriptor, uint256 amount, address token
    );
    event TransactionCostCharged(address recipient, uint256 amount);

    function chargeFee(
        address strategy,
        bytes4 feeDescriptor,
        address feeToken,
        uint256 feeBasis
    ) external payable returns (uint256 remainder);

    function chargeFees(
        address strategy,
        bytes4 feeDescriptor,
        address[] memory feeTokens
    ) external payable;

    function getBalance(
        Sickle sickle,
        address token
    ) external view returns (uint256);
}
"
    },
    "contracts/interfaces/libraries/ISwapLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { SwapParams } from "contracts/structs/SwapStructs.sol";

interface ISwapLib {
    function swap(
        SwapParams memory swap
    ) external payable;

    function swapMultiple(
        SwapParams[] memory swaps
    ) external payable;
}
"
    },
    "contracts/interfaces/libraries/ITransferLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface ITransferLib {
    error ArrayLengthMismatch();
    error TokenInRequired();
    error AmountInRequired();
    error DuplicateTokenIn();
    error TokenOutRequired();
    error IncompatibleEthTokens();

    function transferTokenToUser(
        address token
    ) external payable;

    function transferTokensToUser(
        address[] memory tokens
    ) external payable;

    function transferTokenFromUser(
        address tokenIn,
        uint256 amountIn,
        address strategy,
        bytes4 feeSelector
    ) external payable;

    function transferTokensFromUser(
        address[] memory tokensIn,
        uint256[] memory amountsIn,
        address strategy,
        bytes4 feeSelector
    ) external payable;
}
"
    },
    "contracts/interfaces/libraries/IPositionSettingsLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { Farm } from "contracts/structs/FarmStrategyStructs.sol";
import { PositionSettings } from "contracts/structs/PositionSettingsStructs.sol";
import { IPositionSettingsRegistry } from
    "contracts/interfaces/IPositionSettingsRegistry.sol";

interface IPositionSettingsLib {
    function setPositionSettings(
        IPositionSettingsRegistry nftSettingsRegistry,
        Farm calldata farm,
        PositionSettings calldata settings
    ) external;
}
"
    },
    "contracts/interfaces/libraries/IZapLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { ZapIn, ZapOut } from "contracts/structs/ZapStructs.sol";

interface IZapLib {
    function zapIn(
        ZapIn memory zap
    ) external payable;

    function zapOut(
        ZapOut memory zap
    ) external;
}
"
    },
    "contracts/interfaces/IAutomation.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { Sickle } from "contracts/Sickle.sol";
import {
    Farm,
    HarvestParams,
    CompoundParams,
    WithdrawParams
} from "contracts/structs/FarmStrategyStructs.sol";

interface IAutomation {
    function harvestFor(
        Sickle sickle,
        Farm calldata farm,
        HarvestParams calldata params,
        address[] calldata sweepTokens
    ) external;

    function compoundFor(
        Sickle sickle,
        CompoundParams calldata params,
        address[] calldata sweepTokens
    ) external;

    function exitFor(
        Sickle sickle,
        Farm calldata farm,
        HarvestParams calldata harvestParams,
        address[] calldata harvestSweepTokens,
        WithdrawParams calldata withdrawParams,
        address[] calldata withdrawSweepTokens
    ) external;
}
"
    },
    "contracts/interfaces/IPositionSettingsRegistry.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {
    PositionKey,
    PositionSettings
} from "contracts/structs/PositionSettingsStructs.sol";

interface IPositionSettingsRegistry {
    error InvalidStakingContract();
    error InvalidPool();
    error InvalidRouter();
    error SickleNotDeployed();
    error AutoHarvestNotSet();
    error AutoCompoundNotSet();
    error RewardBehaviorNotSet();
    error AutoExitNotSet();
    error ConditionsNotMet();
    error InvalidPrice();
    error InvalidTokenOut();
    error ExitTriggersNotSet();
    error InvalidSlippageBP();
    error InvalidPriceImpactBP();
    error OnlySickle();
    error NonZeroRewardConfig();
    error NonZeroExitConfig();
    error InvalidTriggerReserves();
    error InvalidTokenIndices();
    error InvalidExitTriggers();

    event PositionSettingsSet(PositionKey key, PositionSettings settings);
    event ConnectionRegistrySet(address connectorRegistry);

    function getPositionSettings(
        PositionKey calldata key
    ) external view returns (PositionSettings memory);

    function setPositionSettings(
        PositionKey calldata key,
        PositionSettings calldata settings
    ) external;

    function validateExitFor(
        PositionKey memory key
    ) external;

    function validateHarvestFor(
        PositionKey memory key
    ) external;

    function validateCompoundFor(
        PositionKey memory key
    ) external;
}
"
    },
    "contracts/structs/PositionSettingsStructs.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { Sickle } from "contracts/Sickle.sol";

struct PositionKey {
    Sickle sickle;
    address stakingContract;
    uint256 poolIndex;
}

enum RewardBehavior {
    None,
    Harvest,
    Compound
}

struct RewardConfig {
    RewardBehavior rewardBehavior;
    address harvestTokenOut;
}

struct ExitConfig {
    uint256 baseTokenIndex;
    uint256 quoteTokenIndex;
    uint256 triggerPriceLow;
    address exitTokenOutLow;
    uint256 triggerPriceHigh;
    address exitTokenOutHigh;
    uint256[] triggerReservesLow;
    address[] triggerReservesTokensOut;
    uint256 priceImpactBP;
    uint256 slippageBP;
}

/**
 * Settings for automating an ERC20 position
 * @param pool: Uniswap or Aerodrome vAMM/sAMM pair for the position (requires
 * ILiquidityConnector connector registered)
 * @param router: Router for the pair (requires connector registration)
 * @param automateRewards: Whether to automatically harvest or compound rewards
 * for this position, regardless of rebalance settings.
 * @param rewardConfig: Configuration for reward automation
 * Harvest as-is, harvest and convert to a different token, or compound into the
 * position.
 * @param autoExit: Whether to automatically exit the position when it goes out
 * of
 * range
 * @param exitConfig: Configuration for the above
 */
struct PositionSettings {
    address pool;
    address router;
    bool automateRewards;
    RewardConfig rewardConfig;
    bool autoExit;
    ExitConfig exitConfig;
    bytes extraData;
}
"
    },
    "contracts/events/FarmStrategyEvents.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { Sickle } from "contracts/Sickle.sol";

abstract contract FarmStrategyEvents {
    event SickleDeposited(
        Sickle indexed sickle,
        address indexed stakingContract,
        uint256 indexed poolIndex
    );

    event SickleHarvested(
        Sickle indexed sickle,
        address indexed stakingContract,
        uint256 indexed poolIndex
    );

    event SickleCompounded(
        Sickle indexed sickle,
        address indexed claimStakingContract,
        uint256 claimPoolIndex,
        address indexed depositStakingContract,
        uint256 depositPoolIndex
    );

    event SickleWithdrawn(
        Sickle indexed sickle,
        address indexed stakingContract,
        uint256 indexed poolIndex
    );

    event SickleExited(
        Sickle indexed sickle,
        address indexed stakingContract,
        uint256 indexed poolIndex
    );
}
"
    },
    "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/modules/AccessControlModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { Sickle } from "contracts/Sickle.sol";
import { SickleFactory } from "contracts/SickleFactory.sol";

contract AccessControlModule {
    SickleFactory public immutable factory;

    error NotOwner(address sender); // 30cd7471
    error NotApproved();
    error SickleNotDeployed();
    error NotRegisteredSickle();

    constructor(
        SickleFactory factory_
    ) {
        factory = factory_;
    }

    modifier onlyRegisteredSickle() {
        if (factory.admins(address(this)) == address(0)) {
            revert NotRegisteredSickle();
        }

        _;
    }

    // @dev allow access only to the sickle's owner or addresses approved by him
    // to use only for functions such as claiming rewards or compounding rewards
    modifier onlyApproved(
        Sickle sickle
    ) {
        // Here we check if the Sickle was really deployed, this gives use the
        // guarantee that the contract that we are going to call is genuine
        if (factory.admins(address(sickle)) == address(0)) {
            revert SickleNotDeployed();
        }

        if (sickle.approved() != msg.sender) revert NotApproved();

        _;
    }
}
"
    },
    "contracts/structs/ZapStructs.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { SwapParams } from "contracts/structs/SwapStructs.sol";

import {
    AddLiquidityParams,
    RemoveLiquidityParams
} from "contracts/structs/LiquidityStructs.sol";

struct ZapIn {
    SwapParams[] swaps;
    AddLiquidityParams addLiquidityParams;
}

struct ZapOut {
    RemoveLiquidityParams removeLiquidityParams;
    SwapParams[] swaps;
}
"
    },
    "contracts/structs/SwapStructs.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

struct SwapParams {
    address tokenApproval;
    address router;
    uint256 amountIn;
    uint256 desiredAmountOut;
    uint256 minAmountOut;
    address tokenIn;
    address tokenOut;
    bytes extraData;
}
"
    },
    "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;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/proxy/Clones.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (proxy/Clones.sol)

pragma solidity ^0.8.0;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 *
 * _Available since v3.4._
 */
library Clones {
    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(0, 0x09, 0x37)
        }
        require(instance != address(0), "ERC1167: create failed");
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(0, 0x09, 0x37, salt)
        }
        require(instance != address(0), "ERC1167: create2 failed");
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := keccak256(add(ptr, 0x43), 0x55)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(address implementation, bytes32 salt)
        internal
        view
        returns (address predicted)
    {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}
"
    },
    "contracts/SickleRegistry.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

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

library SickleRegistryEvents {
    event CollectorChanged(address newCollector);
    event FeesUpdated(bytes32[] feeHashes, uint256[] feesInBP);
    event ReferralCodeCreated(bytes32 indexed code, address indexed referrer);

    // Multicall caller and target whitelist status changes
    event CallerStatusChanged(address caller, bool isWhitelisted);
    event TargetStatusChanged(address target, bool isWhitelisted);
}

/// @title SickleRegistry contract
/// @author vfat.tools
/// @notice Manages the whitelisted contracts and the collector address
contract SickleRegistry is Admin {
    /// CONSTANTS ///

    uint256 constant MAX_FEE = 500; // 5%

    /// ERRORS ///

    error ArrayLengthMismatch(); // 0xa24a13a6
    error FeeAboveMaxLimit(); // 0xd6cf7b5e
    error InvalidReferralCode(); // 0xe55b4629

    /// STORAGE ///

    /// @notice Address of the fee collector
    address public collector;

    /// @notice Tracks the contracts that can be called through Sickle multicall
    /// @return True if the contract is a whitelisted target
    mapping(address => bool) public isWhitelistedTarget;

    /// @notice Tracks the contracts that can call Sickle multicall
    /// @return True if the contract is a whitelisted caller
    mapping(address => bool) public isWhitelistedCaller;

    /// @notice Keeps track of the referrers and their associated code
    mapping(bytes32 => address) public referralCodes;

    /// @notice Mapping for fee hashes (hash of the strategy contract addresses
    /// and the function selectors) and their associated fees
    /// @return The fee in basis points to apply to the transaction amount
    mapping(bytes32 => uint256) public feeRegistry;

    /// WRITE FUNCTIONS ///

    /// @param admin_ Address of the admin
    /// @param collector_ Address of the collector
    constructor(address admin_, address collector_) Admin(admin_) {
        collector = collector_;
    }

    /// @notice Updates the whitelist status for multiple multicall targets
    /// @param targets Addresses of the contracts to update
    /// @param isApproved New status for the contracts
    /// @custom:access Restricted to protocol admin.
    function setWhitelistedTargets(
        address[] calldata targets,
        bool isApproved
    ) external onlyAdmin {
        for (uint256 i; i < targets.length;) {
            isWhitelistedTarget[targets[i]] = isApproved;
            emit SickleRegistryEvents.TargetStatusChanged(
                targets[i], isApproved
            );

            unchecked {
                ++i;
            }
        }
    }

    /// @notice Updates the fee collector address
    /// @param newCollector Address of the new fee collector
    /// @custom:access Restricted to protocol admin.
    function updateCollector(
        address newCollector
    ) external onlyAdmin {
        collector = newCollector;
        emit SickleRegistryEvents.CollectorChanged(newCollector);
    }

    /// @notice Update the whitelist status for multiple multicall callers
    /// @param callers Addresses of the callers
    /// @param isApproved New status for the caller
    /// @custom:access Restricted to protocol admin.
    function setWhitelistedCallers(
        address[] calldata callers,
        bool isApproved
    ) external onlyAdmin {
        for (uint256 i; i < callers.length;) {
            isWhitelistedCaller[callers[i]] = isApproved;
            emit SickleRegistryEvents.CallerStatusChanged(
                callers[i], isApproved
            );

            unchecked {
                ++i;
            }
        }
    }

    /// @notice Associates a referral code to the address of the caller
    function setReferralCode(
        bytes32 referralCode
    ) external {
        if (referralCodes[referralCode] != address(0)) {
            revert InvalidReferralCode();
        }

        referralCodes[referralCode] = msg.sender;
        emit SickleRegistryEvents.ReferralCodeCreated(referralCode, msg.sender);
    }

    /// @notice Update the fees for multiple strategy functions
    /// @param feeHashes Array of fee hashes
    /// @param feesArray Array of fees to apply (in basis points)
    /// @custom:access Restricted to protocol admin.
    function setFees(
        bytes32[] calldata feeHashes,
        uint256[] calldata feesArray
    ) external onlyAdmin {
        if (feeHashes.length != feesArray.length) {
            revert ArrayLengthMismatch();
        }

        for (uint256 i = 0; i < feeHashes.length;) {
            if (feesArray[i] <= MAX_FEE) {
                feeRegistry[feeHashes[i]] = feesArray[i];
            } else {
                revert FeeAboveMaxLimit();
            }
            unchecked {
                ++i;
            }
        }

        emit SickleRegistryEvents.FeesUpdated(feeHashes, feesArray);
    }
}
"
    },
    "contracts/base/Admin.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

/// @title Admin contract
/// @author vfat.tools
/// @notice Provides an administration mechanism allowing restricted functions
abstract contract Admin {
    /// ERRORS ///

    /// @notice Thrown when the caller is not the admin
    error NotAdminError(); //0xb5c42b3b

    /// EVENTS ///

    /// @notice Emitted when a new admin is set
    /// @param oldAdmin Address of the old admin
    /// @param newAdmin Address of the new admin
    event AdminSet(address oldAdmin, address newAdmin);

    /// STORAGE ///

    /// @notice Address of the current admin
    address public admin;

    /// MODIFIERS ///

    /// @dev Restricts a function to the admin
    modifier onlyAdmin() {
        if (msg.sender != admin) revert NotAdminError();
        _;
    }

    /// WRITE FUNCTIONS ///

    /// @param admin_ Address of the admin
    constructor(
        address admin_
    ) {
        emit AdminSet(address(0), admin_);
        admin = admin_;
    }

    /// @notice Sets a new admin
    /// @param newAdmin Address of the new admin
    /// @custom:access Restricted to protocol admin.
    function setAdmin(
        address newAdmin
    ) external onlyAdmin {
        emit AdminSet(admin, newAdmin);
        admin = newAdmin;
    }
}
"
    },
    "contracts/base/TimelockAdmin.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

/// @title TimelockAdmin contract
/// @author vfat.tools
/// @notice Provides an timelockAdministration mechanism allowing restricted
/// functions
abstract contract TimelockAdmin {
    /// ERRORS ///

    /// @notice Thrown when the caller is not the timelockAdmin
    error NotTimelockAdminError();

    /// EVENTS ///

    /// @notice Emitted when a new timelockAdmin is set
    /// @param oldTimelockAdmin Address of the old timelockAdmin
    /// @param newTimelockAdmin Address of the new timelockAdmin
    event TimelockAdminSet(address oldTimelockAdmin, address newTimelockAdmin);

    /// STORAGE ///

    /// @notice Address of the current timelockAdmin
    address public timelockAdmin;

    /// MODIFIERS ///

    /// @dev Restricts a function to the timelockAdmin
    modifier onlyTimelockAdmin() {
        if (msg.sender != timelockAdmin) revert NotTimelockAdminError();
        _;
    }

    /// WRITE FUNCTIONS ///

    /// @param timelockAdmin_ Address of the timelockAdmin
    constructor(
        address timelockAdmin_
    ) {
        emit TimelockAdminSet(timelockAdmin, timelockAdmin_);
        timelockAdmin = timelockAdmin_;
    }

    /// @notice Sets a new timelockAdmin
    /// @dev Can only be called by the current timelockAdmin
    /// @param newTimelockAdmin Address of the new timelockAdmin
    function setTimelockAdmin(
        address newTimelockAdmin
    ) external onlyTimelockAdmin {
        emit TimelockAdminSet(timelockAdmin, newTimelockAdmin);
        timelockAdmin = newTimelockAdmin;
    }
}
"
    },
    "contracts/structs/LiquidityStructs.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

struct AddLiquidityParams {
    address router;
    address lpToken;
    address[] tokens;
    uint256[] desiredAmounts;
    uint256[] minAmounts;
    bytes extraData;
}

struct RemoveLiquidityParams {
    address router;
    address lpToken;
    address[] tokens;
    uint256 lpAmountIn;
    uint256[] minAmountsOut;
    bytes extraData;
}
"
    },
    "contracts/base/SickleStorage.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

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

library SickleStorageEvents {
    event ApprovedAddressChanged(address newApproved);
}

/// @title SickleStorage contract
/// @author vfat.tools
/// @not

Tags:
Proxy, Swap, Liquidity, Staking, Upgradeable, Factory|addr:0x75d57c8d1d16d1045a33dd127929da4f52d59a16|verified:true|block:23382553|tx:0x843928125e5d49f66447c21dcaddc84db79da5cf271d3879bf6707e0e126f129|first_check:1758109132

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

Comments

Log in to comment.

No comments yet.