EulerSwapRegistry

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": {
    "lib/euler-swap/src/EulerSwapRegistry.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import {SafeERC20, IERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";

import {IEulerSwap} from "./interfaces/IEulerSwap.sol";
import {IEulerSwapFactory} from "./interfaces/IEulerSwapFactory.sol";
import {IEulerSwapRegistry} from "./interfaces/IEulerSwapRegistry.sol";
import {IPerspective} from "./interfaces/IPerspective.sol";
import {SwapLib} from "./libraries/SwapLib.sol";
import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol";

/// @title EulerSwapRegistry contract
/// @custom:security-contact security@euler.xyz
/// @author Euler Labs (https://www.eulerlabs.com/)
contract EulerSwapRegistry is IEulerSwapRegistry, EVCUtil {
    using EnumerableSet for EnumerableSet.AddressSet;
    using SafeERC20 for IERC20;

    /// @dev Pool instances must be deployed by this factory
    address public immutable eulerSwapFactory;
    /// @dev Reentrancy guard, shares a storage slot with validVaultPerspective
    bool locked;
    /// @dev Perspective that checks whether vaults used by a pool are permitted by this registry
    address public validVaultPerspective;
    /// @dev Curator can set the minimum validity bond, update the valid vault perspective,
    /// and remove pools from the factory lists
    address public curator;
    /// @dev Minimum size of validity bond, in native token
    uint256 public minimumValidityBond;

    /// @dev Mapping from euler account to pool, if installed
    mapping(address eulerAccount => address) internal installedPools;
    /// @dev Mapping from pool to validity bond amount
    mapping(address pool => uint256) internal validityBonds;
    /// @dev Set of all pool addresses
    EnumerableSet.AddressSet internal allPools;
    /// @dev Mapping from sorted pair of underlyings to set of pools
    mapping(address asset0 => mapping(address asset1 => EnumerableSet.AddressSet)) internal poolMap;

    event PoolRegistered(
        address indexed asset0,
        address indexed asset1,
        address indexed eulerAccount,
        address pool,
        IEulerSwap.StaticParams sParams,
        uint256 validityBond
    );
    event PoolUnregistered(address indexed asset0, address indexed asset1, address indexed eulerAccount, address pool);
    event PoolChallenged(
        address indexed challenger,
        address indexed pool,
        address tokenIn,
        address tokenOut,
        uint256 amount,
        bool exactIn,
        uint256 bondAmount,
        address recipient
    );
    event CuratorTransferred(address indexed oldCurator, address indexed newCurator);
    event MinimumValidityBondUpdated(uint256 oldValue, uint256 newValue);
    event ValidVaultPerspectiveUpdated(address indexed oldPerspective, address indexed newPerspective);

    error Locked();
    error Unauthorized();
    error NotEulerSwapPool();
    error OldOperatorStillInstalled();
    error OperatorNotInstalled();
    error InvalidVaultImplementation();
    error SliceOutOfBounds();
    error InsufficientValidityBond();
    error ChallengeNoBondAvailable();
    error ChallengeBadAssets();
    error ChallengeLiquidityDeferred();
    error ChallengeMissingBond();
    error ChallengeUnauthorized();
    error ChallengeSwapSucceeded();
    error ChallengeSwapNotLiquidityFailure();

    error E_AccountLiquidity(); // From EVK

    constructor(address evc, address eulerSwapFactory_, address validVaultPerspective_, address curator_)
        EVCUtil(evc)
    {
        eulerSwapFactory = eulerSwapFactory_;
        validVaultPerspective = validVaultPerspective_;
        curator = curator_;
    }

    modifier nonReentrant() {
        require(!locked, Locked());
        locked = true;
        _;
        locked = false;
    }

    /// @inheritdoc IEulerSwapRegistry
    function registerPool(address poolAddr) external payable nonReentrant {
        require(IEulerSwapFactory(eulerSwapFactory).deployedPools(poolAddr), NotEulerSwapPool());
        IEulerSwap pool = IEulerSwap(poolAddr);
        IEulerSwap.StaticParams memory sParams = pool.getStaticParams();

        require(_msgSender() == sParams.eulerAccount, Unauthorized());

        require(isValidVault(sParams.supplyVault0) && isValidVault(sParams.supplyVault1), InvalidVaultImplementation());
        require(sParams.borrowVault0 == address(0) || isValidVault(sParams.borrowVault0), InvalidVaultImplementation());
        require(sParams.borrowVault1 == address(0) || isValidVault(sParams.borrowVault1), InvalidVaultImplementation());

        require(msg.value >= minimumValidityBond, InsufficientValidityBond());

        uninstall(sParams.eulerAccount, sParams.eulerAccount, false);

        require(evc.isAccountOperatorAuthorized(sParams.eulerAccount, address(pool)), OperatorNotInstalled());

        (address asset0, address asset1) = pool.getAssets();

        installedPools[sParams.eulerAccount] = address(pool);
        validityBonds[address(pool)] = msg.value;

        allPools.add(address(pool));
        poolMap[asset0][asset1].add(address(pool));

        emit PoolRegistered(asset0, asset1, sParams.eulerAccount, address(pool), sParams, msg.value);
    }

    /// @inheritdoc IEulerSwapRegistry
    function unregisterPool() external nonReentrant {
        address eulerAccount = _msgSender();
        uninstall(eulerAccount, eulerAccount, false);
    }

    modifier onlyCurator() {
        require(_msgSender() == curator, Unauthorized());
        _;
    }

    /// @inheritdoc IEulerSwapRegistry
    function curatorUnregisterPool(address pool, address bondReceiver) external onlyCurator nonReentrant {
        address eulerAccount = IEulerSwap(pool).getStaticParams().eulerAccount;
        if (bondReceiver == address(0)) bondReceiver = eulerAccount;
        uninstall(eulerAccount, bondReceiver, true);
    }

    /// @inheritdoc IEulerSwapRegistry
    function transferCurator(address newCurator) external onlyCurator nonReentrant {
        emit CuratorTransferred(curator, newCurator);
        curator = newCurator;
    }

    /// @inheritdoc IEulerSwapRegistry
    function setMinimumValidityBond(uint256 newMinimum) external onlyCurator nonReentrant {
        emit MinimumValidityBondUpdated(minimumValidityBond, newMinimum);
        minimumValidityBond = newMinimum;
    }

    /// @inheritdoc IEulerSwapRegistry
    function setValidVaultPerspective(address newPerspective) external onlyCurator nonReentrant {
        emit ValidVaultPerspectiveUpdated(validVaultPerspective, newPerspective);
        validVaultPerspective = newPerspective;
    }

    /// @inheritdoc IEulerSwapRegistry
    function challengePool(
        address poolAddr,
        address tokenIn,
        address tokenOut,
        uint256 amount,
        bool exactIn,
        address recipient
    ) external nonReentrant {
        IEulerSwap pool = IEulerSwap(poolAddr);
        address eulerAccount = pool.getStaticParams().eulerAccount;
        bool asset0IsInput;

        require(validityBonds[poolAddr] > 0, ChallengeNoBondAvailable());

        {
            (address asset0, address asset1) = pool.getAssets();
            require(
                (asset0 == tokenIn && asset1 == tokenOut) || (asset0 == tokenOut && asset1 == tokenIn),
                ChallengeBadAssets()
            );
            asset0IsInput = asset0 == tokenIn;
        }

        require(!evc.isAccountStatusCheckDeferred(eulerAccount), ChallengeLiquidityDeferred());

        uint256 quote = pool.computeQuote(tokenIn, tokenOut, amount, exactIn);

        {
            (bool success, bytes memory error) = address(this).call(
                abi.encodeWithSelector(
                    this.challengePoolAttempt.selector,
                    msg.sender,
                    poolAddr,
                    asset0IsInput,
                    tokenIn,
                    exactIn ? amount : quote,
                    exactIn ? quote : amount
                )
            );
            require(!success, ChallengeSwapSucceeded());
            require(
                bytes4(error) == E_AccountLiquidity.selector || bytes4(error) == SwapLib.HookError.selector,
                ChallengeSwapNotLiquidityFailure()
            );
        }

        uint256 bondAmount = validityBonds[poolAddr];
        emit PoolChallenged(msg.sender, poolAddr, tokenIn, tokenOut, amount, exactIn, bondAmount, recipient);

        uninstall(eulerAccount, recipient, true);
    }

    /// @dev Function invoked by challengePool so that errors can be caught. Not intended
    /// to be called by the outside world.
    function challengePoolAttempt(
        address challenger,
        address poolAddr,
        bool asset0IsInput,
        address tokenIn,
        uint256 amountIn,
        uint256 amountOut
    ) external {
        require(msg.sender == address(this), ChallengeUnauthorized());

        IERC20(tokenIn).safeTransferFrom(challenger, poolAddr, amountIn);

        if (asset0IsInput) IEulerSwap(poolAddr).swap(0, amountOut, challenger, "");
        else IEulerSwap(poolAddr).swap(amountOut, 0, challenger, "");
    }

    /// @inheritdoc IEulerSwapRegistry
    function poolByEulerAccount(address eulerAccount) external view returns (address) {
        return installedPools[eulerAccount];
    }

    /// @inheritdoc IEulerSwapRegistry
    function validityBond(address pool) external view returns (uint256) {
        return validityBonds[pool];
    }

    /// @inheritdoc IEulerSwapRegistry
    function poolsLength() external view returns (uint256) {
        return allPools.length();
    }

    /// @inheritdoc IEulerSwapRegistry
    function poolsSlice(uint256 start, uint256 end) external view returns (address[] memory) {
        return getSlice(allPools, start, end);
    }

    /// @inheritdoc IEulerSwapRegistry
    function pools() external view returns (address[] memory) {
        return allPools.values();
    }

    /// @inheritdoc IEulerSwapRegistry
    function poolsByPairLength(address asset0, address asset1) external view returns (uint256) {
        return poolMap[asset0][asset1].length();
    }

    /// @inheritdoc IEulerSwapRegistry
    function poolsByPairSlice(address asset0, address asset1, uint256 start, uint256 end)
        external
        view
        returns (address[] memory)
    {
        return getSlice(poolMap[asset0][asset1], start, end);
    }

    /// @inheritdoc IEulerSwapRegistry
    function poolsByPair(address asset0, address asset1) external view returns (address[] memory) {
        return poolMap[asset0][asset1].values();
    }

    /// @notice Calls a perspective contract to determine if a vault is acceptable.
    /// @param v Candidate vault address
    function isValidVault(address v) internal view returns (bool) {
        return IPerspective(validVaultPerspective).isVerified(v);
    }

    /// @notice Uninstalls the pool associated with the given Euler account
    /// @dev This function removes the pool from the registry's tracking and emits a PoolUnregistered event
    /// @dev The function checks if the operator is still installed and reverts if it is
    /// @dev If no pool exists for the account, the function returns without any action
    /// @param eulerAccount The address of the Euler account whose pool should be uninstalled
    /// @param bondRecipient Where the bond should be sent.
    /// @param forced Whether this is a forced uninstall, vs a user-requested uninstall
    function uninstall(address eulerAccount, address bondRecipient, bool forced) internal {
        address pool = installedPools[eulerAccount];
        if (pool == address(0)) return;

        if (!forced) {
            require(!evc.isAccountOperatorAuthorized(eulerAccount, pool), OldOperatorStillInstalled());
            delete installedPools[eulerAccount];
        }

        (address asset0, address asset1) = IEulerSwap(pool).getAssets();

        allPools.remove(pool);
        poolMap[asset0][asset1].remove(pool);

        redeemValidityBond(pool, bondRecipient);

        emit PoolUnregistered(asset0, asset1, eulerAccount, pool);
    }

    /// @notice Sends a pool's validity bond to a recipient.
    /// @param pool The EulerSwap instance's address
    /// @param recipient Who should receive the bond
    function redeemValidityBond(address pool, address recipient) internal returns (uint256 bondAmount) {
        bondAmount = validityBonds[pool];

        if (bondAmount != 0) {
            address owner = evc.getAccountOwner(recipient);
            if (owner != address(0)) recipient = owner;
            validityBonds[pool] = 0;
            (bool success,) = recipient.call{value: bondAmount}("");
            require(success, ChallengeMissingBond());
        }
    }

    /// @notice Returns a slice of an array of addresses
    /// @dev Creates a new memory array containing elements from start to end index
    ///      If end is type(uint256).max, it will return all elements from start to the end of the array
    /// @param arr The storage array to slice
    /// @param start The starting index of the slice (inclusive)
    /// @param end The ending index of the slice (exclusive)
    /// @return A new memory array containing the requested slice of addresses
    function getSlice(EnumerableSet.AddressSet storage arr, uint256 start, uint256 end)
        internal
        view
        returns (address[] memory)
    {
        uint256 length = arr.length();
        if (end == type(uint256).max) end = length;
        if (end < start || end > length) revert SliceOutOfBounds();

        address[] memory slice = new address[](end - start);
        for (uint256 i; i < end - start; ++i) {
            slice[i] = arr.at(start + i);
        }

        return slice;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(bytes32 value => uint256) _positions;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._positions[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._positions[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 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 {
    /**
     * @dev An operation with an ERC-20 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.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    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.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    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.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    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 Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            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 {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            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 silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "lib/euler-swap/src/interfaces/IEulerSwap.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

interface IEulerSwap {
    /// @dev Constant pool parameters, loaded from trailing calldata.
    struct StaticParams {
        address supplyVault0;
        address supplyVault1;
        address borrowVault0;
        address borrowVault1;
        address eulerAccount;
        address feeRecipient;
    }

    /// @dev Reconfigurable pool parameters, loaded from storage.
    struct DynamicParams {
        uint112 equilibriumReserve0;
        uint112 equilibriumReserve1;
        uint112 minReserve0;
        uint112 minReserve1;
        uint80 priceX;
        uint80 priceY;
        uint64 concentrationX;
        uint64 concentrationY;
        uint64 fee0;
        uint64 fee1;
        uint40 expiration;
        uint8 swapHookedOperations;
        address swapHook;
    }

    /// @dev Starting configuration of pool storage.
    struct InitialState {
        uint112 reserve0;
        uint112 reserve1;
    }

    /// @notice Performs initial activation setup, such as approving vaults to access the
    /// EulerSwap instance's tokens, enabling vaults as collateral, setting up Uniswap
    /// hooks, etc. This should only be invoked by the factory.
    function activate(DynamicParams calldata dynamicParams, InitialState calldata initialState) external;

    /// @notice Installs or uninstalls a manager. Managers can reconfigure the dynamic EulerSwap parameters.
    /// Only callable by the owner (eulerAccount).
    /// @param manager Address to install/uninstall
    /// @param installed Whether the manager should be installed or uninstalled
    function setManager(address manager, bool installed) external;

    /// @notice Addresses configured as managers. Managers can reconfigure the pool parameters.
    /// @param manager Address to check
    /// @return installed Whether the address is currently a manager of this pool
    function managers(address manager) external view returns (bool installed);

    /// @notice Reconfigured the pool's parameters. Only callable by the owner (eulerAccount)
    /// or a manager.
    function reconfigure(DynamicParams calldata dParams, InitialState calldata initialState) external;

    /// @notice Retrieves the pool's static parameters.
    function getStaticParams() external view returns (StaticParams memory);

    /// @notice Retrieves the pool's dynamic parameters.
    function getDynamicParams() external view returns (DynamicParams memory);

    /// @notice Retrieves the underlying assets supported by this pool.
    function getAssets() external view returns (address asset0, address asset1);

    /// @notice Retrieves the current reserves from storage, along with the pool's lock status.
    /// @return reserve0 The amount of asset0 in the pool
    /// @return reserve1 The amount of asset1 in the pool
    /// @return status The status of the pool (0 = unactivated, 1 = unlocked, 2 = locked)
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 status);

    /// @notice Whether or not this EulerSwap instance is installed as an operator of
    /// the eulerAccount in the EVC.
    function isInstalled() external view returns (bool installed);

    /// @notice Generates a quote for how much a given size swap will cost.
    /// @param tokenIn The input token that the swapper SENDS
    /// @param tokenOut The output token that the swapper GETS
    /// @param amount The quantity of input or output tokens, for exact input and exact output swaps respectively
    /// @param exactIn True if this is an exact input swap, false if exact output
    /// @return The quoted quantity of output or input tokens, for exact input and exact output swaps respectively
    function computeQuote(address tokenIn, address tokenOut, uint256 amount, bool exactIn)
        external
        view
        returns (uint256);

    /// @notice Upper-bounds on the amounts of each token that this pool can currently support swaps for.
    /// @return limitIn Max amount of `tokenIn` that can be sold.
    /// @return limitOut Max amount of `tokenOut` that can be bought.
    function getLimits(address tokenIn, address tokenOut) external view returns (uint256 limitIn, uint256 limitOut);

    /// @notice Optimistically sends the requested amounts of tokens to the `to`
    /// address, invokes `eulerSwapCall` callback on `to` (if `data` was provided),
    /// and then verifies that a sufficient amount of tokens were transferred to
    /// satisfy the swapping curve invariant.
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
}
"
    },
    "lib/euler-swap/src/interfaces/IEulerSwapFactory.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

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

interface IEulerSwapFactory {
    /// @notice Deploy a new EulerSwap pool with the given parameters
    /// @dev The pool address is deterministically generated using CREATE2 with a salt derived from
    ///      the euler account address and provided salt parameter. This allows the pool address to be
    ///      predicted before deployment.
    /// @param sParams Static parameters
    /// @param dParams Dynamic parameters
    /// @param initialState Initial state of the pool
    /// @param salt Unique value to generate deterministic pool address
    /// @return Address of the newly deployed pool
    function deployPool(
        IEulerSwap.StaticParams memory sParams,
        IEulerSwap.DynamicParams memory dParams,
        IEulerSwap.InitialState memory initialState,
        bytes32 salt
    ) external returns (address);

    /// @notice Set of pools deployed by this factory.
    /// @param pool Address to check
    function deployedPools(address pool) external view returns (bool);

    /// @notice Given a potential pool's static parameters, this function returns the creation
    /// code that will be used to compute the pool's address.
    function creationCode(IEulerSwap.StaticParams memory sParams) external view returns (bytes memory);

    /// @notice Compute the address of a new EulerSwap pool with the given parameters
    /// @dev The pool address is deterministically generated using CREATE2 with a salt derived from
    ///      the euler account address and provided salt parameter. This allows the pool address to be
    ///      predicted before deployment.
    /// @param sParams Static parameters
    /// @param salt Unique value to generate deterministic pool address
    /// @return Address of the newly deployed pool
    function computePoolAddress(IEulerSwap.StaticParams memory sParams, bytes32 salt) external view returns (address);
}
"
    },
    "lib/euler-swap/src/interfaces/IEulerSwapRegistry.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

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

interface IEulerSwapRegistry {
    /// @notice Registers an EulerSwap pool
    /// @param pool Address of pool created by EulerSwapFactory
    function registerPool(address pool) external payable;

    /// @notice Uninstalls the pool associated with the calling Euler account
    /// @dev This function removes the pool from the registry's tracking and emits a PoolUnregistered event
    /// @dev The function can only be called by the Euler account that owns the pool
    /// @dev If no pool is installed for the caller, the function returns without any action
    function unregisterPool() external;

    /// @notice Remove a pool from the pools and poolsByPair lists. This can be used to
    /// clean-up incorrectly configured pools. Only callable by the curator.
    /// @param pool Address of the EulerSwap instance to remove.
    /// @param bondReceiver Where to send the validity bond. If address(0), it is returned to pool creator.
    function curatorUnregisterPool(address pool, address bondReceiver) external;

    /// @notice Changes the address with curator privileges. Only callable by the curator.
    /// @param newCurator New address to give curator privileges. Caller gives up the privileges.
    function transferCurator(address newCurator) external;

    /// @notice Updates the minimum validity bond required to create new pools. Validity bonds
    /// are in native token (ie ETH).
    /// @param newMinimum The new minimum bond value, in native token
    function setMinimumValidityBond(uint256 newMinimum) external;

    /// @notice Updates the valid vault perspective.
    /// @param newPerspective The new perspective's address.
    function setValidVaultPerspective(address newPerspective) external;

    /// @notice Attempt to remove a pool from the pools and poolsByPair lists. Anyone can invoke
    /// this function in order to retrieve the pool's posted validity bond. This function will
    /// retrieve a quote and actually attempt to execute a swap for that quote. If it succeeds,
    /// the function will revert. If it fails with E_AccountLiquidity, then the bond is transferred
    /// to the caller.
    /// @param poolAddr Pool that the caller believe is issuing invalid quotes.
    /// @param tokenIn Input token address
    /// @param tokenOut Output token address
    /// @param amount The amount of token to quote
    /// @param exactIn Whether the amount parameter refers to an input or output amount
    /// @param recipient Address to send the validity bond, if challenge was successful
    function challengePool(
        address poolAddr,
        address tokenIn,
        address tokenOut,
        uint256 amount,
        bool exactIn,
        address recipient
    ) external;

    /// @notice Returns a slice of all deployed pools
    /// @dev Returns a subset of the pools array from start to end index
    /// @param start The starting index of the slice (inclusive)
    /// @param end The ending index of the slice (exclusive)
    /// @return An array containing the requested slice of pool addresses
    function poolsSlice(uint256 start, uint256 end) external view returns (address[] memory);

    /// @notice Returns all deployed pools
    /// @dev Returns the complete array of all pool addresses
    /// @return An array containing all pool addresses
    function pools() external view returns (address[] memory);

    /// @notice Returns the number of pools for a specific asset pair
    /// @dev Returns the length of the pool array for the given asset pair
    /// @param asset0 The address of the first asset
    /// @param asset1 The address of the second asset
    /// @return The number of pools for the specified asset pair
    function poolsByPairLength(address asset0, address asset1) external view returns (uint256);

    /// @notice Returns a slice of pools for a specific asset pair
    /// @dev Returns a subset of the pools array for the given asset pair from start to end index
    /// @param asset0 The address of the first asset
    /// @param asset1 The address of the second asset
    /// @param start The starting index of the slice (inclusive)
    /// @param end The ending index of the slice (exclusive)
    /// @return An array containing the requested slice of pool addresses for the asset pair
    function poolsByPairSlice(address asset0, address asset1, uint256 start, uint256 end)
        external
        view
        returns (address[] memory);

    /// @notice Returns all pools for a specific asset pair
    /// @dev Returns the complete array of pool addresses for the given asset pair
    /// @param asset0 The address of the first asset
    /// @param asset1 The address of the second asset
    /// @return An array containing all pool addresses for the specified asset pair
    function poolsByPair(address asset0, address asset1) external view returns (address[] memory);

    /// @notice Returns the pool address associated with a specific holder
    /// @dev Returns the pool address from the EulerAccountState mapping for the given holder
    /// @param who The address of the holder to query
    /// @return The address of the pool associated with the holder
    function poolByEulerAccount(address who) external view returns (address);

    /// @notice Returns the total number of deployed pools
    /// @dev Returns the length of the allPools array
    /// @return The total number of pools deployed through the factory
    function poolsLength() external view returns (uint256);

    /// @notice Size of validity bond for a given pool, in native token (ie ETH).
    /// @param pool EulerSwap instance address.
    /// @return The size of the validity bond, or 0 if none.
    function validityBond(address pool) external view returns (uint256);
}
"
    },
    "lib/euler-swap/src/interfaces/IPerspective.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @title IPerspective
/// @custom:security-contact security@euler.xyz
/// @author Euler Labs (https://www.eulerlabs.com/)
/// @notice A contract that verifies the properties of a vault.
interface IPerspective {
    /// @notice Checks if a vault is verified.
    /// @param vault The address of the vault to check.
    /// @return True if the vault is verified, false otherwise.
    function isVerified(address vault) external view returns (bool);
}
"
    },
    "lib/euler-swap/src/libraries/SwapLib.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {SafeERC20, IERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";

import {IEVault} from "evk/EVault/IEVault.sol";

import {CtxLib} from "./CtxLib.sol";
import {CurveLib} from "./CurveLib.sol";
import {FundsLib} from "./FundsLib.sol";
import {QuoteLib} from "./QuoteLib.sol";
import {EulerSwapProtocolFeeConfig} from "../EulerSwapProtocolFeeConfig.sol";
import {IEulerSwap} from "../interfaces/IEulerSwap.sol";
import "../interfaces/IEulerSwapHookTarget.sol";

library SwapLib {
    using SafeERC20 for IERC20;

    /// @notice Emitted after every swap.
    ///   * `sender` is the initiator of the swap, or the Router when invoked via hook.
    ///   * `amount0In` and `amount1In` are after fees have been subtracted.
    ///   * `fee0` and `fee1` are the amount of input tokens received fees.
    ///   * `reserve0` and `reserve1` are the pool's new reserves (after the swap).
    ///   * `to` is the specified recipient of the funds, or the PoolManager when invoked via hook.
    event Swap(
        address indexed sender,
        uint256 amount0In,
        uint256 amount1In,
        uint256 amount0Out,
        uint256 amount1Out,
        uint256 fee0,
        uint256 fee1,
        uint112 reserve0,
        uint112 reserve1,
        address indexed to
    );

    error CurveViolation();
    error HookError(uint8 hookFlag, bytes wrappedError);

    struct SwapContext {
        // Populated by init
        address evc;
        address protocolFeeConfig;
        IEulerSwap.StaticParams sParams;
        IEulerSwap.DynamicParams dParams;
        address asset0;
        address asset1;
        address sender;
        address to;
        // Amount parameters
        uint256 amount0InFull;
        uint256 amount1InFull;
        uint256 amount0Out;
        uint256 amount1Out;
        // Internal
        uint256 amount0In; // full minus fees
        uint256 amount1In; // full minus fees
    }

    function init(address evc, address protocolFeeConfig, address sender, address to)
        internal
        view
        returns (SwapContext memory ctx)
    {
        ctx.evc = evc;
        ctx.protocolFeeConfig = protocolFeeConfig;
        ctx.sParams = CtxLib.getStaticParams();
        ctx.dParams = CtxLib.getDynamicParams();

        ctx.asset0 = IEVault(ctx.sParams.supplyVault0).asset();
        ctx.asset1 = IEVault(ctx.sParams.supplyVault1).asset();
        ctx.sender = sender;
        ctx.to = to;

        require(ctx.dParams.expiration == 0 || ctx.dParams.expiration > block.timestamp, QuoteLib.Expired());
    }

    function setAmountsOut(SwapContext memory ctx, uint256 amount0Out, uint256 amount1Out) internal pure {
        ctx.amount0Out = amount0Out;
        ctx.amount1Out = amount1Out;
    }

    function setAmountsIn(SwapContext memory ctx, uint256 amount0InFull, uint256 amount1InFull) internal pure {
        ctx.amount0InFull = amount0InFull;
        ctx.amount1InFull = amount1InFull;
    }

    function invokeBeforeSwapHook(SwapContext memory ctx) internal {
        if ((ctx.dParams.swapHookedOperations & EULER_SWAP_HOOK_BEFORE_SWAP) == 0) return;

        (bool success, bytes memory data) = ctx.dParams.swapHook.call(
            abi.encodeCall(IEulerSwapHookTarget.beforeSwap, (ctx.amount0Out, ctx.amount1Out, ctx.sender, ctx.to))
        );
        require(success, HookError(EULER_SWAP_HOOK_BEFORE_SWAP, data));
    }

    function invokeAfterSwapHook(SwapContext memory ctx, CtxLib.State storage s, uint256 fee0, uint256 fee1) internal {
        if ((ctx.dParams.swapHookedOperations & EULER_SWAP_HOOK_AFTER_SWAP) == 0) return;

        s.status = 1; // Unlock the reentrancy guard during afterSwap, allowing hook to reconfigure()

        (bool success, bytes memory data) = ctx.dParams.swapHook.call(
            abi.encodeCall(
                IEulerSwapHookTarget.afterSwap,
                (
                    ctx.amount0In,
                    ctx.amount1In,
                    ctx.amount0Out,
                    ctx.amount1Out,
                    fee0,
                    fee1,
                    ctx.sender,
                    ctx.to,
                    s.reserve0,
                    s.reserve1
                )
            )
        );
        require(success, HookError(EULER_SWAP_HOOK_AFTER_SWAP, data));

        s.status = 2;
    }

    function doDeposits(SwapContext memory ctx) internal {
        doDeposit(ctx, true);
        doDeposit(ctx, false);
    }

    function doWithdraws(SwapContext memory ctx) internal {
        doWithdraw(ctx, false);
        doWithdraw(ctx, true);
    }

    function finish(SwapContext memory ctx) internal {
        CtxLib.State storage s = CtxLib.getState();

        uint256 newReserve0 = s.reserve0 + ctx.amount0In - ctx.amount0Out;
        uint256 newReserve1 = s.reserve1 + ctx.amount1In - ctx.amount1Out;

        require(CurveLib.verify(ctx.dParams, newReserve0, newReserve1), CurveViolation());

        s.reserve0 = uint112(newReserve0);
        s.reserve1 = uint112(newReserve1);

        uint256 fee0 = ctx.amount0InFull - ctx.amount0In;
        uint256 fee1 = ctx.amount1InFull - ctx.amount1In;

        emit Swap(
            ctx.sender,
            ctx.amount0In,
            ctx.amount1In,
            ctx.amount0Out,
            ctx.amount1Out,
            fee0,
            fee1,
            s.reserve0,
            s.reserve1,
            ctx.to
        );

        invokeAfterSwapHook(ctx, s, fee0, fee1);
    }

    // Private

    function doDeposit(SwapContext memory ctx, bool asset0IsInput) private {
        uint256 amount = asset0IsInput ? ctx.amount0InFull : ctx.amount1InFull;
        if (amount == 0) return;

        address assetInput = asset0IsInput ? ctx.asset0 : ctx.asset1;

        uint256 fee = QuoteLib.getFee(ctx.dParams, asset0IsInput);
        require(fee < 1e18, QuoteLib.SwapRejected());

        uint256 feeAmount = amount * fee / 1e18;

        // Slice off protocol fee

        {
            (address protocolFeeRecipient, uint64 protocolFee) =
                EulerSwapProtocolFeeConfig(ctx.protocolFeeConfig).getProtocolFee(address(this));

            if (protocolFee != 0) {
                uint256 protocolFeeAmount = feeAmount * protocolFee / 1e18;

                if (protocolFeeAmount != 0) {
                    IERC20(assetInput).safeTransfer(protocolFeeRecipient, protocolFeeAmount);

                    amount -= protocolFeeAmount;
                    feeAmount -= protocolFeeAmount;
                }
            }
        }

        // Slice off separate LP fee recipient

        if (ctx.sParams.feeRecipient != address(0) && feeAmount != 0) {
            IERC20(assetInput).safeTransfer(ctx.sParams.feeRecipient, feeAmount);

            amount -= feeAmount;
            feeAmount = 0;
        }

        // Deposit remainder on behalf of eulerAccount

        amount = FundsLib.depositAssets(
            ctx.evc,
            ctx.sParams.eulerAccount,
            asset0IsInput ? ctx.sParams.supplyVault0 : ctx.sParams.supplyVault1,
            asset0IsInput ? ctx.sParams.borrowVault0 : ctx.sParams.borrowVault1,
            amount
        );

        amount = amount > feeAmount ? amount - feeAmount : 0;

        if (asset0IsInput) ctx.amount0In = amount;
        else ctx.amount1In = amount;
    }

    function doWithdraw(SwapContext memory ctx, bool asset0IsInput) private {
        uint256 amount = asset0IsInput ? ctx.amount1Out : ctx.amount0Out;
        if (amount == 0) return;

        FundsLib.withdrawAssets(
            ctx.evc,
            ctx.sParams.eulerAccount,
            asset0IsInput ? ctx.sParams.supplyVault1 : ctx.sParams.supplyVault0,
            asset0IsInput ? ctx.sParams.borrowVault1 : ctx.sParams.borrowVault0,
            amount,
            ctx.to
        );
    }
}
"
    },
    "lib/ethereum-vault-connector/src/utils/EVCUtil.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IEVC} from "../interfaces/IEthereumVaultConnector.sol";
import {ExecutionContext, EC} from "../ExecutionContext.sol";

/// @title EVCUtil
/// @custom:security-contact security@euler.xyz
/// @author Euler Labs (https://www.eulerlabs.com/)
/// @notice This contract is an abstract base contract for interacting with the Ethereum Vault Connector (EVC).
/// It provides utility functions for authenticating the callers in the context of the EVC, a pattern for enforcing the
/// contracts to be called through the EVC.
abstract contract EVCUtil {
    using ExecutionContext for EC;

    uint160 internal constant ACCOUNT_ID_OFFSET = 8;
    IEVC internal immutable evc;

    error EVC_InvalidAddress();
    error NotAuthorized();
    error ControllerDisabled();

    constructor(address _evc) {
        if (_evc == address(0)) revert EVC_InvalidAddress();

        evc = IEVC(_evc);
    }

    /// @notice Returns the address of the Ethereum Vault Connector (EVC) used by this contract.
    /// @return The address of the EVC contract.
    function EVC() external view virtual returns (address) {
        return address(evc);
    }

    /// @notice Ensures that the msg.sender is the EVC by using the EVC callback functionality if necessary.
    /// @dev Optional to use for functions requiring account and vault status checks to enforce predictable behavior.
    /// @dev If this modifier used in conjuction with any other modifier, it must appear as the first (outermost)
    /// modifier of the function.
    modifier callThroughEVC() virtual {
        _callThroughEVC();
        _;
    }

    /// @notice Ensures that the caller is the EVC in the appropriate context.
    /// @dev Should be used for checkAccountStatus and checkVaultStatus functions.
    modifier onlyEVCWithChecksInProgress() virtual {
        _onlyEVCWithChecksInProgress();
        _;
    }

    /// @notice Ensures a standard authentication path on the EVC allowing the account owner or any of its EVC accounts.
    /// @dev This modifier checks if the caller is the EVC and if so, verifies the execution context.
    /// It reverts if the operator is authenticated, control collateral is in progress, or checks are in progress.
    /// @dev This modifier must not be used on functions utilized by liquidation flows, i.e. transfer or withdraw.
    /// @dev This modifier must not be used on checkAccountStatus and checkVaultStatus functions.
    /// @dev This modifier can be used on access controlled functions to prevent non-standard authentication paths on
    /// the EVC.
    modifier onlyEVCAccount() virtual {
        _authenticateCallerWithStandardContextState(false);
        _;
    }

    /// @notice Ensures a standard authentication path on the EVC.
    /// @dev This modifier checks if the caller is the EVC and if so, verifies the execution context.
    /// It reverts if the operator is authenticated, control collateral is in progress, or checks are in progress.
    /// It reverts if the authenticated account owner is known and it is not the account owner.
    /// @dev It assumes that if the caller is not the EVC, the caller is the account owner.
    /// @dev This modifier must not be used on functions utilized by liquidation flows, i.e. transfer or withdraw.
    /// @dev This modifier must not be used on checkAccountStatus and checkVaultStatus functions.
    /// @dev This modifier can be used on access controlled functions to prevent non-standard authentication paths on
    /// the EVC.
    modifier onlyEVCAccountOwner() virtual {
        _authenticateCallerWithStandardContextState(true);
        _;
    }

    /// @notice Checks whether the specified account and the other account have the same owner.
    /// @dev The function is used to check whether one account is authorized to perform operations on behalf of the
    /// other. Accounts are considered to have a common owner if they share the first 19 bytes of their address.
    /// @param account The address of the account that is being checked.
    /// @param otherAccount The address of the other account that is being checked.
    /// @return A boolean flag that indicates whether the accounts have the same owner.
    function _haveCommonOwner(address account, address otherAccount) internal pure returns (bool) {
        bool result;
        assembly {
            result := lt(xor(account, otherAccount), 0x100)
        }
        return result;
    }

    /// @notice Returns the address prefix of the specified account.
    /// @dev The address prefix is the first 19 bytes of the account address.
    /// @param account The address of the account whose address prefix is being retrieved.
    /// @return A bytes19 value that represents the address prefix of the account.
    function _getAddressPrefix(address account) internal pure returns (bytes19) {
        return bytes19(uint152(uint160(account) >> ACCOUNT_ID_OFFSET));
    }

    /// @notice Retrieves the message sender in the context of the EVC.
    /// @dev This function returns the account on behalf of which the current operation is being performed, which is
    /// either msg.sender or the account authenticated by the EVC.
    /// @return The address of the message sender.
    function _msgSender() internal view virtual returns (address) {
        address sender = msg.sender;

        if (sender == address(evc)) {
            (sender,) = evc.getCurrentOnBehalfOfAccount(address(0));
        }

        return sender;
    }

    /// @notice Retrieves the message sender in the context of the EVC for a borrow operation.
    /// @dev This function returns the account on behalf of which the current operation is being performed, which is
    /// either msg.sender or the account authenticated by the EVC. This function reverts if this contract is not enabled
    /// as a controller for the account on behalf of which the operation is being executed.
    /// @return The address of the message sender.
    function _msgSenderForBorrow() internal view virtual returns (address) {
        address

Tags:
ERC20, ERC165, Multisig, Mintable, Burnable, Swap, Liquidity, Yield, Voting, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x5fccb84363f020c0cade052c9c654aabf932814a|verified:true|block:23697908|tx:0x23245f73dbbc530ec89be569251ee620e5692b4769cda59dd2fc83f686eea5f2|first_check:1761924252

Submitted on: 2025-10-31 16:24:13

Comments

Log in to comment.

No comments yet.