StrategyHyperliquidSTETH

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": {
    "src/main/strategies/hyperliquid/StrategyHyperliquidSTETH.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.25;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol";
import "../../../interfaces/flashloanHelper/IFlashloanHelper.sol";
import "../../../interfaces/aave/v3/IPoolV3.sol";
import "../../../interfaces/aave/IAaveOracle.sol";
import "../../../interfaces/IStrategy.sol";
import "../../../interfaces/lido/IWstETH.sol";
import "../../../interfaces/circle/ITokenMessenger.sol";
import "../../../interfaces/kelp/ILRTDepositPool.sol";
import "../../libraries/Errors.sol";
import "../../swap/ParaSwapCaller.sol";

contract StrategyHyperliquidSTETH is
    IStrategy,
    IERC3156FlashBorrower,
    OwnableUpgradeable,
    ParaSwapCaller
{
    using SafeERC20 for IERC20;

    uint256 public constant PRECISION = 1e18;

    // The version of the contract
    string public constant VERSION = "1.0";
    // Storage
    bytes32 internal constant STORAGE_SLOT = keccak256("cian.hedge.steth");
    // USE_MAX is used to indicate that the maximum amount should be used.
    uint256 internal constant USE_MAX = type(uint256).max;

    uint256 internal constant USE_PREVIOUS_OUT = type(uint256).max - 1;
    // The address of the AAVE v3 Pool contract
    IPoolV3 internal constant POOL_AAVEV3 = IPoolV3(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);
    // The address of the AAVE v3 Oracle contract
    IAaveOracle internal constant ORACLE_AAVEV3 = IAaveOracle(0x54586bE62E3c3580375aE3723C145253060Ca0C2);
    // The address of USDC
    address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    // The address of aave wstETH
    address internal constant A_WSTETH = 0x0B925eD163218f6662a35e0f0371Ac234f9E9371;
    // The address of AAVE v3 variable debt token for USDC
    address internal constant D_USDC_AAVEV3 = 0x72E95b8931767C79bA4EeE721354d6E99a61D004;
    // Max Daily NetValue Change
    uint256 internal constant MAX_DAILY_NET_ASSET_CHANGE = 1e16; // 100bps per day

    address public constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;

    address public constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;

    address public constant RSETH = 0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7;

    ILRTDepositPool internal constant KELP_POOL = ILRTDepositPool(0x036676389e48133B63a802f8635AD39E752D375D);

    struct HedgeStorage {
        address vault;
        address rebalancer;
        address flashLoanHelper;
        address circleBridge;
        uint256 originalAssetAmount;
        uint256 totalValue;
        uint256 latestUpdateTimestamp;
        uint256 maxRatio;
        address L2Receiver;
        uint256 bridgeMaxFee;
    }

    enum ActionSelectors {
        Deposit,
        Withdraw,
        Borrow,
        Repay,
        Swap,
        TransferTo,
        Bridge,
        RepayWithRemain
    }

    event UpdateRebalancer(address oldRebalancer, address newRebalancer);
    event UpdateSafeProtocolRatio(uint256 oldSafeProtocolRatio, uint256 newSafeProtocolRatio);
    event OnTransferIn(address token, uint256 amount);
    event TransferRsETHToVault(address token, uint256 amount);
    event NetAssetsUpdated(uint256 oldNetAssets, uint256 newNetAssets);
    event Deposit(address token, uint256 amount);
    event Withdraw(address token, uint256 amount);
    event Borrow(address token, uint256 amount);
    event Repay(address token, uint256 amount);
    event Swap(address srcToken, address dstToken, uint256 amount);
    event Wrap(uint256 amount);
    event Unwrap(uint256 amount);
    event Bridge(uint256 amount, address token, address receiver);
    event UpdateMaxRatio(uint256 oldMaxRatio, uint256 newMaxRatio);
    event UpdateL2Receiver(address oldReceiver, address newReceiver);
    event UpdateBridgeMaxFee(uint256 oldMaxFee, uint256 newMaxFee);

    /**
     * @dev Ensure that this method is only called by the Vault contract.
     */
    modifier onlyVault() {
        if (msg.sender != getStorage().vault) revert Errors.CallerNotVault();
        _;
    }

    /**
     * @dev  Ensure that this method is only called by authorized portfolio managers.
     */
    modifier onlyRebalancer() {
        if (msg.sender != getStorage().rebalancer)
            revert Errors.CallerNotRebalancer();
        _;
    }

    /**
     * @dev Initialize the strategy with given parameters.
     * @param _initBytes Initialization data
     */
    function initialize(bytes calldata _initBytes) external initializer {
        (uint256 _maxLtv, address _admin, address _rebalancer, address _flashLoanHelper, address _circleBridge, address _L2Receiver) = abi.decode(
            _initBytes,
            (uint256, address, address, address, address, address)
        );
        __Ownable_init(_admin);
        if (_admin == address(0)) revert Errors.InvalidAdmin();
        if (_rebalancer == address(0)) revert Errors.InvalidRebalancer();

        HedgeStorage storage s = getStorage();
        s.rebalancer = _rebalancer;
        s.vault = msg.sender;
        s.maxRatio = _maxLtv;
        s.flashLoanHelper = _flashLoanHelper;
        s.circleBridge = _circleBridge;
        s.L2Receiver = _L2Receiver;

        IERC20(WSTETH).safeIncreaseAllowance(address(POOL_AAVEV3), USE_MAX);
        IERC20(USDC).safeIncreaseAllowance(address(POOL_AAVEV3), USE_MAX);
        // POOL_AAVEV3.setUserEMode(3); 

        __Ownable_init(_admin);
    }

    /**
     * @dev Add a new address to the position adjustment whitelist.
     * @param _newRebalancer The new address to be added.
     */
    function updateRebalancer(address _newRebalancer) external onlyOwner {
        if (_newRebalancer == address(0)) revert Errors.InvalidRebalancer();
        emit UpdateRebalancer(getStorage().rebalancer, _newRebalancer);
        getStorage().rebalancer = _newRebalancer;
    }

    function updateAssetAmount(uint256 _amount) external onlyRebalancer {
        getStorage().originalAssetAmount = _amount;
    }

    function updateL2Receiver(address _newReceiver) public onlyRebalancer {
        emit UpdateL2Receiver(getStorage().L2Receiver, _newReceiver);
        getStorage().L2Receiver = _newReceiver;
    }

    function updateBridgeMaxFee(uint256 _newMaxFee) public onlyRebalancer {
        emit UpdateBridgeMaxFee(getStorage().bridgeMaxFee, _newMaxFee);
        getStorage().bridgeMaxFee = _newMaxFee;
    }

    function getStorage() internal pure returns (HedgeStorage storage s) {
        bytes32 slot = STORAGE_SLOT;
        assembly {
            s.slot := slot
        }
    }

    function vault() external view returns (address) {
        return getStorage().vault;
    }

    function rebalancer() external view returns (address) {
        return getStorage().rebalancer;
    }

    function originalAssetAmount() external view returns (uint256) {
        return getStorage().originalAssetAmount;
    }

    function _deposit(address _token, uint256 _amount) internal {
        if (_token != WSTETH) revert Errors.InvalidAsset();
        if (_amount == 0) return;
        POOL_AAVEV3.supply(_token, _amount, address(this), 0);
    }

    function _withdraw(address _token, uint256 _amount) internal {
        if (_token != WSTETH) revert Errors.InvalidAsset();
        if (_amount == 0) return;
        
        POOL_AAVEV3.withdraw(_token, _amount, address(this));
        checkProtocolRatio();
    }

    function _borrow(address _token, uint256 _amount) internal {
        if (_token != USDC) revert Errors.InvalidAsset();
        if (_amount == 0) return;
        
        POOL_AAVEV3.borrow(_token, _amount, 2, 0, address(this));
        checkProtocolRatio();
    }

    function _repay(address _token, uint256 _amount) internal {
        if (_token != USDC) revert Errors.InvalidAsset();
        if (_amount == 0) return;
        POOL_AAVEV3.repay(_token, _amount, 2, address(this));
    }

    function _swap(address _srcToken, address _dstToken, uint256 _amount, uint256 _minOut, bytes memory _swapData)
        internal
        returns (uint256)
    {
        (uint256 out_,) = executeSwap(_amount, _srcToken, _dstToken, _swapData, _minOut);
        return out_;
    }

    function _wrap(uint256 _amount) internal returns (uint256) {
        // if (IERC20(STETH).balanceOf(address(this)) < _amount) {
        //     revert Errors.InvalidAsset();
        // }
        IERC20(STETH).safeIncreaseAllowance(address(WSTETH), _amount);
        uint256 wrappedAmount_ = IWstETH(WSTETH).wrap(_amount);
        return wrappedAmount_;
    }

    function _unwrap(uint256 _amount) internal returns (uint256) {
        // if (IERC20(WSTETH).balanceOf(address(this)) < _amount) {
        //     revert Errors.InvalidAsset();
        // }
        uint256 unwrappedAmount_ = IWstETH(WSTETH).unwrap(_amount);
        return unwrappedAmount_;
    }

    function _bridge(address _token, uint256 _amount, address _receiver) internal {
        if (_token != USDC) revert Errors.InvalidAsset();
        if ( _receiver != getStorage().L2Receiver) revert Errors.InvalidL2Receiver();

        if (_amount == type(uint256).max) {
            _amount = IERC20(USDC).balanceOf(address(this));
        }

        IERC20(USDC).approve(getStorage().circleBridge, _amount);
        bytes32 recieverBytes_ = bytes32(abi.encode(_receiver));

        uint256 maxFee = _amount * getStorage().bridgeMaxFee / 10000;
        ITokenMessenger(getStorage().circleBridge).depositForBurn(_amount, uint32(3), recieverBytes_, USDC, bytes32(0), maxFee, 2000);
    }

    function _repayWithRemain(address _token, uint256 _remainAmount) internal {
        uint256 balance_ = IERC20(_token).balanceOf(address(this));
        if (balance_ < _remainAmount) {
            return;
        }
        balance_ -= _remainAmount;
        _repay(_token, balance_);
    }

    function checkProtocolRatio() internal view {
        uint256 ratio_ = getRatio();
        if (ratio_ > getStorage().maxRatio) revert Errors.RatioOutOfRange();
    }

    function convertWstETHToRsETH(uint256 _amount) internal returns (uint256 rsETHAmount_, uint256 stETHAmount_) {
        uint256 amountBefore_ = IERC20(STETH).balanceOf(address(this));
        _unwrap(_amount);
        uint256 amountAfter_ = IERC20(STETH).balanceOf(address(this));

        stETHAmount_ = amountAfter_ - amountBefore_;

        IERC20(STETH).safeIncreaseAllowance(address(KELP_POOL), stETHAmount_);
        KELP_POOL.depositAsset(STETH, stETHAmount_, 0, "");

        rsETHAmount_ = IERC20(RSETH).balanceOf(address(this));
    }

    function deposit(address _token, uint256 _amount) external onlyRebalancer {
        _deposit(_token, _amount);
        emit Deposit(_token, _amount);
    }

    function withdraw(address _token, uint256 _amount) external onlyRebalancer {
        _withdraw(_token, _amount);
        emit Withdraw(_token, _amount);
    }

    function borrow(address _token, uint256 _amount) external onlyRebalancer {
        _borrow(_token, _amount);
        emit Borrow(_token, _amount);
    }

    function repay(address _token, uint256 _amount) external onlyRebalancer {
        _repay(_token, _amount);
        emit Repay(_token, _amount);
    }

    function swap(
        address _srcToken,
        address _dstToken,
        uint256 _amount,
        uint256 _minOut,
        bytes memory _swapData
    ) external onlyRebalancer {
        if (
            (_srcToken == USDC && _dstToken == WSTETH) ||
            (_srcToken == WSTETH && _dstToken == USDC)
        ) {
            _swap(_srcToken, _dstToken, _amount, _minOut, _swapData);
            emit Swap(_srcToken, _dstToken, _amount);
        }
    }

    function wrap(uint256 _amount) external onlyRebalancer {
        _wrap(_amount);
        emit Wrap(_amount);
    }

    function unwrap(uint256 _amount) external onlyRebalancer {
        _unwrap(_amount);
        emit Unwrap(_amount);
    }

    function bridge(address _token, uint256 _amount, address _receiver) external onlyRebalancer {
        _bridge(_token, _amount, _receiver);
        emit Bridge(_amount, _token, _receiver);
    }    

    function getMaxRatio() public view returns (uint256) {
        return getStorage().maxRatio;
    }

    function getProtocolAccountData() public view returns (uint256 wstEthAmount_, uint256 debtWstEthAmount_) {
        wstEthAmount_ = IERC20(A_WSTETH).balanceOf(address(this));
        uint256 dUsdcAmount_ = IERC20(D_USDC_AAVEV3).balanceOf(address(this)); // Return base 6

        uint256 wstEthPrice = ORACLE_AAVEV3.getAssetPrice(WSTETH); // Return base 8

        debtWstEthAmount_ = dUsdcAmount_ == 0 ? 0 : dUsdcAmount_ * PRECISION * 1e2 / wstEthPrice;
    }

    function getNetAssets() external view returns (uint256) {
        // return KELP_POOL.getRsETHAmountToMint(STETH, getStorage().totalValue);
        return getStorage().totalValue;
    }

    function getRatio() public view returns (uint256 ratio_) {
        (uint256 wstEthAmount_, uint256 debtWstEthAmount_) = getProtocolAccountData();
        ratio_ = wstEthAmount_ == 0 ? 0 : debtWstEthAmount_ * PRECISION / wstEthAmount_;
    }

    function updateMaxRatio(uint256 _newMaxRatio) external onlyOwner {
        uint256 oldMaxRatio_ = getStorage().maxRatio;
        getStorage().maxRatio = _newMaxRatio;
        emit UpdateMaxRatio(oldMaxRatio_, _newMaxRatio);
    }

    function snapshotProtocolUSD() public view returns (uint256 deposits_, uint256 debts_) {
        (deposits_, debts_,,,,) = POOL_AAVEV3.getUserAccountData(address(this));
    }

    function snapshotProtocol() public view returns (uint256 deposits_, uint256 debts_) {
        deposits_ = IERC20(A_WSTETH).balanceOf(address(this));
        debts_ = IERC20(D_USDC_AAVEV3).balanceOf(address(this));
    } 

    function snapshotBalance(address[] calldata tokens) public view returns (uint256[] memory balances_) {
        balances_ = new uint256[](tokens.length);
        for (uint256 i = 0; i < tokens.length; ++i) {
            balances_[i] = IERC20(tokens[i]).balanceOf(address(this));
        }
    }

    function updateNetAssets(uint256 _newNetAsset) external onlyRebalancer {
        uint256 oldNetAsset_ = getStorage().totalValue;
        if (oldNetAsset_ != 0) {
            // Check difference with latest netAssets
            uint256 allowedDiff_ = (((block.timestamp -
                getStorage().latestUpdateTimestamp) / 86400) + 1) *
                MAX_DAILY_NET_ASSET_CHANGE;
            uint256 diff_ = _newNetAsset > oldNetAsset_
                ? ((_newNetAsset - oldNetAsset_) * 1e18) / oldNetAsset_
                : ((oldNetAsset_ - _newNetAsset) * 1e18) / oldNetAsset_;
            if (diff_ > allowedDiff_) revert Errors.InvalidNetAssets();
        }
        getStorage().totalValue = _newNetAsset;
        getStorage().latestUpdateTimestamp = block.timestamp;
        emit NetAssetsUpdated(oldNetAsset_, _newNetAsset);
    }

    function composedCall(uint8[] calldata _actionId, bytes[] calldata _data) external onlyRebalancer {
        if (_actionId.length != _data.length) revert Errors.InvalidLength();
        uint256 previousOut_ = 0;
        for (uint256 i = 0; i < _actionId.length; ++i) {
            previousOut_ = _dispatchCall(_actionId[i], previousOut_, _data[i]);
        }
    }

    function onTransferIn(
        address _token,
        uint256 _amount
    ) external onlyVault returns (bool) {
        if (_token != STETH) revert Errors.InvalidUnderlyingToken();
        IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);

        uint256 amountBefore_ = IERC20(WSTETH).balanceOf(address(this));

        _wrap(_amount);

        uint256 amountAfter_ = IERC20(WSTETH).balanceOf(address(this));

        getStorage().originalAssetAmount += amountAfter_ - amountBefore_;
        getStorage().totalValue += _amount;

        emit OnTransferIn(_token, _amount);
        return true;
    }

    function transferToVault(
        address _token,
        uint256 _amount
    ) external onlyRebalancer {
        if (_token != WSTETH) revert Errors.InvalidUnderlyingToken();
        if (_amount == type(uint256).max) {
            _amount = IERC20(WSTETH).balanceOf(address(this));
        }

        HedgeStorage storage s = getStorage();

        // uint256 amountBefore_ = IERC20(STETH).balanceOf(address(this));
        // _unwrap(_amount);
        // uint256 amountAfter_ = IERC20(STETH).balanceOf(address(this));

        // (uint256 rsETHAmount_, uint256 stETHAmount_) = convertWstETHToRsETH(_amount);

        uint256 amountBefore_ = IERC20(STETH).balanceOf(address(this));
        _unwrap(_amount);
        uint256 amountAfter_ = IERC20(STETH).balanceOf(address(this));

        uint256 stETHAmount_ = amountAfter_ - amountBefore_;

        IERC20(STETH).safeTransfer(
            s.vault,
            stETHAmount_
        );

        uint256 currentDeposit_ = s.originalAssetAmount;
        if (_amount > currentDeposit_) {
            _amount = currentDeposit_;
        }

        s.originalAssetAmount -= _amount;
        s.totalValue = s.totalValue > (stETHAmount_) ? s.totalValue - (stETHAmount_) : 0;

        // emit TransferRsETHToVault(RSETH, rsETHAmount_);
    }

    function callFlashLoan(
        address _token,
        uint256 _amount,
        bytes calldata _data
    ) external onlyRebalancer {
        bytes memory dataBytes_ = abi.encode(uint256(0), this.onFlashLoan.selector, _data);
        IFlashloanHelper(getStorage().flashLoanHelper).flashLoan(IERC3156FlashBorrower(address(this)), _token, _amount, dataBytes_);
    }

    function onFlashLoan(
        address _initiator,
        address _token,
        uint256 _amount,
        uint256 _fee,
        bytes calldata _data
    ) external override returns (bytes32) {
        if (_initiator != address(this) || msg.sender != getStorage().flashLoanHelper) revert Errors.InvalidFlashloanCall();

        (uint8[] memory _actionId, bytes[] memory _actionData) = abi.decode(_data, (uint8[], bytes[]));

        if (_actionId.length != _actionData.length || _actionId.length < 1) revert Errors.InvalidLength();

        uint256 previousOut_ = _amount;
        for (uint256 i = 0; i < _actionId.length - 1; ++i) {
            previousOut_ = _dispatchCall(_actionId[i], previousOut_, _actionData[i]);
        }
        previousOut_ = _amount + _fee;
        _dispatchCall(_actionId[_actionId.length - 1], previousOut_, _actionData[_actionData.length - 1]);
        IERC20(_token).safeIncreaseAllowance(msg.sender, _amount + _fee);

        return keccak256("ERC3156FlashBorrower.onFlashLoan");
    }

    function _dispatchCall(uint8 _actionId, uint256 _previousOut, bytes memory _data) internal returns (uint256) {
        if (_actionId == uint8(ActionSelectors.Deposit)) {
            (address token_, uint256 amount_) = abi.decode(_data, (address, uint256));
            if (amount_ == USE_PREVIOUS_OUT) {
                amount_ = _previousOut;
            }
            _deposit(token_, amount_);
            return 0;
        } else if (_actionId == uint8(ActionSelectors.Withdraw)) {
            (address token_, uint256 amount_) = abi.decode(_data, (address, uint256));
            if (amount_ == USE_PREVIOUS_OUT) {
                amount_ = _previousOut;
            }
            _withdraw(token_, amount_);
            return 0;
        } else if (_actionId == uint8(ActionSelectors.Borrow)) {
            (address token_, uint256 amount_) = abi.decode(_data, (address, uint256));
            if (amount_ == USE_PREVIOUS_OUT) {
                amount_ = _previousOut;
            }
            _borrow(token_, amount_);
            return 0;
        } else if (_actionId == uint8(ActionSelectors.Repay)) {
            (address token_, uint256 amount_) = abi.decode(_data, (address, uint256));
            if (amount_ == USE_PREVIOUS_OUT) {
                amount_ = _previousOut;
            }
            _repay(token_, amount_);
            return 0;
        } else if (_actionId == uint8(ActionSelectors.Swap)) {
            (address fromToken_, address toToken_, uint256 amount_, uint256 minOut_, bytes memory swapData_) =
                abi.decode(_data, (address, address, uint256, uint256, bytes));
            return _swap(fromToken_, toToken_, amount_, minOut_, swapData_);
        } else if (_actionId == uint8(ActionSelectors.Bridge)) {
            (address token_, uint256 amount_, address receiver_) = abi.decode(_data, (address, uint256, address));
            _bridge(token_, amount_, receiver_);
            return 0;
        } else if (_actionId == uint8(ActionSelectors.RepayWithRemain)) {
            (address token_, uint256 remainAmount_) = abi.decode(_data, (address, uint256));
            if (remainAmount_ == USE_PREVIOUS_OUT) {
                remainAmount_ = _previousOut;
            }
            _repayWithRemain(token_, remainAmount_);
            return 0;
        }
        return 0;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable
    struct OwnableStorage {
        address _owner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;

    function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
        assembly {
            $.slot := OwnableStorageLocation
        }
    }

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    function __Ownable_init(address initialOwner) internal onlyInitializing {
        __Ownable_init_unchained(initialOwner);
    }

    function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        OwnableStorage storage $ = _getOwnableStorage();
        return $._owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        OwnableStorage storage $ = _getOwnableStorage();
        address oldOwner = $._owner;
        $._owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC3156FlashLender.sol)

pragma solidity ^0.8.20;

import {IERC3156FlashBorrower} from "./IERC3156FlashBorrower.sol";

/**
 * @dev Interface of the ERC3156 FlashLender, as defined in
 * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].
 */
interface IERC3156FlashLender {
    /**
     * @dev The amount of currency available to be lended.
     * @param token The loan currency.
     * @return The amount of `token` that can be borrowed.
     */
    function maxFlashLoan(address token) external view returns (uint256);

    /**
     * @dev The fee to be charged for a given loan.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @return The amount of `token` to be charged for the loan, on top of the returned principal.
     */
    function flashFee(address token, uint256 amount) external view returns (uint256);

    /**
     * @dev Initiate a flash loan.
     * @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
     */
    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external returns (bool);
}
"
    },
    "src/interfaces/flashloanHelper/IFlashloanHelper.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.25;

import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";

interface IFlashloanHelper {
    function flashLoan(IERC3156FlashBorrower _receiver, address _token, uint256 _amount, bytes calldata _params)
        external
        returns (bool);

    function addToWhitelist(address _account) external;
}
"
    },
    "src/interfaces/aave/v3/IPoolV3.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;

import "./libraries/types/DataTypes.sol";

/**
 * @title IPool
 * @author Aave
 * @notice Defines the basic interface for an Aave Pool.
 */
interface IPoolV3 {
    function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;

    function withdraw(address asset, uint256 amount, address to) external returns (uint256);

    function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf)
        external;

    function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf)
        external
        returns (uint256);

    function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;

    function flashLoanSimple(
        address receiverAddress,
        address asset,
        uint256 amount,
        bytes calldata params,
        uint16 referralCode
    ) external;

    function getUserAccountData(address user)
        external
        view
        returns (
            uint256 totalCollateralBase,
            uint256 totalDebtBase,
            uint256 availableBorrowsBase,
            uint256 currentLiquidationThreshold,
            uint256 ltv,
            uint256 healthFactor
        );

    function setUserEMode(uint8 categoryId) external;

    function getUserEMode(address user) external view returns (uint256);

    function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;

    function getReserveData(address asset) external view returns (DataTypes.ReserveData memory);
}
"
    },
    "src/interfaces/aave/IAaveOracle.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

interface IAaveOracle {
    function getAssetPrice(address asset) external view returns (uint256);
}
"
    },
    "src/interfaces/IStrategy.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.25;

interface IStrategy {
    function getNetAssets() external returns (uint256);

    function onTransferIn(address token, uint256 amount) external returns (bool);
}
"
    },
    "src/interfaces/lido/IWstETH.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IWstETH is IERC20 {
    function wrap(uint256 _stETHAmount) external returns (uint256);

    function unwrap(uint256 _stETHAmount) external returns (uint256);

    function tokensPerStEth() external view returns (uint256);

    function stEthPerToken() external view returns (uint256);

    function getStETHByWstETH(uint256 _stETHAmount) external view returns (uint256);

    function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256);
}
"
    },
    "src/interfaces/circle/ITokenMessenger.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.25;

interface ITokenMessenger {
    // CCTP V1
    function depositForBurn(
        uint256 amount,
        uint32 destinationDomain,
        bytes32 mintRecipient,
        address burnToken
    ) external returns (uint64 _nonce);

    // CCTP V2
    function depositForBurn(
        uint256 amount,
        uint32 destinationDomain,
        bytes32 mintRecipient, // The wallet address that will receive the minted USDC
        address burnToken,
        bytes32 destinationCaller, // The address on the target chain to call receiveMessage
        uint256 maxFee, // The maximum fee allowed for the transfer
        uint32 minFinalityThreshold // minFinalityThreshold (1000 or less for Fast Transfer)
    ) external;
}
"
    },
    "src/interfaces/kelp/ILRTDepositPool.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.21;

interface ILRTDepositPool {
    //errors
    error InvalidAmountToDeposit();
    error NotEnoughAssetToTransfer();
    error MaximumDepositLimitReached();
    error MaximumNodeDelegatorLimitReached();
    error InvalidMaximumNodeDelegatorLimit();
    error MinimumAmountToReceiveNotMet();
    error NodeDelegatorNotFound();
    error NodeDelegatorHasAssetBalance(address assetAddress, uint256 assetBalance);
    error NodeDelegatorHasETH();
    error EthTransferFailed();

    //events
    event MaxNodeDelegatorLimitUpdated(uint256 maxNodeDelegatorLimit);
    event NodeDelegatorAddedinQueue(address[] nodeDelegatorContracts);
    event NodeDelegatorRemovedFromQueue(address nodeDelegatorContracts);
    event AssetDeposit(
        address indexed depositor,
        address indexed asset,
        uint256 depositAmount,
        uint256 rsethMintAmount,
        string referralId
    );
    event ETHDeposit(address indexed depositor, uint256 depositAmount, uint256 rsethMintAmount, string referralId);
    event MinAmountToDepositUpdated(uint256 minAmountToDeposit);
    event MaxNegligibleAmountUpdated(uint256 maxNegligibleAmount);
    event ETHSwappedForLST(uint256 ethAmount, address indexed toAsset, uint256 returnAmount);
    event EthTransferred(address to, uint256 amount);

    // functions
    function depositETH(
        uint256 minRSETHAmountExpected,
        string calldata referralId
    ) external payable;

    function depositAsset(
        address asset,
        uint256 depositAmount,
        uint256 minRSETHAmountExpected,
        string calldata referralId
    ) external;

    function getSwapETHToAssetReturnAmount(address toAsset, uint256 ethAmountToSend)
        external
        view
        returns (uint256 returnAmount);

    function getTotalAssetDeposits(address asset) external view returns (uint256);

    function getAssetCurrentLimit(address asset) external view returns (uint256);

    function getRsETHAmountToMint(address asset, uint256 depositAmount) external view returns (uint256);

    function addNodeDelegatorContractToQueue(address[] calldata nodeDelegatorContract) external;

    function transferAssetToNodeDelegator(uint256 ndcIndex, address asset, uint256 amount) external;

    function updateMaxNodeDelegatorLimit(uint256 maxNodeDelegatorLimit) external;

    function getNodeDelegatorQueue() external view returns (address[] memory);

    function getAssetDistributionData(address asset)
        external
        view
        returns (
            uint256 assetLyingInDepositPool,
            uint256 assetLyingInNDCs,
            uint256 assetStakedInEigenLayer,
            uint256 assetUnstakingFromEigenLayer,
            uint256 assetLyingInConverter,
            uint256 assetLyingUnstakingVault
        );

    function getETHDistributionData()
        external
        view
        returns (
            uint256 ethLyingInDepositPool,
            uint256 ethLyingInNDCs,
            uint256 ethStakedInEigenLayer,
            uint256 ethUnstakingFromEigenLayer,
            uint256 ethLyingInConverter,
            uint256 ethLyingInUnstakingVault
        );

    function isNodeDelegator(address nodeDelegatorContract) external view returns (uint256);

    // receivers
    function receiveFromRewardReceiver() external payable;
    function receiveFromLRTConverter() external payable;
    function receiveFromNodeDelegator() external payable;
}
"
    },
    "src/main/libraries/Errors.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.25;

library Errors {
    // Revert Errors:
    error CallerNotOperator(); // 0xa5523ee5
    error CallerNotRebalancer(); // 0xbd72e291
    error CallerNotVault(); // 0xedd7338f
    error CallerNotMinter(); // 0x5eee367a
    error ExitFeeRateTooHigh(); // 0xf4d1caab
    error FlashloanInProgress(); // 0x772ac4e8
    error IncorrectState(); // 0x508c9390
    error InfoExpired(); // 0x4ddf4a65
    error InvalidAccount(); // 0x6d187b28
    error InvalidAdapter(); // 0xfbf66df1
    error InvalidAdmin(); // 0xb5eba9f0
    error InvalidAsset(); // 0xc891add2
    error InvalidCaller(); // 0x48f5c3ed
    error InvalidClaimTime(); // 0x1221b97b
    error InvalidFeeReceiver(); // 0xd200485c
    error InvalidFlashloanCall(); // 0xd2208d52
    error InvalidFlashloanHelper(); // 0x8690f016
    error InvalidFlashloanProvider(); // 0xb6b48551
    error InvalidGasLimit(); // 0x98bdb2e0
    error InvalidInitiator(); // 0xbfda1f28
    error InvalidLength(); // 0x947d5a84
    error InvalidLimit(); // 0xe55fb509
    error InvalidL2Receiver(); // 0x6305ce43
    error InvalidManagementFeeClaimPeriod(); // 0x4022e4f6
    error InvalidManagementFeeRate(); // 0x09aa66eb
    error InvalidMarketCapacity(); // 0xc9034604
    error InvalidNetAssets(); // 0x6da79d69
    error InvalidNewOperator(); // 0xba0cdec5
    error InvalidOperator(); // 0xccea9e6f
    error InvalidRebalancer(); // 0xff288a8e
    error InvalidRedeemOperator(); // 0xd214a597
    error InvalidSafeProtocolRatio(); // 0x7c6b23d6
    error InvalidShares(); // 0x6edcc523
    error InvalidTarget(); // 0x82d5d76a
    error InvalidToken(); // 0xc1ab6dc1
    error InvalidTokenId(); // 0x3f6cc768
    error InvalidUnderlyingToken(); // 0x2fb86f96
    error InvalidVault(); // 0xd03a6320
    error InvalidWithdrawalUser(); // 0x36c17319
    error ManagementFeeRateTooHigh(); // 0x09aa66eb
    error ManagementFeeClaimPeriodTooShort(); // 0x4022e4f6
    error MarketCapacityTooLow(); // 0xc9034604
    error NotSupportedYet(); // 0xfb89ba2a
    error PriceNotUpdated(); // 0x1f4bcb2b
    error PriceUpdatePeriodTooLong(); // 0xe88d3ecb
    error RatioOutOfRange(); // 0x9179cbfa
    error RevenueFeeRateTooHigh(); // 0x0674143f
    error UnSupportedOperation(); // 0xe9ec8129
    error UnsupportedToken(); // 0x6a172882
    error WithdrawZero(); // 0x7ea773a9
    error DepositHalted(); // 0x3ddeeb34
}
"
    },
    "src/main/swap/ParaSwapCaller.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.25;

import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../../interfaces/paraswap/IParaSwapV62.sol";

contract ParaSwapCaller {
    using Address for address;
    using SafeERC20 for IERC20;

    address internal constant PARASWAP_AUGUSTUS_PROXY_V6 = 0x6A000F20005980200259B80c5102003040001068;

    function _decodeBalancerV2Tokens(
        bytes memory balancerData
    ) internal pure returns (address srcToken, address destToken) {
    // solhint-disable-next-line no-inline-assembly
        assembly ("memory-safe") {
        // For a memory bytes array, the first 32 bytes store the length.
        // The actual data starts at an offset of 32 bytes from the start of the array pointer.
        let dataPtr := add(balancerData, 32)
        // The encoded function arguments start after the 4-byte function selector.
        let dataWithoutSelector := add(dataPtr, 4)

        // Check the function selector by loading the first 32 bytes of the data.
        switch mload(dataPtr)
        // If the selector is for swap(tuple singleSwap,tuple funds,uint256 limit,uint256 deadline)
        case 0x52bbbe2900000000000000000000000000000000000000000000000000000000 {
            // According to the ABI encoding for this function signature, assetIn and assetOut
            // are at fixed offsets from the start of the arguments data.
            // Load srcToken from singleSwap.assetIn.
            srcToken := mload(add(dataWithoutSelector, 288))
            // Load destToken from singleSwap.assetOut.
            destToken := mload(add(dataWithoutSelector, 320))
        }
        // If the selector is for batchSwap(uint8 kind,tuple[] swaps,address[] assets,tuple funds,int256[] limits,uint256 deadline)
        case 0x945bcec900000000000000000000000000000000000000000000000000000000 {
            // Load the offset to the 'assets' array. It's the 3rd argument, at offset 64 from the start of arguments.
            let assetsOffset := mload(add(dataWithoutSelector, 64))
            // Get the pointer to the 'assets' array data (which starts with the length).
            let assetsPtr := add(dataWithoutSelector, assetsOffset)
            // Load the length of the 'assets' array.
            let assetsCount := mload(assetsPtr)
            // Get the swap type ('kind') from the first argument.
            let swapType := mload(dataWithoutSelector)
            
            // Set srcToken and destToken based on the swapType.
            switch eq(swapType, 1) // 1 is GIVEN_OUT
            case 1 {
                // For GIVEN_OUT, srcToken is the last asset, and destToken is the first.
                // Load srcToken as the last asset in balancerData.assets.
                // The address of the last element is assetsPtr + assetsCount * 32.
                srcToken := mload(add(assetsPtr, mul(assetsCount, 32)))
                // Load destToken as the first asset in balancerData.assets.
                // The address of the first element is assetsPtr + 32.
                destToken := mload(add(assetsPtr, 32))
            }
            default { // 0 is GIVEN_IN
                // For GIVEN_IN, srcToken is the first asset, and destToken is the last.
                // Load srcToken as the first asset.
                srcToken := mload(add(assetsPtr, 32))
                // Load destToken as the last asset.
                destToken := mload(add(assetsPtr, mul(assetsCount, 32)))
            }
        }
        default {
            // If the selector is invalid, revert with a custom error.
            mstore(0, 0x7352d91c00000000000000000000000000000000000000000000000000000000) // selector for InvalidSelector()
            revert(0, 4)
        }
        // Balancer uses address(0) for ETH, so we convert it to a standard wrapped ETH representation.
        if eq(srcToken, 0) { srcToken := 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE }
        if eq(destToken, 0) { destToken := 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE }
    }
    return (srcToken, destToken);
}

    function analysisPayload(
        bytes calldata _swapData
    ) external pure returns (uint256 amountIn_, address tokenIn_, address tokenOut_) {
        // Match the selector
        require(
            _swapData.length >= 4,
            "ParaSwapCaller: Invalid calldata length"
        );
        bytes4 selector = bytes4(_swapData[:4]);
        if (selector == IParaSwapV62.swapExactAmountIn.selector) {
            // Decode the swapData
            (, GenericData memory d_, , , ) = abi.decode(
                _swapData[4:],
                (address, GenericData, uint256, bytes, bytes)
            );
            tokenIn_ = address(d_.srcToken);
            tokenOut_ = address(d_.destToken);
            amountIn_ = d_.fromAmount;
        } else if (selector == IParaSwapV62.swapOnAugustusRFQTryBatchFill.selector) {
            // todo: test this function
            // Decode the swapData
            (AugustusRFQData memory data_, OrderInfo[] memory order_, ) = abi.decode(
                _swapData[4:],
                (AugustusRFQData, OrderInfo[], bytes)
            );
            uint8 wrapApproveDirection_ = data_.wrapApproveDirection;
            bool direction_;
            assembly {
                direction_ := and(shr(3, wrapApproveDirection_), 1)
            }
            amountIn_ = data_.fromAmount;
            if (direction_) {
                // If direction is true, we are filling taker amount
                tokenIn_ = address(order_[0].order.makerAsset);
                tokenOut_ = address(order_[0].order.takerAsset);
            } else {
                // If direction is false, we are filling maker amount
                tokenIn_ = address(order_[0].order.takerAsset);
                tokenOut_ = address(order_[0].order.makerAsset);
            }
        } else if (selector == IParaSwapV62.swapExactAmountInOnBalancerV2.selector) {
            // Decode the swapData
            (BalancerV2Data memory d_, , , bytes memory data_) = abi.decode(
                _swapData[4:],
                (BalancerV2Data, uint256, bytes, bytes)
            );
            // The first 20 bytes are the beneficiary address and the left most bit is the approve flag
            (tokenIn_, tokenOut_) = _decodeBalancerV2Tokens(data_);
            amountIn_ = d_.fromAmount;
        } else if (selector == IParaSwapV62.swapExactAmountInOnCurveV1.selector) {
            (CurveV1Data memory curveV1Data_, , ) = abi.decode(
                _swapData[4:],
                (CurveV1Data, uint256, bytes)
            );
            tokenIn_ = address(curveV1Data_.srcToken);
            tokenOut_ = address(curveV1Data_.destToken);
            amountIn_ = curveV1Data_.fromAmount;
        } else if (selector == IParaSwapV62.swapExactAmountInOnCurveV2.selector) {
            (CurveV2Data memory curveV2Data_, , ) = abi.decode(
                _swapData[4:],
                (CurveV2Data, uint256, bytes)
            );
            tokenIn_ = address(curveV2Data_.srcToken);
            tokenOut_ = address(curveV2Data_.destToken);
            amountIn_ = curveV2Data_.fromAmount;
        } else if (selector == IParaSwapV62.swapExactAmountInOnUniswapV2.selector) {
            (UniswapV2Data memory uniswapV2Data_, , ) = abi.decode(
                _swapData[4:],
                (UniswapV2Data, uint256, bytes)
            );
            tokenIn_ = address(uniswapV2Data_.srcToken);
            tokenOut_ = address(uniswapV2Data_.destToken);
            amountIn_ = uniswapV2Data_.fromAmount;
        } else if (selector == IParaSwapV62.swapExactAmountInOnUniswapV3.selector) {
            (UniswapV3Data memory uniswapV3Data_, , ) = abi.decode(
                _swapData[4:],
                (UniswapV3Data, uint256, bytes)
            );
            tokenIn_ = address(uniswapV3Data_.srcToken);
            tokenOut_ = address(uniswapV3Data_.destToken);
            amountIn_ = uniswapV3Data_.fromAmount;
        } else if (selector == IParaSwapV62.swapExactAmountInOutOnMakerPSM.selector) {
            (MakerPSMData memory makerPSMData_, ) = abi.decode(
                _swapData[4:],
                (MakerPSMData, bytes)
            );
            tokenIn_ = address(makerPSMData_.srcToken);
            tokenOut_ = address(makerPSMData_.destToken);
            amountIn_ = makerPSMData_.fromAmount;
        } else {
            revert("ParaSwapCaller: Unsupported selector");
        }
    }

    /**
     * @dev Executes the swap operation and verify the validity of the parameters and results.
     * @param _amount The maximum amount of currency spent.
     * @param _srcToken The token to be spent.
     * @param _dstToken The token to be received.
     * @param _swapData Calldata of 1inch.
     * @param _swapGetMin Minimum amount of the token to be received.
     * @return returnAmount_ Actual amount of the token spent.
     * @return spentAmount_ Actual amount of the token received.
     */
    function executeSwap(
        uint256 _amount,
        address _srcToken,
        address _dstToken,
        bytes memory _swapData,
        uint256 _swapGetMin
    ) internal returns (uint256 returnAmount_, uint256 spentAmount_) {
        (bool success_, bytes memory resp_) = address(this).staticcall(
            abi.encodeWithSelector(
                this.analysisPayload.selector,
                _swapData
            )
        );
        uint256 amountBefore_ = IERC20(_dstToken).balanceOf(address(this));
        require(success_, "ParaSwapCaller: Analysis payload failed");
        (uint256 amountIn_, address tokenIn_, address tokenOut_) = abi.decode(
            resp_,
            (uint256, address, address)
        );
        require(
            amountIn_ <= _amount,
            "ParaSwapCaller: Amount in exceeds maximum"
        );
        if (_srcToken != tokenIn_) {
            revert("ParaSwapCaller: Source token mismatch");
        }
        if (_dstToken != tokenOut_) {
            revert("ParaSwapCaller: Destination token mismatch");
        }
        // If srcToken is not ETH, call approve.
        if (_srcToken != address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) {
            IERC20(_srcToken).safeIncreaseAllowance(PARASWAP_AUGUSTUS_PROXY_V6, _amount);
        }
        // Call the ParaSwap Augustus proxy contract with the swap data.
        (success_, resp_) = PARASWAP_AUGUSTUS_PROXY_V6.call{value: _srcToken == address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) ? _amount : 0}(_swapData);
        require(success_, "ParaSwapCaller: Swap execution failed");
        uint256 amountAfter_ = IERC20(_dstToken).balanceOf(address(this));
        require(
            amountAfter_ - amountBefore_ >= _swapGetMin,
            "ParaSwapCaller: Insufficient output amount"
        );
        return (
            amountAfter_ - amountBefore_,
            amountIn_
        );
    }
}"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/Address.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also t

Tags:
ERC20, Multisig, Swap, Liquidity, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xc6603c8e75d1c614568f2a91e206ff8fc0fae257|verified:true|block:23394905|tx:0x7a1a40102eb0338e6c439f9db66082cd5a1bb66be5220cd43b8b4bd784794e96|first_check:1758277799

Submitted on: 2025-09-19 12:30:00

Comments

Log in to comment.

No comments yet.